From 55c25e908de8bbd9080f836697b05ed3f3c93391 Mon Sep 17 00:00:00 2001 From: chsergey Date: Sat, 23 Apr 2016 17:58:06 +0600 Subject: [PATCH 1/6] Add tests for Postman Parse markdown file with Postman's javascript tests definitions to API Blueprint named Actions and add to result collection file. --- test/api.test.md | 60 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 test/api.test.md diff --git a/test/api.test.md b/test/api.test.md new file mode 100644 index 0000000..d05f9f7 --- /dev/null +++ b/test/api.test.md @@ -0,0 +1,60 @@ +# Tests for Postman +- [Postman Sandbox Doc](https://www.getpostman.com/docs/sandbox) +- [Postman Sandbox Examples](https://www.getpostman.com/docs/testing_examples) + +This is an example of [Blueman](https://github.com/pixelfusion/blueman) file with test scripts for Postman. + +`### H3` heading **must** contains [API Blueprint](https://apiblueprint.org)'s [Action Section](https://github.com/apiaryio/api-blueprint/blob/master/API%20Blueprint%20Specification.md#def-action-section) : action defined by name, i.e. `## []`. + +Postman test script **must** be followed by this heading section. + +All scripts **must** be started with Markdown code block definition [example](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet#code). + +All test scripts for Actions are optional. + +## Initial code +Header `## H2` above wiil be ignored. + +This section **may** by empty or not defined. + +Code below will be prepend to all test scripts. **May** be empty. + +```javascript +var jsonData = JSON.parse(responseBody); +``` + +### Create a Player +This text will be ignored. Use it to comments. + +```javascript +tests["Content-Type is present"] = postman.getResponseHeader("Content-Type"); //Note: the getResponseHeader() method returns the header value, if it exists. +tests["Status code is 200"] = responseCode.code === 200; +``` + +This text also will be ignored. + + +### Another action +No tests. + + +### Example 0 +Empty test + +``` +``` + + +### Example 1 +Code without language definition + +``` +var a = 1; +``` + + +### Example 2 +Empty code block with language definition + +```javascript +``` \ No newline at end of file From c654df54b4aa28f1f90525e7487d03ab83c79df8 Mon Sep 17 00:00:00 2001 From: chsergey Date: Sat, 23 Apr 2016 18:25:20 +0600 Subject: [PATCH 2/6] postman tests and unit tests --- .../Console/Command/ConvertCommand.php | 119 +++++++++++++++++- 1 file changed, 113 insertions(+), 6 deletions(-) diff --git a/src/Blueman/Console/Command/ConvertCommand.php b/src/Blueman/Console/Command/ConvertCommand.php index a442acf..3e89958 100644 --- a/src/Blueman/Console/Command/ConvertCommand.php +++ b/src/Blueman/Console/Command/ConvertCommand.php @@ -39,6 +39,20 @@ protected function configure() InputOption::VALUE_REQUIRED, 'The base host of your API (e.g. https://api.example.com/v1).' ) + ->addOption( + 'tests-filename', + null, + InputOption::VALUE_OPTIONAL, + 'The JSON file name with Postman tests (located at --path)', + 'blueman.tests.md' + ) + ->addOption( + 'tests-include', + null, + InputOption::VALUE_OPTIONAL, + 'Add Postman tests to result JSON collection file (see --tests-filename)', + false + ) ->setHelp(<<convert command converts an API Blueprint JSON file into a Postman collection. EOT @@ -49,7 +63,9 @@ protected function execute(InputInterface $input, OutputInterface $output) { $filePath = $input->getOption('path'); - $file = $filePath.DIRECTORY_SEPARATOR.$input->getArgument('input-file'); + $output->writeln('Working Path: ' . $filePath . ''); + + $file = $filePath . DIRECTORY_SEPARATOR . $input->getArgument('input-file'); if (!file_exists($file)) { throw new \Exception( @@ -65,10 +81,24 @@ protected function execute(InputInterface $input, OutputInterface $output) ); } + /** @var object|false $tests */ + $tests = false; + if($input->getOption('tests-include')) { + + $testsFile = $filePath . $input->getOption('tests-filename'); + + if($testsFileExists = file_exists($testsFile)) { + $output->writeln('Using Blueman file with Postman tests: ' . $testsFile . ''); + $tests = $this->parseTestsFile($testsFile); + } else { + $output->writeln('Blueman file with Postman tests NOT found:' . $testsFile . ''); + } + } + $blueprint = $blueprint->ast; $collection = array(); - $collection['id'] = (string)Uuid::uuid4(); + $collection['id'] = (string) Uuid::uuid4(); $collection['name'] = $blueprint->name; $collection['description'] = $blueprint->description; @@ -101,6 +131,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $folders['order'] = array(); foreach ($resourceGroup->resources as $resource) { + /** @var object $action */ foreach ($resource->actions as $action) { $actionId = (string)Uuid::uuid4(); @@ -114,13 +145,17 @@ protected function execute(InputInterface $input, OutputInterface $output) $headers[] = sprintf('%s: %s', $header->name, $header->value); } $request['headers'] = implode("\n", $headers); - $request['data'] = (string)$exampleRequest->body; + $request['data'] = (string) $exampleRequest->body; $request['dataMode'] = 'raw'; $request['collectionId'] = $collection['id']; } - $request['url'] = $host.$this->parseUri($resource, $action); + $request['url'] = $host . $this->parseUri($resource, $action); $request['name'] = $resource->uriTemplate; $request['method'] = $action->method; + if($tests) { + $request['tests'] = $this->getTest($action->name, $tests); + } + $requests[] = $request; } } @@ -150,8 +185,8 @@ protected function execute(InputInterface $input, OutputInterface $output) /** * Parses the URI to make sure any parameters are replaced with actual values * - * @param stdObject $resource The current resource - * @param stdObject $action The current action + * @param object $resource The current resource + * @param object $action The current action * @return string The parsed URI */ private function parseUri($resource, $action) @@ -272,4 +307,76 @@ private function hasUriParams($uri) { return strpos($uri, '{') !== false; } + + /** + * Find test by resource Action name + * @param string$actionName + * @param object $tests [0 - prepend, 1 - tests by actions] + * @return string + */ + private function getTest($actionName, $tests) + { + + return isset($tests[1][$actionName]) + ? $tests[0] . $tests[1][$actionName] + : ''; + } + + /** + * Parse Markdown with tests + * @todo to parse class? + * @param string $testsFile + * @return array + */ + private function parseTestsFile($testsFile) + { + if(!$markdown = file($testsFile, FILE_SKIP_EMPTY_LINES)) { + return array(); + } + + $tests = array(); + $prepend = ''; + $mode = false; + $head = false; + $append = false; + + $heading = '/^(#+)\s+(.*)(\s*)$/'; + $code = '/^(```)(.*)/'; + + foreach ($markdown as $line) { + $matches = []; + $head = $head ? $head : false; + if(preg_match($heading, $line, $matches)) { + $mode = $matches[1]; + $head = trim($matches[2]); + $tests[$head] = ''; + continue; + } + if(preg_match($code, $line, $matches) && $head) { + $append = !$append; + continue; + } + if($head && $mode && $append) { + switch ($mode) { + case '##': + $prepend .= $line; + break; + case '###': + $tests[$head] .= $line; + break; + } + } + } + + foreach ($tests as $action => $test) { + if(!$test) { + unset($tests[$action]); + } + } + + return array( + $prepend, + $tests, + ); + } } From 2b10447cf586fba6167e52432f1a68e5659b3d26 Mon Sep 17 00:00:00 2001 From: chsergey Date: Sat, 23 Apr 2016 18:26:41 +0600 Subject: [PATCH 3/6] valid uuid channel --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 61fc80c..7b35ed9 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ ], "require": { "symfony/console": "~2.4", - "rhumsaa/uuid": "~2.7" + "ramsey/uuid": "~2.7" }, "require-dev": { "phpunit/phpunit": "~3.7", From a710c1c35452fc9d4cd215d70376f839391328d1 Mon Sep 17 00:00:00 2001 From: chsergey Date: Sat, 23 Apr 2016 18:27:05 +0600 Subject: [PATCH 4/6] add unit tests --- test/Blueman/ConvertCommandTest.php | 49 ++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/test/Blueman/ConvertCommandTest.php b/test/Blueman/ConvertCommandTest.php index 1d8f0d2..fc2b4b8 100644 --- a/test/Blueman/ConvertCommandTest.php +++ b/test/Blueman/ConvertCommandTest.php @@ -68,8 +68,8 @@ public function testNonAstException() public function testParsingUriWithoutParams() { $cmd = new ConvertCommand(); - $method = $this->getAccessibleParseUriMethod(); - + $method = $this->getAccessibleMethod('parseUri'); + $resource = new stdClass(); $resource->uriTemplate = '/players'; $action = new stdClass(); @@ -82,7 +82,7 @@ public function testParsingUriWithoutParams() public function testParsingUriWithSingleQueryParam() { $cmd = new ConvertCommand(); - $method = $this->getAccessibleParseUriMethod(); + $method = $this->getAccessibleMethod('parseUri'); $resource = new stdClass(); $resource->uriTemplate = '/players{?name}'; @@ -102,7 +102,7 @@ public function testParsingUriWithSingleQueryParam() public function testParsingUriWithMultipleQueryParams() { $cmd = new ConvertCommand(); - $method = $this->getAccessibleParseUriMethod(); + $method = $this->getAccessibleMethod('parseUri'); $resource = new stdClass(); $resource->uriTemplate = '/players{?name,age}'; @@ -126,7 +126,7 @@ public function testParsingUriWithMultipleQueryParams() public function testParsingUriWithSingleUriParam() { $cmd = new ConvertCommand(); - $method = $this->getAccessibleParseUriMethod(); + $method = $this->getAccessibleMethod('parseUri'); $resource = new stdClass(); $resource->uriTemplate = '/players/{name}'; @@ -146,7 +146,7 @@ public function testParsingUriWithSingleUriParam() public function testParsingUriWithMultipleUriParams() { $cmd = new ConvertCommand(); - $method = $this->getAccessibleParseUriMethod(); + $method = $this->getAccessibleMethod('parseUri'); $resource = new stdClass(); $resource->uriTemplate = '/players/{name}/games/{game_id}'; @@ -170,7 +170,7 @@ public function testParsingUriWithMultipleUriParams() public function testParsingUriWithMultipleUriAndQueryParams() { $cmd = new ConvertCommand(); - $method = $this->getAccessibleParseUriMethod(); + $method = $this->getAccessibleMethod('parseUri'); $resource = new stdClass(); $resource->uriTemplate = '/players/{name}/games/{game_id}{?filter,locale}'; @@ -198,16 +198,43 @@ public function testParsingUriWithMultipleUriAndQueryParams() $this->assertEquals('/players/John/games/52387?filter=flunkyball&locale=US', $result); } + + public function testPostmanTests() { + $cmd = new ConvertCommand(); + $method = $this->getAccessibleMethod('parseTestsFile'); + + $result = $method->invokeArgs($cmd, array('test/api.test.md')); + + /** + * Isset code to prepend + */ + $this->assertArrayHasKey(0, $result); + $this->assertNotEmpty($result[0]); + /** + * Isset tests for action + */ + $this->assertArrayHasKey(1, $result); + $this->assertArrayHasKey('Create a Player', $result[1]); + $this->assertNotEmpty($result[1]['Create a Player'], 'Empty tests for action: Create a Player'); + $this->assertNotEmpty($result[1]['Example 1'], 'Empty tests for action: Example 1'); + /** + * No test for actions + */ + $this->assertArrayNotHasKey('Another action', $result[1]); + $this->assertArrayNotHasKey('Example 0', $result[1]); + $this->assertArrayNotHasKey('Example 2', $result[1]); + } /** - * Helper to call `parseUri` on `ConvertCommand` + * Helper to get accessible method from `ConvertCommand` * - * @return ReflectionMethod Accessible `ConvertCommand::parseUri` + * @param string $method + * @return ReflectionMethod */ - private function getAccessibleParseUriMethod() + private function getAccessibleMethod($method) { $reflection = new ReflectionClass('\Blueman\Console\Command\ConvertCommand'); - $method = $reflection->getMethod('parseUri'); + $method = $reflection->getMethod($method); $method->setAccessible(true); return $method; From 1cef50a8af9ad69bd025b55dac287971cf81dc20 Mon Sep 17 00:00:00 2001 From: chsergey Date: Sat, 23 Apr 2016 18:27:26 +0600 Subject: [PATCH 5/6] new feature --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index 8234a56..bb2a760 100644 --- a/README.md +++ b/README.md @@ -99,3 +99,27 @@ Lastly, if you don't do either of the above you'll be prompted to set the host w ```sh $ blueman convert api.json --host=https://api.{{host}}/v1 ``` + +### Setting the Postman tests + +You also may to define Postman's tests for your named Actions of Resources. + +To use this feature do: + +- create a Markdown formatted file in your path where `api.json` located (see --path option). By default, Blueman will be try get `blueman.tests.md` filename. +- read example of markdown file [test/api.test.md] +- write Postman tests for any Resource Action you want +- use `--tests-include` (default: false) and `--tests-filename` (default: blueman.tests.md) options +- enjoy :) + +#### Usage example + +```sh +$ blueman convert api.json --tests-include=true +``` + +or + +```sh +$ blueman convert api.json --tests-include=true --tests-filename=.md +``` \ No newline at end of file From 1b9a6860facb7e21caace0d04dd8531f6fe28099 Mon Sep 17 00:00:00 2001 From: chsergey Date: Sat, 23 Apr 2016 18:47:06 +0600 Subject: [PATCH 6/6] phpDoc fix --- src/Blueman/Console/Command/ConvertCommand.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Blueman/Console/Command/ConvertCommand.php b/src/Blueman/Console/Command/ConvertCommand.php index 3e89958..806c271 100644 --- a/src/Blueman/Console/Command/ConvertCommand.php +++ b/src/Blueman/Console/Command/ConvertCommand.php @@ -81,9 +81,8 @@ protected function execute(InputInterface $input, OutputInterface $output) ); } - /** @var object|false $tests */ - $tests = false; - if($input->getOption('tests-include')) { + $tests = array(); + if('true' === $input->getOption('tests-include')) { $testsFile = $filePath . $input->getOption('tests-filename');