diff --git a/Vagrant/chef/cookbooks/default/recipes/default.rb b/Vagrant/chef/cookbooks/default/recipes/default.rb new file mode 100644 index 0000000..3a8e1eb --- /dev/null +++ b/Vagrant/chef/cookbooks/default/recipes/default.rb @@ -0,0 +1,117 @@ +execute "add dotdeb repo" do + user "root" + not_if "grep 'deb http://packages.dotdeb.org wheezy all' /etc/apt/sources.list" + command "echo 'deb http://packages.dotdeb.org wheezy all' >> /etc/apt/sources.list && echo 'deb-src http://packages.dotdeb.org wheezy all' >> /etc/apt/sources.list" +end + +execute "add dotdeb key" do + user "root" + not_if "apt-key finger | grep 'Key fingerprint = 6572 BBEF 1B5F F28B 28B7 0683 7E3F 0700 89DF 5277'" + command "wget http://www.dotdeb.org/dotdeb.gpg && apt-key add dotdeb.gpg && apt-get update" +end + +# Run apt-get update to create the stamp file +execute "apt-get-update" do + command "apt-get update" + ignore_failure true + not_if do ::File.exists?('/var/lib/apt/periodic/update-success-stamp') end +end + +# For other recipes to call to force an update +execute "apt-get update" do + command "apt-get update" + ignore_failure true + action :nothing +end + +# provides /var/lib/apt/periodic/update-success-stamp on apt-get update +package "update-notifier-common" do + notifies :run, resources(:execute => "apt-get-update"), :immediately +end + +execute "apt-get-update-periodic" do + command "apt-get update" + ignore_failure true + only_if do + File.exists?('/var/lib/apt/periodic/update-success-stamp') && + File.mtime('/var/lib/apt/periodic/update-success-stamp') < Time.now - 86400 + end +end + +# install the software we need +%w( +curl +vim +git +libapache2-mod-php5 +php5-cli +php5-curl +php5-gd +php5-intl +php5-mysql +mysql-server +php5-mcrypt +php5-memcached +php-apc +redis-server +php5-redis +htop +unzip +).each { | pkg | package pkg } + +# upgrade system packages +execute "apt-get-upgrade-system" do + command "apt-get upgrade" + ignore_failure false + only_if do + File.exists?('/var/lib/apt/periodic/update-success-stamp') && + File.mtime('/var/lib/apt/periodic/update-success-stamp') < Time.now - 86400 + end +end + +execute "apache-enable-mod-rewrite" do + user "root" + command "a2enmod rewrite" + notifies :reload, "service[apache2]" +end + +execute "apache-enable-mod-ssl" do + user "root" + command "a2enmod ssl" + notifies :reload, "service[apache2]" +end + +service "apache2" do + supports :restart => true, :reload => true, :status => true + action [ :enable, :start ] +end + +execute "check if date.timezone is Europe/Berlin in /etc/php5/apache2/php.ini?" do + user "root" + not_if "grep '^date.timezone = Europe/Berlin' /etc/php5/apache2/php.ini" + command "sed -i 's/;date.timezone =.*/date.timezone = Europe\\/Berlin/g' /etc/php5/apache2/php.ini" +end + +execute "check if date.timezone is Europe/Berlin in /etc/php5/cli/php.ini?" do + user "root" + not_if "grep '^date.timezone = Europe/Berlin' /etc/php5/cli/php.ini" + command "sed -i 's/;date.timezone =.*/date.timezone = Europe\\/Berlin/g' /etc/php5/cli/php.ini" +end + +execute "check if memory_limit is set to the correct value in /etc/php5/apache2/php.ini?" do + user "root" + not_if "grep 'memory_limit = 256M' /etc/php5/apache2/php.ini" + command "sed -i 's/memory_limit =.*/memory_limit = 256M/g' /etc/php5/apache2/php.ini" +end + +execute "check if memory_limit is set to the correct value /etc/php5/cli/php.ini?" do + user "root" + not_if "grep 'memory_limit = 512M' /etc/php5/cli/php.ini" + command "sed -i 's/memory_limit =.*/memory_limit = 512M/g' /etc/php5/cli/php.ini" +end + +execute "check if max_execution_time is set to the correct value in /etc/php5/apache2/php.ini?" do + user "root" + not_if "grep 'max_execution_time = 60' /etc/php5/apache2/php.ini" + command "sed -i 's/max_execution_time =.*/max_execution_time = 60/g' /etc/php5/apache2/php.ini" +end diff --git a/Vagrant/chef/cookbooks/default/templates/default/ssl_vhost.conf.erb b/Vagrant/chef/cookbooks/default/templates/default/ssl_vhost.conf.erb new file mode 100644 index 0000000..4a4af8c --- /dev/null +++ b/Vagrant/chef/cookbooks/default/templates/default/ssl_vhost.conf.erb @@ -0,0 +1,38 @@ + + ServerName <%= @name %> + <% if @aliases %> + ServerAlias <% @aliases.each do |a| %><%= a %> <% end %> + <% end %> + RewriteEngine On + RewriteRule ^(.*) https://<%= @name %>$1 [R=301,L] + + + + ServerName <%= @name %> + <% if @aliases %> + ServerAlias <% @aliases.each do |a| %><%= a %> <% end %> + <% end %> + DocumentRoot <%= @docroot %> + "> + Options FollowSymLinks + AllowOverride All + Order allow,deny + Allow from all + + + Options FollowSymLinks + AllowOverride None + + + # SSL settings + SSLEngine on + SSLCertificateFile /etc/ssl/certs/<%= @name %>.crt.pem + SSLCertificateKeyFile /etc/ssl/private/<%= @name %>.key.pem + BrowserMatch "MSIE [2-6]" nokeepalive ssl-unclean-shutdown downgrade-1.0 force-response-1.0 + # MSIE 7 and newer should be able to use keepalive + BrowserMatch "MSIE [7-9]" ssl-unclean-shutdown + + LogLevel info + ErrorLog /var/log/apache2/<%= @name %>-error.log + CustomLog /var/log/apache2/<%= @name %>-access.log combined + \ No newline at end of file diff --git a/Vagrant/chef/cookbooks/default/templates/default/vhost.conf.erb b/Vagrant/chef/cookbooks/default/templates/default/vhost.conf.erb new file mode 100644 index 0000000..c4a440b --- /dev/null +++ b/Vagrant/chef/cookbooks/default/templates/default/vhost.conf.erb @@ -0,0 +1,20 @@ + + ServerName <%= @name %> + <% if @aliases %> + ServerAlias <% @aliases.each do |a| %><%= a %> <% end %> + <% end %> + DocumentRoot <%= @docroot %> + "> + Options FollowSymLinks + AllowOverride All + Order allow,deny + Allow from all + + + Options FollowSymLinks + AllowOverride None + + LogLevel info + ErrorLog /var/log/apache2/<%= @name %>-error.log + CustomLog /var/log/apache2/<%= @name %>-access.log combined + \ No newline at end of file diff --git a/Vagrant/chef/cookbooks/dev/recipes/default.rb b/Vagrant/chef/cookbooks/dev/recipes/default.rb new file mode 100644 index 0000000..e45d344 --- /dev/null +++ b/Vagrant/chef/cookbooks/dev/recipes/default.rb @@ -0,0 +1,96 @@ +include_recipe "default" + +# Install dev only software. +%w( +php5-xdebug +).each { | pkg | package pkg } + +# Change apache user to avoid permission issues. +execute "Change apache user in /etc/apache2/apache2.conf" do + user "root" + not_if "grep '^User vagrant' /etc/apache2/apache2.conf" + command "echo 'User vagrant' >> /etc/apache2/apache2.conf" +end + +execute "Change apache group in /etc/apache2/apache2.conf" do + user "root" + not_if "grep '^Group vagrant' /etc/apache2/apache2.conf" + command "echo 'Group vagrant' >> /etc/apache2/apache2.conf" +end + +execute "Set default server name in /etc/apache2/apache2.conf" do + user "root" + not_if "grep '^ServerName' /etc/apache2/apache2.conf" + command "echo 'ServerName sites.dev' >> /etc/apache2/apache2.conf" +end + +vhosts = [] +begin + vhosts = data_bag("vhosts") +rescue + puts "Vhost data bag is empty" +end +vhosts.each do | name | + vhost = data_bag_item("vhosts", name) + conffile = "/etc/apache2/sites-available/#{vhost['name']}.conf" + if vhost['type'] == "SSL" + execute "Generate certificate" do + user "root" + command "openssl req -x509 -newkey rsa:2048 -keyout /tmp/#{vhost['name']}.key.pem -out /tmp/#{vhost['name']}.crt.pem -days 365 -nodes -subj '/C=DE/ST=Bavaria/L=Munic/CN=#{vhost['name']}' && mv /tmp/#{vhost['name']}.crt.pem /etc/ssl/certs/#{vhost['name']}.crt.pem && mv /tmp/#{vhost['name']}.key.pem /etc/ssl/private/#{vhost['name']}.key.pem" + not_if "test -f /etc/ssl/certs/#{vhost['name']}.crt.pem && test -f /etc/ssl/private/#{vhost['name']}.key.pem" + end + template conffile do + user "root" + mode "0644" + source "ssl_vhost.conf.erb" + cookbook "default" + notifies :reload, "service[apache2]" + variables ({ + :name => vhost['name'], + :aliases => vhost['aliases'], + :docroot => vhost['docroot'] + }) + end + else + template conffile do + user "root" + mode "0644" + source "vhost.conf.erb" + cookbook "default" + notifies :reload, "service[apache2]" + variables ({ + :name => vhost['name'], + :aliases => vhost['aliases'], + :docroot => vhost['docroot'] + }) + end + end + execute "Remove not needed vhosts" do + user "root" + command "cd /etc/apache2/sites-available && rm `ls | grep -v '^#{vhost['name']}.conf$'` && cd /etc/apache2/sites-enabled && rm `ls | grep -v '^#{vhost['name']}.conf$'`" + only_if "ls /etc/apache2/sites-available | grep -v '^#{vhost['name']}.conf$'" + end + execute "Link vhost to enabled sites" do + user "root" + command "ln -s #{conffile} /etc/apache2/sites-enabled/#{vhost['name']}.conf" + not_if "test -L /etc/apache2/sites-enabled/#{vhost['name']}.conf" + end +end + +# Set up Xdebug. +xdebug = data_bag_item("config", "xdebug") + +template "/etc/php5/mods-available/xdebug.ini" do + user "root" + mode "0644" + source "xdebug.ini.erb" + notifies :reload, "service[apache2]" + variables ({ + :hostip => xdebug['hostip'] + }) +end +execute "Activate Xdebug" do + user "root" + command "ln -s /etc/php5/mods-available/xdebug.ini /etc/php5/conf.d/20-xdebug.ini" + not_if "test -L /etc/php5/conf.d/20-xdebug.ini" +end diff --git a/Vagrant/chef/cookbooks/dev/templates/default/xdebug.ini.erb b/Vagrant/chef/cookbooks/dev/templates/default/xdebug.ini.erb new file mode 100644 index 0000000..35a4285 --- /dev/null +++ b/Vagrant/chef/cookbooks/dev/templates/default/xdebug.ini.erb @@ -0,0 +1,12 @@ +zend_extension=/usr/lib/php5/20100525/xdebug.so + +[xdebug] +xdebug.remote_enable=On +xdebug.remote_host="<%= @hostip %>" +xdebug.remote_port=9000 +xdebug.remote_handler="dbgp" +xdebug.cli_color=1 +xdebug.overload_var_dump=0 +xdebug.show_mem_delta=1 +xdebug.trace_format=1 +xdebug.max_nesting_level=250 \ No newline at end of file diff --git a/Vagrant/chef/data_bags/dev/config/xdebug.json b/Vagrant/chef/data_bags/dev/config/xdebug.json new file mode 100755 index 0000000..acc5fea --- /dev/null +++ b/Vagrant/chef/data_bags/dev/config/xdebug.json @@ -0,0 +1,4 @@ +{ + "id": "xdebug", + "hostip": "10.2.254.1" +} \ No newline at end of file diff --git a/Vagrant/chef/data_bags/dev/vhosts/web-api-extension.dev.json b/Vagrant/chef/data_bags/dev/vhosts/web-api-extension.dev.json new file mode 100755 index 0000000..dad24ef --- /dev/null +++ b/Vagrant/chef/data_bags/dev/vhosts/web-api-extension.dev.json @@ -0,0 +1,9 @@ +{ + "id": "web-api-extension", + "type": "SSL", + "name": "web-api-extension.dev", + "aliases": [ + "web-api-extension.dev" + ], + "docroot": "/vagrant/web" +} \ No newline at end of file diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 0000000..0d818f9 --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,23 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +# Vagrantfile API/syntax version. Don't touch unless you know what you're doing! +VAGRANTFILE_API_VERSION = "2" + +Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| + config.vm.box = "sites" + config.vm.box_url = "file:///Volumes/Jarlssen/98\ Development/41\ Vagrant\ Boxes/sites.jarlssen.de/sites-cheflatest-virtualbox.box" + config.vm.hostname = "web-api-ext-dev" + config.vm.synced_folder ".", "/vagrant", :nfs => true + config.vm.network :private_network, ip: "10.2.254.4" + config.vm.provision :chef_solo do |chef| + chef.cookbooks_path = "Vagrant/chef/cookbooks" + chef.data_bags_path = "Vagrant/chef/data_bags/dev" + # chef debug level, start vagrant like this to debug: + # $ CHEF_LOG_LEVEL=debug vagrant + chef.log_level = ENV['CHEF_LOG'] || "info" + # chef recipes + chef.add_recipe("default") + chef.add_recipe("dev") + end + end diff --git a/Vagrantfile.dist b/Vagrantfile.dist new file mode 100644 index 0000000..5e1edc8 --- /dev/null +++ b/Vagrantfile.dist @@ -0,0 +1,23 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +# Vagrantfile API/syntax version. Don't touch unless you know what you're doing! +VAGRANTFILE_API_VERSION = "2" + +Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| + config.vm.box = "sites" + config.vm.box_url = "file:///Volumes/Jarlssen/98\ Development/41\ Vagrant\ Boxes/sites.jarlssen.de/sites-cheflatest-virtualbox.box" + config.vm.hostname = "web-api-ext-dev" + config.vm.synced_folder ".", "/vagrant", :nfs => true + config.vm.network :private_network, ip: "10.2.254.2" + config.vm.provision :chef_solo do |chef| + chef.cookbooks_path = "Vagrant/chef/cookbooks" + chef.data_bags_path = "Vagrant/chef/data_bags/dev" + # chef debug level, start vagrant like this to debug: + # $ CHEF_LOG_LEVEL=debug vagrant + chef.log_level = ENV['CHEF_LOG'] || "info" + # chef recipes + chef.add_recipe("default") + chef.add_recipe("dev") + end + end diff --git a/src/Context/WebApiContext.php b/src/Context/WebApiContext.php index f21b24b..c09cce0 100644 --- a/src/Context/WebApiContext.php +++ b/src/Context/WebApiContext.php @@ -20,35 +20,39 @@ * Provides web API description definitions. * * @author Konstantin Kudryashov + * @author Tomaz Ahlin (Improvements only) */ class WebApiContext implements ApiClientAwareContext { /** * @var string */ - private $authorization; + protected $authorization; /** * @var ClientInterface */ - private $client; + protected $client; /** * @var array */ - private $headers = array(); + protected $headers = array(); /** * @var \GuzzleHttp\Message\RequestInterface */ - private $request; + protected $request; /** * @var \GuzzleHttp\Message\ResponseInterface */ - private $response; + protected $response; - private $placeHolders = array(); + /** + * @var array + */ + protected $placeHolders = array(); /** * {@inheritdoc} @@ -124,7 +128,7 @@ public function iSendARequestWithValues($method, $url, TableNode $post) } $bodyOption = array( - 'body' => json_encode($fields), + 'body' => json_encode($fields), ); $this->request = $this->getClient()->createRequest($method, $url, $bodyOption); if (!empty($this->headers)) { @@ -174,8 +178,17 @@ public function iSendARequestWithFormData($method, $url, PyStringNode $body) $body = $this->replacePlaceHolder(trim($body)); $fields = array(); - parse_str(implode('&', explode("\n", $body)), $fields); + + $dataLines = explode("\n", $body); + foreach($dataLines as $line) { + $line = explode('=', $line); + $fields[$line[0]] = $line[1]; + } + $this->request = $this->getClient()->createRequest($method, $url); + if (!empty($this->headers)) { + $this->request->addHeaders($this->headers); + } /** @var \GuzzleHttp\Post\PostBodyInterface $requestBody */ $requestBody = $this->request->getBody(); foreach ($fields as $key => $value) { @@ -227,6 +240,145 @@ public function theResponseShouldNotContain($text) Assertions::assertNotRegExp($expectedRegexp, $actual); } + /** + * Checks that response is json and stores the data to array. + * + * @Then /^(?:the )?response should be json$/ + */ + public function theResponseShouldBeJson() + { + $data = json_decode($this->response->getBody(), true); + Assertions::assertNotFalse($data); + } + + /** + * Checks that response is json and it contains desired keys. + * + * @param string $key + * @param string $type + * @param string $subKeys + * + * @Then /^(?:the )?response should contain "([^"]*)" with "(at least|exactly)" "([^"]*)"$/ + */ + public function theResponseShouldContainKeys($key, $type, $subKeys) + { + $this->theResponseShouldContainOneItem($key, $type, $subKeys, false); + } + + /** + * Checks that response is json and it contains desired keys and values. + * + * @param string $key + * @param string $type + * @param string $subKeys + * @param string $subKeyValues + * + * @Then /^(?:the )?response should contain "([^"]*)" with "(at least|exactly)" "([^"]*)" with values "([^"]*)"$/ + */ + public function theResponseShouldContainKeysAndValues($key, $type, $subKeys, $subKeyValues) + { + $this->theResponseShouldContainOneItem($key, $type, $subKeys, true, $subKeyValues); + } + + /** + * Checks that response is json and stores the data to array. + * + * @param string $key + * @param string $type + * @param string $subKeys + * @param boolean $compareValues + * @param string $subKeyValues + */ + private function theResponseShouldContainOneItem($key, $type, $subKeys, $compareValues, $subKeyValues = '') + { + $data = json_decode($this->response->getBody(), true); + Assertions::assertNotFalse($data); + + $subKeys = array_map('trim', explode(',', $subKeys)); + $subKeyValues = array_map('trim', explode(',', $subKeyValues)); + + if ($compareValues) { + Assertions::assertEquals(count($subKeyValues), count($subKeys)); + } + + Assertions::assertArrayHasKey('data', $data); + + for ($j=0, $c=count($subKeys); $j<$c; $j++) { + $subKey = $subKeys[$j]; + if ($this->isExactType($type)) { + Assertions::assertEquals(count($subKeys), count($data[$key])); + } + Assertions::assertArrayHasKey($subKey, $data[$key]); + if ($compareValues) { + Assertions::assertEquals($subKeyValues[$j], $data[$key][$subKey]); + } + } + } + + /** + * Checks that response is json and contains the expected number of items with the desired keys. + * + * @param string $key + * @param integer $n + * @param string $type + * @param string $subKeys + * + * @Then /^(?:the )?response should contain "([^"]*)" with (\d+) items containing "(at least|exactly)" "([^"]*)"$/ + */ + public function theResponseShouldContainItemsWithKeys($key, $n, $type, $subKeys) + { + $this->theResponseShouldContainItems($key, $n, $type, $subKeys, false); + } + + /** + * Checks that response is json and contains the expected number of items with the desired keys. + * + * @param string $key + * @param integer $n + * @param string $type + * @param string $subKeys + * @param boolean $compareValues + * @param string $subKeyValues + */ + private function theResponseShouldContainItems($key, $n, $type, $subKeys, $compareValues, $subKeyValues = '') + { + $data = json_decode($this->response->getBody(), true); + Assertions::assertNotFalse($data); + + $subKeys = array_map('trim', explode(',', $subKeys)); + $subKeyValues = array_map('trim', explode(',', $subKeyValues)); + + if ($compareValues) { + Assertions::assertEquals(count($subKeyValues), count($subKeys)); + } + + Assertions::assertArrayHasKey($key, $data); + Assertions::assertEquals($n, count($data[$key])); + + for ($i=0; $i<$n; $i++) { + for ($j=0, $c=count($subKeys); $j<$c; $j++) { + $subKey = $subKeys[$j]; + Assertions::assertArrayHasKey($i, $data[$key]); + if ($this->isExactType($type)) { + Assertions::assertEquals(count($subKeys), count($data[$key][$i])); + } + Assertions::assertArrayHasKey($subKey, $data[$key][$i]); + if ($compareValues) { + Assertions::assertEquals($subKeyValues[$j], $data[$key][$i][$subKey]); + } + } + } + } + + /** + * @param $type + * @returns boolean + */ + private function isExactType($type) + { + return $type === 'exactly'; + } + /** * Checks that response body contains JSON from PyString. * @@ -245,7 +397,7 @@ public function theResponseShouldContainJson(PyStringNode $jsonString) if (null === $etalon) { throw new \RuntimeException( - "Can not convert etalon to json:\n" . $this->replacePlaceHolder($jsonString->getRaw()) + "Can not convert etalon to json:\n" . $this->replacePlaceHolder($jsonString->getRaw()) ); }