├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── bin ├── cli.js └── util.js ├── docs ├── compilation.html ├── global.html ├── index.html ├── index.js.html ├── lib_compile.js.html ├── lib_handlebar-helpers.js.html ├── lib_process.js.html ├── lib_util.js.html ├── processing.html ├── scripts │ ├── linenumber.js │ └── prettify │ │ ├── Apache-License-2.0.txt │ │ ├── lang-css.js │ │ └── prettify.js ├── styles │ ├── jsdoc.css │ └── prettify.css └── templateHelpers.html ├── index.js ├── jsdoc.config.json ├── lib ├── compile.js ├── handlebar-helpers.js ├── process.js └── util.js ├── package-lock.json ├── package.json ├── templates ├── operationLevel.handlebars ├── pathLevel.handlebars ├── topLevel.handlebars └── transactionLevel.handlebars └── test ├── compile ├── documents │ ├── customTemplates │ │ ├── operationLevel.handlebars │ │ ├── pathLevel.handlebars │ │ ├── topLevel.handlebars │ │ └── transactionLevel.handlebars │ └── fullProcessed.json └── test.js └── process ├── documents ├── customValuesTest.json └── swagger.yaml └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | node_modules 3 | .DS_Store 4 | .idea 5 | oatts.iml 6 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of 9 | experience, education, socio-economic status, nationality, personal appearance, 10 | race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or reject 41 | comments, commits, code, wiki edits, issues, and other contributions that are 42 | not aligned to this Code of Conduct, or to ban temporarily or permanently any 43 | contributor for other behaviors that they deem inappropriate, threatening, 44 | offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | This Code of Conduct also applies outside the project spaces when the Project 56 | Steward has a reasonable belief that an individual's behavior may have a 57 | negative impact on the project or its community. 58 | 59 | ## Conflict Resolution 60 | 61 | We do not believe that all conflict is bad; healthy debate and disagreement 62 | often yield positive results. However, it is never okay to be disrespectful or 63 | to engage in behavior that violates the project’s code of conduct. 64 | 65 | If you see someone violating the code of conduct, you are encouraged to address 66 | the behavior directly with those involved. Many issues can be resolved quickly 67 | and easily, and this gives people more control over the outcome of their 68 | dispute. If you are unable to resolve the matter for any reason, or if the 69 | behavior is threatening or harassing, report it. We are dedicated to providing 70 | an environment where participants feel welcome and safe. 71 | 72 | Reports should be directed to Noah Dietz (ndietz@google.com), the 73 | Project Steward(s) for `oatts`. It is the Project Steward’s duty to 74 | receive and address reported violations of the code of conduct. They will then 75 | work with a committee consisting of representatives from the Open Source 76 | Programs Office and the Google Open Source Strategy team. If for any reason you 77 | are uncomfortable reaching out the Project Steward, please email 78 | opensource@google.com. 79 | 80 | We will investigate every complaint, but you may not receive a direct response. 81 | We will use our discretion in determining when and how to follow up on reported 82 | incidents, which may range from not taking action to permanent expulsion from 83 | the project and project-sponsored spaces. We will notify the accused of the 84 | report and provide them an opportunity to discuss it before any action is taken. 85 | The identity of the reporter will be omitted from the details of the report 86 | supplied to the accused. In potentially harmful situations, such as ongoing 87 | harassment or threats to anyone's safety, we may take action without notice. 88 | 89 | ## Attribution 90 | 91 | This Code of Conduct is adapted from the Contributor Covenant, version 1.4, 92 | available at 93 | https://www.contributor-covenant.org/version/1/4/code-of-conduct.html -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Pull requests are welcome! By participating in this project, you 4 | agree to abide by the Code Of Conduct included in this repository. 5 | 6 | If you have a request or question, start by opening an issue for discussion! 7 | 8 | To start coding, fork, then clone the repo: 9 | 10 | git clone git@github.com:your-username/oatts.git 11 | 12 | Install dependencies: 13 | 14 | npm install 15 | 16 | Make sure the tests pass: 17 | 18 | npm test 19 | 20 | Make your change. **Add tests for your change**. Make the tests pass: 21 | 22 | npm test 23 | 24 | Push to your fork and [submit a pull request][pr]. 25 | 26 | [pr]: https://github.com/noahdietz/oatts/compare/ 27 | 28 | If the change... 29 | 30 | * introduces a new type 31 | * changes the definition of an existing type 32 | * adds a new function 33 | * changes the signature of an existing function 34 | * adds a new local module 35 | 36 | ...please add/update the appropriate [jsdoc] comments. 37 | 38 | [jsdoc]: http://usejsdoc.org/ 39 | 40 | Following passing the tests and committing the change, regrenerate the docs: 41 | 42 | npm run gen-docs 43 | 44 | Commit the changes to `docs/` as a **separate commit.** 45 | 46 | At this point you're waiting on us. I will try to at least comment on the pull request as quickly as possible. 47 | We may suggest some changes or improvements or alternatives. 48 | 49 | Some things that will increase the chance that your pull request is accepted: 50 | 51 | * Write tests. 52 | * Write a [good commit message][commit]. 53 | 54 | [commit]: https://github.com/erlang/otp/wiki/Writing-good-commit-messages -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2018 Google Inc. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenAPI Test Templates (oatts) 2 | 3 | > Generate basic unit test scaffolding for your [OpenAPI specification](https://www.openapis.org/). 4 | 5 | ## Disclaimer 6 | This is not an officially supported Google product. 7 | 8 | `oatts` is based off of the [swagger-test-templates](https://github.com/apigee-127/swagger-test-templates) module and the lessons learned during its development. 9 | 10 | _This is a work in progress._ 11 | 12 | ## Goal 13 | 14 | The goal of `oatts` is to provide a standalone module for generating Node.js unit test code scaffolding based on a given OpenAPI specification. 15 | 16 | The hope is that by providing such a tool, API developers will be encouraged to test the contract between their spec and backend early, often and continuously as the project grows. 17 | 18 | ## Usage 19 | 20 | There are a couple ways to use `oatts`. 21 | 22 | ### Module 23 | 24 | Install via `npm` 25 | 26 | npm install --save oatts 27 | 28 | 29 | Then use it in code 30 | ```js 31 | var oatts = require('oatts'); 32 | 33 | var options = { 34 | // see "Options" section below for available options 35 | }; 36 | 37 | var tests = oatts.generate('/path/to/openapi.yaml', options); 38 | 39 | console.log(tests) 40 | ``` 41 | 42 | ### Command line interface 43 | 44 | Install globally via `npm` 45 | 46 | npm install -g oatts 47 | 48 | 49 | Then use in your command line 50 | ```sh 51 | > oatts generate --help 52 | 53 | Usage: generate [options] 54 | 55 | generate unit test scaffolding for a given OpenAPI/Swagger Spec 56 | 57 | Options: 58 | 59 | -h, --help output usage information 60 | --host target hostname to use in test generation 61 | -p, --paths comma separated list of paths to generate tests for 62 | -e, --samples generate sample response bodies rather than schema, if applicable 63 | -s, --spec path to the target OpenAPI/Swagger spec document to consume 64 | -w, --writeTo directory to write the generated tests to file 65 | -c, --consumes consumes/content-type to use in request when applicable to the API resource 66 | -o, --produces produces/accept to use in request when applicable to the API resource 67 | -u, --customValues custom request values to be used in generation; takes precedent over a customValuesFile 68 | --customValuesFile path to JSON file with custom request values to be used in generation 69 | -m, --scheme which scheme to use if multiple are present in spec 70 | -t --templates path to direcotry of custom templates 71 | -S, --status-codes comma separated list of status codes to explicity generate tests for 72 | 73 | > oatts generate -s ./path/to/openapi.yaml -w ./output/dir 74 | > ls ./output/dir 75 | pet-test.js pet-{petId}-uploadImage-test.js user-test.js 76 | . . . 77 | ``` 78 | 79 | ### Using the result 80 | 81 | The resulting test files are built using the [mocha](https://mochajs.org/) testing framework and [chakram](http://dareid.github.io/chakram/) API testing framework. Thus, you will need both of these dependencies installed in order to run your newly generated tests. 82 | 83 | After installing these, you can run the tests with mocha: 84 | ``` 85 | # start your API server to test against!! 86 | > mocha --recursive 87 | 88 | 89 | tests for /goodbye 90 | tests for get 91 | ✓ should respond 200 for "Success" (57ms) 92 | 93 | tests for /hello 94 | tests for get 95 | ✓ should respond 200 for "Success" 96 | 97 | 98 | 2 passing (82ms) 99 | ``` 100 | 101 | ### Custom Values 102 | Custom values can be supplied through both the command line and a JSON file. The in-line, command line supplied JSON will take precedent. 103 | 104 | An example custom values JSON file can be found [here](./test/process/documents/customValuesTest.json). 105 | 106 | ### Custom Templates 107 | Custom templates can be supplied via the `templates` option. The directory pointed to by the option must contain 4 [Handlebars](http://handlebarsjs.com/) templates named the same way as those found in `./templates`. 108 | 109 | * `topLevel.handlebars`: the top level template for a single test file 110 | * `pathLevel.handlebars`: the path level template, usually the beginning of a test suite for a specific path 111 | * `operationLevel.handlebars`: the operation level template, for a single operation test suite 112 | * `transactionLevel.handlebars`: the template for a single transaction, or a single response code's unit test 113 | 114 | The data available to be used in the templates is specified in the `ProcessedSpec` type. 115 | 116 | There are also a few helpers available to be used in the Handlebars templates, which can be found in the `templateHelpers` documentation namespace. Use the default templates as examples of how to use them. 117 | 118 | ## Options 119 | 120 | The following options can be passed to the generation function, some/all are exposed in the accompanying CLI: 121 | 122 | | Name | CLI Flag | Default | Required | Description | 123 | | ---- |:--------:| -------:| --------:| -----------:| 124 | | `spec` | `--spec -s` | n/a | `true` | Path to a `swagger.yaml` or `openapi.yaml` | 125 | | `host` | `--host` | `spec.host` | `false` | Hostname to put in test requests; defaults to `host` in given spec | 126 | | `paths` | `--paths -p` | `spec.paths` | `false` | API paths to generate tests for; defaults to all paths in given spec | 127 | | `samples` | `--samples -e` | `false` | `false` | Toggle generating sample responses for assertion | 128 | | `writeTo` | `--writeTo -w` | n/a | `false` | Directory to write generated tests to; will create the directory if it doesn't exist | 129 | | `consumes` | `--consumes -c` | `operation.consumes[0]` | | `spec.conumes[0]` | `false` | Consumes header to use in a request when applicable | 130 | | `produces` | `--produces -o` | `operation.produces[0]` | | `spec.produces[0]` | `false` | Produces header to use in a request when applicable | 131 | | `customValues` | `--customValues -u` | n/a | `false` | Values to be populated in requests where specified; overrides `customValuesFile` | 132 | | `customValuesFile` | `--customValuesFile` | n/a | `false` | Path to a JSON file with values to populate in requests | 133 | | `scheme` | `--scheme -m` | `spec.schemes[0]` | `false` | Override for multiple scheme present in a spec | 134 | | `templates` | `--templates -t` | `'./templates'` | `false` | Path to directory containing custom templates | 135 | | `statusCodes` |`--status-codes -S` | `operation.responses` | `false` | comma separated list of status codes to explicity generate tests for | 136 | | `jsonRefs` | | n/a | `false` | *(See [JsonRefs~JsonRefsOptions](https://github.com/whitlockjc/json-refs/blob/master/docs/API.md#module_JsonRefs..JsonRefsOptions))* | 137 | | `customFormats` | | n/a | `false` | The key/value pair of custom formats *(The keys are the format name and the values are async functions. See [ZSchema Custom Formats](https://github.com/zaggino/z-schema#register-a-custom-format))* | 138 | | `customFormatGenerators` | | n/a | `false` | The key/value pair of custom format generators *(The keys are the format name and the values are functions. See [json-schema-mocker Custom Format](https://github.com/json-schema-faker/json-schema-faker#custom-formats))* | 139 | | `customValidators` | | n/a | `false` | The custom validators. See [DocumentValidationFunction](https://github.com/apigee-127/sway/blob/master/docs/API.md#module_sway.DocumentValidationFunction) | 140 | 141 | ## Testing 142 | 143 | To test this module simply use the `npm` script 144 | 145 | npm test 146 | 147 | ## Discussion 148 | 149 | If you have a question or a topic you'd like to discuss, please feel free to open 150 | a discussion on our Google Group [oatts-users](https://groups.google.com/forum/#!forum/oatts-users/). 151 | 152 | ## Contributing 153 | 154 | Contributors are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md). 155 | 156 | ## Copyright 157 | 158 | Copyright 2018, Google Inc. 159 | 160 | ## License 161 | 162 | See [LICENSE](LICENSE) file. 163 | -------------------------------------------------------------------------------- /bin/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // Copyright 2018 Google Inc. All Rights Reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | 'use strict'; 18 | var cli = require('commander') 19 | var oatts = require('../index') 20 | var util = require('./util') 21 | var join = require('path').join; 22 | 23 | cli.version(require('../package.json').version) 24 | .usage('') 25 | 26 | cli.command('generate') 27 | .description( 28 | 'generate unit test scaffolding for a given OpenAPI/Swagger Spec') 29 | .option('--host ', 'target hostname to use in test generation') 30 | .option('-p, --paths ', 31 | 'comma separated list of paths to generate tests for', util.sep) 32 | .option('-e, --samples', 33 | 'generate sample response bodies rather than schema, if applicable') 34 | .option('-s, --spec ', 35 | 'path to the target OpenAPI/Swagger spec document to consume') 36 | .option('-w, --writeTo ', 37 | 'directory to write the generated tests to file') 38 | .option('-c, --consumes ', 39 | 'consumes/content-type to use in request when applicable to the API resource') 40 | .option('-o, --produces ', 41 | 'produces/accept to use in request when applicable to the API resource') 42 | .option('-u, --customValues ', 43 | 'custom request values to be used in generation; takes precedent over a customValuesFile') 44 | .option('--customValuesFile ', 45 | 'path to JSON file with custom request values to be used in generation') 46 | .option('-m, --scheme ', 47 | 'which scheme to use if multiple are present in spec') 48 | .option('-t --templates ', 49 | 'path to direcotry of custom templates') 50 | .option('-S, --status-codes ', 51 | 'comma separated list of status codes to explicity generate tests for', 52 | util.sep) 53 | .action(function (options) { 54 | options.error = util.optionError; 55 | if (!options.spec) { 56 | return options.error('spec path is required'); 57 | } 58 | 59 | var generated = oatts.generate(options.spec, options); 60 | generated.then(function (gen) { 61 | if (options.writeTo === undefined) { 62 | console.log(gen) 63 | } 64 | }, 65 | function (err) { 66 | console.error(err) 67 | }); 68 | }); 69 | 70 | cli.parse(process.argv); -------------------------------------------------------------------------------- /bin/util.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | module.exports = { 18 | sep: sep, 19 | optionError: optionError 20 | } 21 | 22 | function sep(list) { 23 | var sep = list.split(',') 24 | sep.forEach(function (s, ndx, arr) { 25 | arr[ndx] = s.trim() 26 | }); 27 | 28 | return sep 29 | } 30 | 31 | function optionError(message) { 32 | console.error(message); 33 | this.help(); 34 | } -------------------------------------------------------------------------------- /docs/global.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Global - Documentation 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 23 | 24 | 27 | 28 |
29 | 30 |

Global

31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
39 | 40 |
41 | 42 |

43 | 44 |

45 | 46 | 47 |
48 | 49 |
50 |
51 | 52 | 53 | 54 |
55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 |
88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 |
97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 |

Methods

112 | 113 | 114 | 115 | 116 | 117 | 118 |

generate(specPath, options) → {Promise.<GenerationResults>}

119 | 120 | 121 | 122 | 123 | 124 | 125 |
126 | 127 | 128 |
Source:
129 |
132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 |
164 | 165 | 166 | 167 | 168 | 169 |
170 | Generates test artifacts based on the given API Spec and options 171 |
172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 |
Parameters:
184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 |
NameTypeDescription
specPath 212 | 213 | 214 | string 215 | 216 | 217 | 218 | path to the API spec document
options 235 | 236 | 237 | object 238 | 239 | 240 | 241 | options to apply during processing of API spec
253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 |
Returns:
268 | 269 | 270 | 271 | 272 |
273 |
274 | Type 275 |
276 |
277 | 278 | Promise.<GenerationResults> 279 | 280 | 281 |
282 |
283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 |

merge2(obj1, obj2) → {object}

293 | 294 | 295 | 296 | 297 | 298 | 299 |
300 | 301 | 302 |
Source:
303 |
306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 |
338 | 339 | 340 | 341 | 342 | 343 |
344 | merges two objects, with obj1 taking override precedent 345 |
346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 |
Parameters:
358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 |
NameTypeDescription
obj1 386 | 387 | 388 | object 389 | 390 | 391 | 392 | dominant object to merge with
obj2 409 | 410 | 411 | object 412 | 413 | 414 | 415 | subordinate object to merge with
427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 |
Returns:
442 | 443 | 444 | 445 | 446 |
447 |
448 | Type 449 |
450 |
451 | 452 | object 453 | 454 | 455 |
456 |
457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 |

Type Definitions

466 | 467 | 468 | 469 |

GenerationResults

470 | 471 | 472 | 473 | 474 | 475 |
476 | 477 | 478 |
Source:
479 |
482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 |
514 | 515 | 516 | 517 |
Properties:
518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 |
NameTypeDescription
generated 547 | 548 | 549 | Array.<compilation.GeneratedTest> 550 | 551 | 552 | 553 | set of generated test objects
565 | 566 | 567 | 568 | 569 | 570 | 571 |
572 | GenerationResults is the final result of the generation function 573 |
574 | 575 | 576 | 577 |
Type:
578 |
    579 |
  • 580 | 581 | object 582 | 583 | 584 |
  • 585 |
586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 |
597 | 598 |
599 | 600 | 601 | 602 | 603 |
604 | 605 |
606 | 607 |
608 | Documentation generated by JSDoc 3.6.3 on Sat Aug 24 2019 12:31:35 GMT+0100 (Western European Summer Time) using the docdash theme. 609 |
610 | 611 | 612 | 613 | 614 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Home - Documentation 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 23 | 24 | 27 | 28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
49 |

Build Status

50 |

OpenAPI Test Templates (oatts)

51 |
52 |

Generate basic unit test scaffolding for your OpenAPI specification.

53 |
54 |

Disclaimer

55 |

This is not an officially supported Google product.

56 |

oatts is based off of the swagger-test-templates module and the lessons learned during its development.

57 |

This is a work in progress.

58 |

Goal

59 |

The goal of oatts is to provide a standalone module for generating Node.js unit test code scaffolding based on a given OpenAPI specification.

60 |

The hope is that by providing such a tool, API developers will be encouraged to test the contract between their spec and backend early, often and continuously as the project grows.

61 |

Usage

62 |

There are a couple ways to use oatts.

63 |

Module

64 |

Install via npm

65 |
npm install --save oatts
 66 | 
67 |

Then use it in code

68 |
var oatts = require('oatts');
 69 | 
 70 | var options = {
 71 |     // see "Options" section below for available options
 72 | };
 73 | 
 74 | var tests = oatts.generate('/path/to/openapi.yaml', options);
 75 | 
 76 | console.log(tests)
 77 | 
78 |

Command line interface

79 |

Install globally via npm

80 |
npm install -g oatts
 81 | 
82 |

Then use in your command line

83 |
> oatts generate --help
 84 | 
 85 |   Usage: generate [options]
 86 | 
 87 |   generate unit test scaffolding for a given OpenAPI/Swagger Spec
 88 | 
 89 |   Options:
 90 | 
 91 |     -h, --help                             output usage information
 92 |     --host <host>                          target hostname to use in test generation
 93 |     -p, --paths <paths>                    comma separated list of paths to generate tests for
 94 |     -e, --samples                          generate sample response bodies rather than schema, if applicable
 95 |     -s, --spec <spec>                      path to the target OpenAPI/Swagger spec document to consume
 96 |     -w, --writeTo <writeTo>                directory to write the generated tests to file
 97 |     -c, --consumes <consumes>              consumes/content-type to use in request when applicable to the API resource
 98 |     -o, --produces <produces>              produces/accept to use in request when applicable to the API resource
 99 |     -u, --customValues <customValues>      custom request values to be used in generation; takes precedent over a customValuesFile
100 |     --customValuesFile <customValuesFile>  path to JSON file with custom request values to be used in generation
101 |     -m, --scheme <scheme>                  which scheme to use if multiple are present in spec
102 |     -t --templates <templateDir>           path to direcotry of custom templates
103 |     -S, --status-codes <statusCodes>       comma separated list of status codes to explicity generate tests for
104 | 
105 | > oatts generate -s ./path/to/openapi.yaml -w ./output/dir
106 | > ls ./output/dir
107 | pet-test.js  pet-{petId}-uploadImage-test.js  user-test.js 
108 | . . .
109 | 
110 |

Using the result

111 |

The resulting test files are built using the mocha testing framework and chakram API testing framework. Thus, you will need both of these dependencies installed in order to run your newly generated tests.

112 |

After installing these, you can run the tests with mocha:

113 |
# start your API server to test against!!
114 | > mocha --recursive <test directory>
115 | 
116 | 
117 |     tests for /goodbye
118 |         tests for get
119 |             ✓ should respond 200 for "Success" (57ms)
120 | 
121 |     tests for /hello
122 |         tests for get
123 |             ✓ should respond 200 for "Success"
124 | 
125 | 
126 |     2 passing (82ms)
127 | 
128 |

Custom Values

129 |

Custom values can be supplied through both the command line and a JSON file. The in-line, command line supplied JSON will take precedent.

130 |

An example custom values JSON file can be found here.

131 |

Custom Templates

132 |

Custom templates can be supplied via the templates option. The directory pointed to by the option must contain 4 Handlebars templates named the same way as those found in ./templates.

133 |
    134 |
  • topLevel.handlebars: the top level template for a single test file
  • 135 |
  • pathLevel.handlebars: the path level template, usually the beginning of a test suite for a specific path
  • 136 |
  • operationLevel.handlebars: the operation level template, for a single operation test suite
  • 137 |
  • transactionLevel.handlebars: the template for a single transaction, or a single response code's unit test
  • 138 |
139 |

The data available to be used in the templates is specified in the ProcessedSpec type.

140 |

There are also a few helpers available to be used in the Handlebars templates, which can be found in the templateHelpers documentation namespace. Use the default templates as examples of how to use them.

141 |

Options

142 |

The following options can be passed to the generation function, some/all are exposed in the accompanying CLI:

143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 |
NameCLI FlagDefaultRequiredDescription
spec--spec -sn/atruePath to a swagger.yaml or openapi.yaml
host--hostspec.hostfalseHostname to put in test requests; defaults to host in given spec
paths--paths -pspec.pathsfalseAPI paths to generate tests for; defaults to all paths in given spec
samples--samples -efalsefalseToggle generating sample responses for assertion
writeTo--writeTo -wn/afalseDirectory to write generated tests to; will create the directory if it doesn't exist
consumes--consumes -coperation.consumes[0] | | spec.conumes[0]falseConsumes header to use in a request when applicable
produces--produces -ooperation.produces[0] | | spec.produces[0]falseProduces header to use in a request when applicable
customValues--customValues -un/afalseValues to be populated in requests where specified; overrides customValuesFile
customValuesFile--customValuesFilen/afalsePath to a JSON file with values to populate in requests
scheme--scheme -mspec.schemes[0]falseOverride for multiple scheme present in a spec
templates--templates -t'./templates'falsePath to directory containing custom templates
statusCodes--status-codes -Soperation.responsesfalsecomma separated list of status codes to explicity generate tests for
jsonRefsn/afalse(See JsonRefs~JsonRefsOptions)
customFormatsn/afalseThe key/value pair of custom formats (The keys are the format name and the values are async functions. See ZSchema Custom Formats)
customFormatGeneratorsn/afalseThe key/value pair of custom format generators (The keys are the format name and the values are functions. See json-schema-mocker Custom Format)
customValidatorsn/afalseThe custom validators. See DocumentValidationFunction
268 |

Testing

269 |

To test this module simply use the npm script

270 |
npm test
271 | 
272 |

Discussion

273 |

If you have a question or a topic you'd like to discuss, please feel free to open 274 | a discussion on our Google Group oatts-users.

275 |

Contributing

276 |

Contributors are welcome! Please see CONTRIBUTING.md.

277 |

Copyright

278 |

Copyright 2018, Google Inc.

279 |

License

280 |

See LICENSE file.

281 |
282 | 283 | 284 | 285 | 286 | 287 | 288 |
289 | 290 |
291 | 292 |
293 | Documentation generated by JSDoc 3.6.3 on Sat Aug 24 2019 12:31:35 GMT+0100 (Western European Summer Time) using the docdash theme. 294 |
295 | 296 | 297 | 298 | 299 | -------------------------------------------------------------------------------- /docs/index.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | index.js - Documentation 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 23 | 24 | 27 | 28 |
29 | 30 |

index.js

31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
39 |
40 |
// Copyright 2018 Google Inc. All Rights Reserved.
 41 | //
 42 | // Licensed under the Apache License, Version 2.0 (the "License");
 43 | // you may not use this file except in compliance with the License.
 44 | // You may obtain a copy of the License at
 45 | //
 46 | //      http://www.apache.org/licenses/LICENSE-2.0
 47 | //
 48 | // Unless required by applicable law or agreed to in writing, software
 49 | // distributed under the License is distributed on an "AS IS" BASIS,
 50 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 51 | // See the License for the specific language governing permissions and
 52 | // limitations under the License.
 53 | 
 54 | 'use strict';
 55 | 
 56 | var proc = require('./lib/process.js')
 57 | var compile = require('./lib/compile.js')
 58 | var sway = require('sway')
 59 | var fs = require('fs')
 60 | var join = require('path').join
 61 | var merge2 = require('./lib/util').merge2
 62 | 
 63 | module.exports = {
 64 |   generate: generate
 65 | }
 66 | 
 67 | /**
 68 |  * GenerationResults is the final result of the generation function
 69 |  * @typedef {object} GenerationResults
 70 |  * @property {compilation.GeneratedTest[]} generated set of generated test objects
 71 |  */
 72 | 
 73 | /**
 74 |  * Generates test artifacts based on the given API Spec and options
 75 |  * @function generate
 76 |  * @instance
 77 |  * @param {string} specPath path to the API spec document
 78 |  * @param {object} options options to apply during processing of API spec
 79 |  * @return {Promise<GenerationResults>}
 80 |  */
 81 | function generate(specPath, options) {
 82 |   return sway.create({
 83 |     'definition': specPath,
 84 |     'jsonRefs': options && options.jsonRefs,
 85 |     'customFormats': options && options.customFormats,
 86 |     'customFormatGenerators': options && options.customFormatGenerators,
 87 |     'customValidators': options && options.customValidators
 88 |   })
 89 |       .then(function (api) {
 90 |         if (options.customValues) {
 91 |           options.customValues = JSON.parse(options.customValues);
 92 |         }
 93 | 
 94 |         if (options.customValuesFile) {
 95 |           var customFromFile = require(
 96 |               join(process.cwd(), options.customValuesFile))
 97 |           options.customValues = merge2(options.customValues, customFromFile)
 98 |         }
 99 | 
100 |         var processed = proc(api, options)
101 |         var compiled = compile(processed, options)
102 | 
103 |         if (options.writeTo !== undefined) {
104 |           if (!fs.existsSync(options.writeTo)) {
105 |             fs.mkdirSync(options.writeTo)
106 |           }
107 | 
108 |           try {
109 |             for (var i = 0; i < compiled.length; i++) {
110 |               const testObj = compiled[i];
111 |               fs.writeFileSync(join(options.writeTo, testObj.filename), testObj.contents)
112 |             }
113 |           } catch (err) {
114 |             console.log(err);
115 |           }
116 |         }
117 | 
118 |         return {'generated': compiled}
119 |       }, function (err) {
120 |         console.error(err.stack);
121 |         return err
122 |       });
123 | }
124 | 
125 |
126 |
127 | 128 | 129 | 130 | 131 |
132 | 133 |
134 | 135 |
136 | Documentation generated by JSDoc 3.6.3 on Sat Aug 24 2019 12:31:35 GMT+0100 (Western European Summer Time) using the docdash theme. 137 |
138 | 139 | 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /docs/lib_compile.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | lib/compile.js - Documentation 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 23 | 24 | 27 | 28 |
29 | 30 |

lib/compile.js

31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
39 |
40 |
// Copyright 2018 Google Inc. All Rights Reserved.
 41 | //
 42 | // Licensed under the Apache License, Version 2.0 (the "License");
 43 | // you may not use this file except in compliance with the License.
 44 | // You may obtain a copy of the License at
 45 | //
 46 | //      http://www.apache.org/licenses/LICENSE-2.0
 47 | //
 48 | // Unless required by applicable law or agreed to in writing, software
 49 | // distributed under the License is distributed on an "AS IS" BASIS,
 50 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 51 | // See the License for the specific language governing permissions and
 52 | // limitations under the License.
 53 | 
 54 | 'use strict';
 55 | 
 56 | var handlebars = require('handlebars');
 57 | var helpers = require('./handlebar-helpers')
 58 | var fs = require('fs')
 59 | var path = require('path')
 60 | 
 61 | var DefaultTemplateDirectory = path.join(__dirname, '..', 'templates');
 62 | var TopLevelTemplateName = 'topLevel.handlebars';
 63 | var PathTemplateName = 'pathLevel.handlebars';
 64 | var OperationTemplateName = 'operationLevel.handlebars';
 65 | var TransactionTemplateName = 'transactionLevel.handlebars';
 66 | 
 67 | /**
 68 |  * @namespace compilation
 69 |  */
 70 | 
 71 | module.exports = compile
 72 | 
 73 | handlebars.registerHelper('notEmptyObject', helpers.notEmptyObject)
 74 | handlebars.registerHelper('json', helpers.json)
 75 | handlebars.registerHelper('isNotDefaultStatusCode',
 76 |     helpers.isNotDefaultStatusCode)
 77 | 
 78 | /**
 79 |  * GeneratedTest is the set of results from procressing a spec & compiling the test code
 80 |  * @typedef {object} GeneratedTest
 81 |  * @memberof compilation
 82 |  * @property {string} filename A file name based off of the path being tested
 83 |  * @property {string} contents Generated test file contents
 84 |  */
 85 | 
 86 | /**
 87 |  * Compiles the templated test files with the given processed API spec data
 88 |  * @function compile
 89 |  * @memberof compilation
 90 |  * @instance
 91 |  * @param {processing.ProcessedSpec} processed the API spec data processed for test generation
 92 |  * @param {object} options for use in compilation of the tests
 93 |  * @return {compilation.GeneratedTest[]}
 94 |  */
 95 | function compile(processed, options) {
 96 |   var tests = []
 97 | 
 98 |   processed.tests.forEach(function (processedPath, ndx, arr) {
 99 |     var pathTest = compilePathLevel(processedPath, processed, options)
100 | 
101 |     if (pathTest !== null) {
102 |       tests.push({
103 |         'filename': processedPath.name + '-test.js',
104 |         'contents': pathTest
105 |       })
106 |     }
107 |   });
108 | 
109 |   if (tests.length === 0) {
110 |     return null
111 |   }
112 | 
113 |   return tests
114 | }
115 | 
116 | /**
117 |  * GeneratedPath is the result of procressing a path & compiling the path-level test code
118 |  * @typedef {object} GeneratedPath
119 |  * @memberof compilation
120 |  * @property {string} test the generated test code portion
121 |  */
122 | 
123 | /**
124 |  * Compiles the path level 'describe' content
125 |  * @function compilePathLevel
126 |  * @memberof compilation
127 |  * @instance
128 |  * @param {processing.ProcessedPath} processedPath the processed path object to be compiled
129 |  * @param {processing.ProcessedSpec} processed the entire processed API spec
130 |  * @param {object} options for use in test compilation
131 |  * @return {compilation.GeneratedPath}
132 |  */
133 | function compilePathLevel(processedPath, processed, options) {
134 |   var tests = []
135 |   var requireLevelCompiler = prepareTemplate(
136 |       path.join(
137 |           options.templates ? options.templates : DefaultTemplateDirectory,
138 |           TopLevelTemplateName
139 |       )
140 |   )
141 | 
142 |   var pathLevelCompiler = prepareTemplate(
143 |       path.join(
144 |           options.templates ? options.templates : DefaultTemplateDirectory,
145 |           PathTemplateName
146 |       )
147 |   )
148 | 
149 |   processedPath.operations.forEach(function (processedOp, ndx, arr) {
150 |     var opLevelTest = compileOperationLevel(processedOp, processedPath,
151 |         processed, options)
152 | 
153 |     if (opLevelTest !== null) {
154 |       tests.push(opLevelTest)
155 |     }
156 |   })
157 | 
158 |   var pathLevelDescribe = pathLevelCompiler({
159 |     'pathLevelDescription': processedPath.pathLevelDescription,
160 |     'tests': tests
161 |   })
162 | 
163 |   return requireLevelCompiler({'test': pathLevelDescribe})
164 | }
165 | 
166 | /**
167 |  * GeneratedOp is the result of procressing an operation & compiling the operation-level test code
168 |  * @typedef {object} GeneratedOp
169 |  * @memberof compilation
170 |  * @property {string} operationLevelDescription the operation level description to use incompilation
171 |  * @property {compilation.GeneratedTransaction[]} operationLevelTests Generated test file contents
172 |  */
173 | 
174 | /**
175 |  * Compiles the operation level 'describe' content
176 |  * @function compileOperationLevel
177 |  * @memberof compilation
178 |  * @instance
179 |  * @param {processing.ProcessedOp} processedOp the processed operation object to be compiled
180 |  * @param {processing.ProcessedPath} processedPath the parent processed path object
181 |  * @param {processing.ProcessedSpec} processed the entire processed API spec
182 |  * @param {object} options for use in test compilation
183 |  * @return {compilation.GeneratedOp}
184 |  */
185 | function compileOperationLevel(processedOp, processedPath, processed, options) {
186 |   var tests = []
187 |   var operationLevelCompiler = prepareTemplate(
188 |       path.join(
189 |           options.templates ? options.templates : DefaultTemplateDirectory,
190 |           OperationTemplateName
191 |       )
192 |   )
193 | 
194 |   processedOp.transactions.forEach(function (transaction, ndx, arr) {
195 |     var transactionTest = compileTransactionLevel(transaction, processedOp,
196 |         processedPath, processed, options)
197 | 
198 |     tests.push(transactionTest)
199 |   })
200 | 
201 |   return operationLevelCompiler({
202 |     'operationLevelDescription': processedOp.operationLevelDescription,
203 |     'operationLevelTests': tests
204 |   })
205 | }
206 | 
207 | /**
208 |  * GeneratedTransaction is the compiled unit test code for a specific transaction
209 |  * @typedef {string} GeneratedTransaction
210 |  * @memberof compilation
211 |  */
212 | 
213 | /**
214 |  * Compiles the operation level 'describe' content
215 |  * @function compileTransactionLevel
216 |  * @memberof compilation
217 |  * @instance
218 |  * @param {processing.ProcessedTransaction} transaction the processed transaction object to be compiled
219 |  * @param {processing.ProcessedOp} processedOp the parent processed operation object
220 |  * @param {processing.ProcessedPath} processedPath the parent processed path object
221 |  * @param {processing.ProcessedSpec} processed the entire processed API spec
222 |  * @param {object} options for use in test compilation
223 |  * @return {compilation.GeneratedTransaction}
224 |  */
225 | function compileTransactionLevel(transaction, processedOp, processedPath,
226 |     processed, options) {
227 |   var transactionCompiler = prepareTemplate(
228 |       path.join(
229 |           options.templates ? options.templates : DefaultTemplateDirectory,
230 |           TransactionTemplateName
231 |       )
232 |   )
233 | 
234 |   return transactionCompiler(transaction)
235 | }
236 | 
237 | /**
238 |  * prepares a handlebars template for the template given by the path
239 |  * @function prepareTemplate
240 |  * @memberof compilation
241 |  * @instance
242 |  * @param {string} templatePath path to the template to load
243 |  * @return {function}
244 |  */
245 | function prepareTemplate(templatePath) {
246 |   var source = fs.readFileSync(templatePath, 'utf8');
247 |   return handlebars.compile(source, {noEscape: true})
248 | }
249 | 
250 |
251 |
252 | 253 | 254 | 255 | 256 |
257 | 258 |
259 | 260 |
261 | Documentation generated by JSDoc 3.6.3 on Sat Aug 24 2019 12:31:35 GMT+0100 (Western European Summer Time) using the docdash theme. 262 |
263 | 264 | 265 | 266 | 267 | 268 | -------------------------------------------------------------------------------- /docs/lib_handlebar-helpers.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | lib/handlebar-helpers.js - Documentation 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 23 | 24 | 27 | 28 |
29 | 30 |

lib/handlebar-helpers.js

31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
39 |
40 |
// Copyright 2018 Google Inc. All Rights Reserved.
 41 | //
 42 | // Licensed under the Apache License, Version 2.0 (the "License");
 43 | // you may not use this file except in compliance with the License.
 44 | // You may obtain a copy of the License at
 45 | //
 46 | //      http://www.apache.org/licenses/LICENSE-2.0
 47 | //
 48 | // Unless required by applicable law or agreed to in writing, software
 49 | // distributed under the License is distributed on an "AS IS" BASIS,
 50 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 51 | // See the License for the specific language governing permissions and
 52 | // limitations under the License.
 53 | 
 54 | 'use strict';
 55 | 
 56 | /**
 57 |  * @namespace templateHelpers
 58 |  */
 59 | 
 60 | module.exports = {
 61 |   notEmptyObject: notEmptyObject,
 62 |   json: json,
 63 |   isNotDefaultStatusCode: isNotDefaultStatusCode
 64 | }
 65 | 
 66 | /**
 67 |  * determines if the given object is empty or not
 68 |  * @function notEmptyObject
 69 |  * @memberof templateHelpers
 70 |  * @instance
 71 |  * @param {object} obj object to be evaluated
 72 |  * @return {boolean}
 73 |  */
 74 | function notEmptyObject(obj) {
 75 |   if (arguments.length < 1) {
 76 |     throw new Error('Handlebars Helper \'notEmptyObject\' needs 1 parameter');
 77 |   }
 78 | 
 79 |   return obj !== undefined && Object.keys(obj).length !== 0
 80 | }
 81 | 
 82 | /**
 83 |  * stringifies the given JSON object
 84 |  * @function json
 85 |  * @memberof templateHelpers
 86 |  * @instance
 87 |  * @param {object} obj JSON object to be stringified
 88 |  * @return {string}
 89 |  */
 90 | function json(obj) {
 91 |   if (arguments.length < 1) {
 92 |     throw new Error('Handlebars Helper \'json\' needs 1 parameter');
 93 |   }
 94 | 
 95 |   return JSON.stringify(obj)
 96 | }
 97 | 
 98 | /**
 99 |  * determines if the given status code is the 'default' status
100 |  * @function isNotDefaultStatusCode
101 |  * @memberof templateHelpers
102 |  * @instance
103 |  * @param {(string | number)} code status code to check
104 |  * @return {boolean}
105 |  */
106 | function isNotDefaultStatusCode(code) {
107 |   if (arguments.length < 1) {
108 |     throw new Error(
109 |         'Handlebars Helper \'isNotDefaultStatusCode\' needs 1 parameter');
110 |   }
111 | 
112 |   return code !== 'default'
113 | }
114 |
115 |
116 | 117 | 118 | 119 | 120 |
121 | 122 |
123 | 124 |
125 | Documentation generated by JSDoc 3.6.3 on Sat Aug 24 2019 12:31:35 GMT+0100 (Western European Summer Time) using the docdash theme. 126 |
127 | 128 | 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /docs/lib_util.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | lib/util.js - Documentation 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 23 | 24 | 27 | 28 |
29 | 30 |

lib/util.js

31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
39 |
40 |
// Copyright 2018 Google Inc. All Rights Reserved.
 41 | //
 42 | // Licensed under the Apache License, Version 2.0 (the "License");
 43 | // you may not use this file except in compliance with the License.
 44 | // You may obtain a copy of the License at
 45 | //
 46 | //      http://www.apache.org/licenses/LICENSE-2.0
 47 | //
 48 | // Unless required by applicable law or agreed to in writing, software
 49 | // distributed under the License is distributed on an "AS IS" BASIS,
 50 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 51 | // See the License for the specific language governing permissions and
 52 | // limitations under the License.
 53 | 
 54 | 'use strict';
 55 | 
 56 | module.exports = {
 57 |   merge2: merge2
 58 | }
 59 | 
 60 | /**
 61 |  * merges two objects, with obj1 taking override precedent
 62 |  * @function merge2
 63 |  * @instance
 64 |  * @param {object} obj1 dominant object to merge with
 65 |  * @param {object} obj2 subordinate object to merge with
 66 |  * @return {object}
 67 |  */
 68 | function merge2(obj1, obj2) {
 69 |   if (!obj1) {
 70 |     return obj2;
 71 |   } else if (!obj2) {
 72 |     return obj1;
 73 |   }
 74 | 
 75 |   var result = obj2
 76 | 
 77 |   Object.keys(obj1).forEach(function (key, ndx, arr) {
 78 |     result[key] = obj1[key];
 79 |   });
 80 | 
 81 |   return result
 82 | }
83 |
84 |
85 | 86 | 87 | 88 | 89 |
90 | 91 |
92 | 93 |
94 | Documentation generated by JSDoc 3.6.3 on Sat Aug 24 2019 12:31:35 GMT+0100 (Western European Summer Time) using the docdash theme. 95 |
96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /docs/scripts/linenumber.js: -------------------------------------------------------------------------------- 1 | /*global document */ 2 | (function() { 3 | var source = document.getElementsByClassName('prettyprint source linenums'); 4 | var i = 0; 5 | var lineNumber = 0; 6 | var lineId; 7 | var lines; 8 | var totalLines; 9 | var anchorHash; 10 | 11 | if (source && source[0]) { 12 | anchorHash = document.location.hash.substring(1); 13 | lines = source[0].getElementsByTagName('li'); 14 | totalLines = lines.length; 15 | 16 | for (; i < totalLines; i++) { 17 | lineNumber++; 18 | lineId = 'line' + lineNumber; 19 | lines[i].id = lineId; 20 | if (lineId === anchorHash) { 21 | lines[i].className += ' selected'; 22 | } 23 | } 24 | } 25 | })(); 26 | -------------------------------------------------------------------------------- /docs/scripts/prettify/Apache-License-2.0.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /docs/scripts/prettify/lang-css.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com", 2 | /^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]); 3 | -------------------------------------------------------------------------------- /docs/scripts/prettify/prettify.js: -------------------------------------------------------------------------------- 1 | var q=null;window.PR_SHOULD_USE_CONTINUATION=!0; 2 | (function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a= 3 | [],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;ci[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m), 9 | l=[],p={},d=0,g=e.length;d=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/, 10 | q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/, 11 | q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g, 12 | "");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a), 13 | a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e} 14 | for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"], 18 | "catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"], 19 | H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"], 20 | J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+ 21 | I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]), 22 | ["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css", 23 | /^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}), 24 | ["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes", 25 | hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p=0){var k=k.match(g),f,b;if(b= 26 | !k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p ul { 202 | padding: 0 10px; 203 | } 204 | 205 | nav > ul > li > a { 206 | color: #606; 207 | } 208 | 209 | nav ul ul { 210 | margin-bottom: 10px 211 | } 212 | 213 | nav ul ul + ul { 214 | margin-top: -10px; 215 | } 216 | 217 | nav ul ul a { 218 | color: hsl(207, 1%, 60%); 219 | border-left: 1px solid hsl(207, 10%, 86%); 220 | } 221 | 222 | nav ul ul a, 223 | nav ul ul a:active { 224 | padding-left: 20px 225 | } 226 | 227 | nav h2 { 228 | font-size: 12px; 229 | margin: 0; 230 | padding: 0; 231 | } 232 | 233 | nav > h2 > a { 234 | display: block; 235 | margin: 10px 0 -10px; 236 | color: #606 !important; 237 | } 238 | 239 | footer { 240 | color: hsl(0, 0%, 28%); 241 | margin-left: 250px; 242 | display: block; 243 | padding: 15px; 244 | font-style: italic; 245 | font-size: 90%; 246 | } 247 | 248 | .ancestors { 249 | color: #999 250 | } 251 | 252 | .ancestors a { 253 | color: #999 !important; 254 | } 255 | 256 | .clear { 257 | clear: both 258 | } 259 | 260 | .important { 261 | font-weight: bold; 262 | color: #950B02; 263 | } 264 | 265 | .yes-def { 266 | text-indent: -1000px 267 | } 268 | 269 | .type-signature { 270 | color: #CA79CA 271 | } 272 | 273 | .type-signature:last-child { 274 | color: #eee; 275 | } 276 | 277 | .name, .signature { 278 | font-family: Consolas, Monaco, 'Andale Mono', monospace 279 | } 280 | 281 | .signature { 282 | color: #fc83ff; 283 | } 284 | 285 | .details { 286 | margin-top: 6px; 287 | border-left: 2px solid #DDD; 288 | line-height: 20px; 289 | font-size: 14px; 290 | } 291 | 292 | .details dt { 293 | width: 120px; 294 | float: left; 295 | padding-left: 10px; 296 | } 297 | 298 | .details dd { 299 | margin-left: 70px; 300 | margin-top: 6px; 301 | margin-bottom: 6px; 302 | } 303 | 304 | .details ul { 305 | margin: 0 306 | } 307 | 308 | .details ul { 309 | list-style-type: none 310 | } 311 | 312 | .details pre.prettyprint { 313 | margin: 0 314 | } 315 | 316 | .details .object-value { 317 | padding-top: 0 318 | } 319 | 320 | .description { 321 | margin-bottom: 1em; 322 | margin-top: 1em; 323 | } 324 | 325 | .code-caption { 326 | font-style: italic; 327 | font-size: 107%; 328 | margin: 0; 329 | } 330 | 331 | .prettyprint { 332 | font-size: 14px; 333 | overflow: auto; 334 | } 335 | 336 | .prettyprint.source { 337 | width: inherit; 338 | line-height: 18px; 339 | display: block; 340 | background-color: #0d152a; 341 | color: #aeaeae; 342 | } 343 | 344 | .prettyprint code { 345 | line-height: 18px; 346 | display: block; 347 | background-color: #0d152a; 348 | color: #4D4E53; 349 | } 350 | 351 | .prettyprint > code { 352 | padding: 15px; 353 | } 354 | 355 | .prettyprint .linenums code { 356 | padding: 0 15px 357 | } 358 | 359 | .prettyprint .linenums li:first-of-type code { 360 | padding-top: 15px 361 | } 362 | 363 | .prettyprint code span.line { 364 | display: inline-block 365 | } 366 | 367 | .prettyprint.linenums { 368 | padding-left: 70px; 369 | -webkit-user-select: none; 370 | -moz-user-select: none; 371 | -ms-user-select: none; 372 | user-select: none; 373 | } 374 | 375 | .prettyprint.linenums ol { 376 | padding-left: 0 377 | } 378 | 379 | .prettyprint.linenums li { 380 | border-left: 3px #34446B solid; 381 | } 382 | 383 | .prettyprint.linenums li.selected, .prettyprint.linenums li.selected * { 384 | background-color: #34446B; 385 | } 386 | 387 | .prettyprint.linenums li * { 388 | -webkit-user-select: text; 389 | -moz-user-select: text; 390 | -ms-user-select: text; 391 | user-select: text; 392 | } 393 | 394 | .params, .props { 395 | border-spacing: 0; 396 | border: 1px solid #ddd; 397 | border-collapse: collapse; 398 | border-radius: 3px; 399 | box-shadow: 0 1px 3px rgba(0,0,0,0.1); 400 | width: 100%; 401 | font-size: 14px; 402 | margin: 1em 0; 403 | } 404 | 405 | .params .type { 406 | white-space: nowrap; 407 | } 408 | 409 | .params code { 410 | white-space: pre; 411 | } 412 | 413 | .params td, .params .name, .props .name, .name code { 414 | color: #4D4E53; 415 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 416 | font-size: 100%; 417 | } 418 | 419 | .params td, .params th, .props td, .props th { 420 | margin: 0px; 421 | text-align: left; 422 | vertical-align: top; 423 | padding: 10px; 424 | display: table-cell; 425 | } 426 | 427 | .params td { 428 | border-top: 1px solid #eee 429 | } 430 | 431 | .params thead tr, .props thead tr { 432 | background-color: #fff; 433 | font-weight: bold; 434 | } 435 | 436 | .params .params thead tr, .props .props thead tr { 437 | background-color: #fff; 438 | font-weight: bold; 439 | } 440 | 441 | .params td.description > p:first-child, .props td.description > p:first-child { 442 | margin-top: 0; 443 | padding-top: 0; 444 | } 445 | 446 | .params td.description > p:last-child, .props td.description > p:last-child { 447 | margin-bottom: 0; 448 | padding-bottom: 0; 449 | } 450 | 451 | span.param-type, .params td .param-type, .param-type dd { 452 | color: #606; 453 | font-family: Consolas, Monaco, 'Andale Mono', monospace 454 | } 455 | 456 | .param-type dt, .param-type dd { 457 | display: inline-block 458 | } 459 | 460 | .param-type { 461 | margin: 14px 0; 462 | } 463 | 464 | .disabled { 465 | color: #454545 466 | } 467 | 468 | /* navicon button */ 469 | .navicon-button { 470 | display: none; 471 | position: relative; 472 | padding: 2.0625rem 1.5rem; 473 | transition: 0.25s; 474 | cursor: pointer; 475 | -webkit-user-select: none; 476 | -moz-user-select: none; 477 | -ms-user-select: none; 478 | user-select: none; 479 | opacity: .8; 480 | } 481 | .navicon-button .navicon:before, .navicon-button .navicon:after { 482 | transition: 0.25s; 483 | } 484 | .navicon-button:hover { 485 | transition: 0.5s; 486 | opacity: 1; 487 | } 488 | .navicon-button:hover .navicon:before, .navicon-button:hover .navicon:after { 489 | transition: 0.25s; 490 | } 491 | .navicon-button:hover .navicon:before { 492 | top: .825rem; 493 | } 494 | .navicon-button:hover .navicon:after { 495 | top: -.825rem; 496 | } 497 | 498 | /* navicon */ 499 | .navicon { 500 | position: relative; 501 | width: 2.5em; 502 | height: .3125rem; 503 | background: #000; 504 | transition: 0.3s; 505 | border-radius: 2.5rem; 506 | } 507 | .navicon:before, .navicon:after { 508 | display: block; 509 | content: ""; 510 | height: .3125rem; 511 | width: 2.5rem; 512 | background: #000; 513 | position: absolute; 514 | z-index: -1; 515 | transition: 0.3s 0.25s; 516 | border-radius: 1rem; 517 | } 518 | .navicon:before { 519 | top: .625rem; 520 | } 521 | .navicon:after { 522 | top: -.625rem; 523 | } 524 | 525 | /* open */ 526 | .nav-trigger:checked + label:not(.steps) .navicon:before, 527 | .nav-trigger:checked + label:not(.steps) .navicon:after { 528 | top: 0 !important; 529 | } 530 | 531 | .nav-trigger:checked + label .navicon:before, 532 | .nav-trigger:checked + label .navicon:after { 533 | transition: 0.5s; 534 | } 535 | 536 | /* Minus */ 537 | .nav-trigger:checked + label { 538 | -webkit-transform: scale(0.75); 539 | transform: scale(0.75); 540 | } 541 | 542 | /* × and + */ 543 | .nav-trigger:checked + label.plus .navicon, 544 | .nav-trigger:checked + label.x .navicon { 545 | background: transparent; 546 | } 547 | 548 | .nav-trigger:checked + label.plus .navicon:before, 549 | .nav-trigger:checked + label.x .navicon:before { 550 | -webkit-transform: rotate(-45deg); 551 | transform: rotate(-45deg); 552 | background: #FFF; 553 | } 554 | 555 | .nav-trigger:checked + label.plus .navicon:after, 556 | .nav-trigger:checked + label.x .navicon:after { 557 | -webkit-transform: rotate(45deg); 558 | transform: rotate(45deg); 559 | background: #FFF; 560 | } 561 | 562 | .nav-trigger:checked + label.plus { 563 | -webkit-transform: scale(0.75) rotate(45deg); 564 | transform: scale(0.75) rotate(45deg); 565 | } 566 | 567 | .nav-trigger:checked ~ nav { 568 | left: 0 !important; 569 | } 570 | 571 | .nav-trigger:checked ~ .overlay { 572 | display: block; 573 | } 574 | 575 | .nav-trigger { 576 | position: fixed; 577 | top: 0; 578 | clip: rect(0, 0, 0, 0); 579 | } 580 | 581 | .overlay { 582 | display: none; 583 | position: fixed; 584 | top: 0; 585 | bottom: 0; 586 | left: 0; 587 | right: 0; 588 | width: 100%; 589 | height: 100%; 590 | background: hsla(0, 0%, 0%, 0.5); 591 | z-index: 1; 592 | } 593 | 594 | @media only screen and (min-width: 320px) and (max-width: 680px) { 595 | body { 596 | overflow-x: hidden; 597 | } 598 | 599 | nav { 600 | background: #FFF; 601 | width: 250px; 602 | height: 100%; 603 | position: fixed; 604 | top: 0; 605 | right: 0; 606 | bottom: 0; 607 | left: -250px; 608 | z-index: 3; 609 | padding: 0 10px; 610 | transition: left 0.2s; 611 | } 612 | 613 | .navicon-button { 614 | display: inline-block; 615 | position: fixed; 616 | top: 1.5em; 617 | right: 0; 618 | z-index: 2; 619 | } 620 | 621 | #main { 622 | width: 100%; 623 | min-width: 360px; 624 | } 625 | 626 | #main h1.page-title { 627 | margin: 1em 0; 628 | } 629 | 630 | #main section { 631 | padding: 0; 632 | } 633 | 634 | footer { 635 | margin-left: 0; 636 | } 637 | } 638 | 639 | /** Add a '#' to static members */ 640 | [data-type="member"] a::before { 641 | content: '#'; 642 | display: inline-block; 643 | margin-left: -14px; 644 | margin-right: 5px; 645 | } 646 | -------------------------------------------------------------------------------- /docs/styles/prettify.css: -------------------------------------------------------------------------------- 1 | .pln { 2 | color: #ddd; 3 | } 4 | 5 | /* string content */ 6 | .str { 7 | color: #61ce3c; 8 | } 9 | 10 | /* a keyword */ 11 | .kwd { 12 | color: #fbde2d; 13 | } 14 | 15 | /* a comment */ 16 | .com { 17 | color: #aeaeae; 18 | } 19 | 20 | /* a type name */ 21 | .typ { 22 | color: #8da6ce; 23 | } 24 | 25 | /* a literal value */ 26 | .lit { 27 | color: #fbde2d; 28 | } 29 | 30 | /* punctuation */ 31 | .pun { 32 | color: #ddd; 33 | } 34 | 35 | /* lisp open bracket */ 36 | .opn { 37 | color: #000000; 38 | } 39 | 40 | /* lisp close bracket */ 41 | .clo { 42 | color: #000000; 43 | } 44 | 45 | /* a markup tag name */ 46 | .tag { 47 | color: #8da6ce; 48 | } 49 | 50 | /* a markup attribute name */ 51 | .atn { 52 | color: #fbde2d; 53 | } 54 | 55 | /* a markup attribute value */ 56 | .atv { 57 | color: #ddd; 58 | } 59 | 60 | /* a declaration */ 61 | .dec { 62 | color: #EF5050; 63 | } 64 | 65 | /* a variable name */ 66 | .var { 67 | color: #c82829; 68 | } 69 | 70 | /* a function name */ 71 | .fun { 72 | color: #4271ae; 73 | } 74 | 75 | /* Specify class=linenums on a pre to get line numbering */ 76 | ol.linenums { 77 | margin-top: 0; 78 | margin-bottom: 0; 79 | } 80 | -------------------------------------------------------------------------------- /docs/templateHelpers.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | templateHelpers - Documentation 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 23 | 24 | 27 | 28 |
29 | 30 |

templateHelpers

31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
39 | 40 |
41 | 42 |

43 | templateHelpers 44 |

45 | 46 | 47 |
48 | 49 |
50 |
51 | 52 | 53 | 54 |
55 | 56 | 57 |
Source:
58 |
61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 |
93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 |
102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 |

Methods

117 | 118 | 119 | 120 | 121 | 122 | 123 |

isNotDefaultStatusCode(code) → {boolean}

124 | 125 | 126 | 127 | 128 | 129 | 130 |
131 | 132 | 133 |
Source:
134 |
137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 |
169 | 170 | 171 | 172 | 173 | 174 |
175 | determines if the given status code is the 'default' status 176 |
177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 |
Parameters:
189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 |
NameTypeDescription
code 217 | 218 | 219 | string 220 | | 221 | 222 | number 223 | 224 | 225 | 226 | status code to check
238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 |
Returns:
253 | 254 | 255 | 256 | 257 |
258 |
259 | Type 260 |
261 |
262 | 263 | boolean 264 | 265 | 266 |
267 |
268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 |

json(obj) → {string}

278 | 279 | 280 | 281 | 282 | 283 | 284 |
285 | 286 | 287 |
Source:
288 |
291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 |
323 | 324 | 325 | 326 | 327 | 328 |
329 | stringifies the given JSON object 330 |
331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 |
Parameters:
343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 |
NameTypeDescription
obj 371 | 372 | 373 | object 374 | 375 | 376 | 377 | JSON object to be stringified
389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 |
Returns:
404 | 405 | 406 | 407 | 408 |
409 |
410 | Type 411 |
412 |
413 | 414 | string 415 | 416 | 417 |
418 |
419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 |

notEmptyObject(obj) → {boolean}

429 | 430 | 431 | 432 | 433 | 434 | 435 |
436 | 437 | 438 |
Source:
439 |
442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 |
474 | 475 | 476 | 477 | 478 | 479 |
480 | determines if the given object is empty or not 481 |
482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 |
Parameters:
494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 |
NameTypeDescription
obj 522 | 523 | 524 | object 525 | 526 | 527 | 528 | object to be evaluated
540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 |
Returns:
555 | 556 | 557 | 558 | 559 |
560 |
561 | Type 562 |
563 |
564 | 565 | boolean 566 | 567 | 568 |
569 |
570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 |
581 | 582 |
583 | 584 | 585 | 586 | 587 |
588 | 589 |
590 | 591 |
592 | Documentation generated by JSDoc 3.6.3 on Sat Aug 24 2019 12:31:35 GMT+0100 (Western European Summer Time) using the docdash theme. 593 |
594 | 595 | 596 | 597 | 598 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | var proc = require('./lib/process.js') 18 | var compile = require('./lib/compile.js') 19 | var sway = require('sway') 20 | var fs = require('fs') 21 | var join = require('path').join 22 | var merge2 = require('./lib/util').merge2 23 | 24 | module.exports = { 25 | generate: generate 26 | } 27 | 28 | /** 29 | * GenerationResults is the final result of the generation function 30 | * @typedef {object} GenerationResults 31 | * @property {compilation.GeneratedTest[]} generated set of generated test objects 32 | */ 33 | 34 | /** 35 | * Generates test artifacts based on the given API Spec and options 36 | * @function generate 37 | * @instance 38 | * @param {string} specPath path to the API spec document 39 | * @param {object} options options to apply during processing of API spec 40 | * @return {Promise} 41 | */ 42 | function generate(specPath, options) { 43 | return sway.create({ 44 | 'definition': specPath, 45 | 'jsonRefs': options && options.jsonRefs, 46 | 'customFormats': options && options.customFormats, 47 | 'customFormatGenerators': options && options.customFormatGenerators, 48 | 'customValidators': options && options.customValidators 49 | }) 50 | .then(function (api) { 51 | if (options.customValues) { 52 | options.customValues = JSON.parse(options.customValues); 53 | } 54 | 55 | if (options.customValuesFile) { 56 | var customFromFile = require( 57 | join(process.cwd(), options.customValuesFile)) 58 | options.customValues = merge2(options.customValues, customFromFile) 59 | } 60 | 61 | var processed = proc(api, options) 62 | var compiled = compile(processed, options) 63 | 64 | if (options.writeTo !== undefined) { 65 | if (!fs.existsSync(options.writeTo)) { 66 | fs.mkdirSync(options.writeTo) 67 | } 68 | 69 | try { 70 | for (var i = 0; i < compiled.length; i++) { 71 | const testObj = compiled[i]; 72 | fs.writeFileSync(join(options.writeTo, testObj.filename), testObj.contents) 73 | } 74 | } catch (err) { 75 | console.log(err); 76 | } 77 | } 78 | 79 | return {'generated': compiled} 80 | }, function (err) { 81 | console.error(err.stack); 82 | return err 83 | }); 84 | } 85 | -------------------------------------------------------------------------------- /jsdoc.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": { 3 | "exclude": [ 4 | "./node_modules", 5 | "./test", 6 | "./templates", 7 | "./docs" 8 | ] 9 | }, 10 | "opts": { 11 | "destination": "./docs", 12 | "readme": "./README.md", 13 | "recurse": true, 14 | "template": "./node_modules/docdash" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/compile.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | var handlebars = require('handlebars'); 18 | var helpers = require('./handlebar-helpers') 19 | var fs = require('fs') 20 | var path = require('path') 21 | 22 | var DefaultTemplateDirectory = path.join(__dirname, '..', 'templates'); 23 | var TopLevelTemplateName = 'topLevel.handlebars'; 24 | var PathTemplateName = 'pathLevel.handlebars'; 25 | var OperationTemplateName = 'operationLevel.handlebars'; 26 | var TransactionTemplateName = 'transactionLevel.handlebars'; 27 | 28 | /** 29 | * @namespace compilation 30 | */ 31 | 32 | module.exports = compile 33 | 34 | handlebars.registerHelper('notEmptyObject', helpers.notEmptyObject) 35 | handlebars.registerHelper('json', helpers.json) 36 | handlebars.registerHelper('isNotDefaultStatusCode', 37 | helpers.isNotDefaultStatusCode) 38 | 39 | /** 40 | * GeneratedTest is the set of results from procressing a spec & compiling the test code 41 | * @typedef {object} GeneratedTest 42 | * @memberof compilation 43 | * @property {string} filename A file name based off of the path being tested 44 | * @property {string} contents Generated test file contents 45 | */ 46 | 47 | /** 48 | * Compiles the templated test files with the given processed API spec data 49 | * @function compile 50 | * @memberof compilation 51 | * @instance 52 | * @param {processing.ProcessedSpec} processed the API spec data processed for test generation 53 | * @param {object} options for use in compilation of the tests 54 | * @return {compilation.GeneratedTest[]} 55 | */ 56 | function compile(processed, options) { 57 | var tests = [] 58 | 59 | processed.tests.forEach(function (processedPath, ndx, arr) { 60 | var pathTest = compilePathLevel(processedPath, processed, options) 61 | 62 | if (pathTest !== null) { 63 | tests.push({ 64 | 'filename': processedPath.name + '-test.js', 65 | 'contents': pathTest 66 | }) 67 | } 68 | }); 69 | 70 | if (tests.length === 0) { 71 | return null 72 | } 73 | 74 | return tests 75 | } 76 | 77 | /** 78 | * GeneratedPath is the result of procressing a path & compiling the path-level test code 79 | * @typedef {object} GeneratedPath 80 | * @memberof compilation 81 | * @property {string} test the generated test code portion 82 | */ 83 | 84 | /** 85 | * Compiles the path level 'describe' content 86 | * @function compilePathLevel 87 | * @memberof compilation 88 | * @instance 89 | * @param {processing.ProcessedPath} processedPath the processed path object to be compiled 90 | * @param {processing.ProcessedSpec} processed the entire processed API spec 91 | * @param {object} options for use in test compilation 92 | * @return {compilation.GeneratedPath} 93 | */ 94 | function compilePathLevel(processedPath, processed, options) { 95 | var tests = [] 96 | var requireLevelCompiler = prepareTemplate( 97 | path.join( 98 | options.templates ? options.templates : DefaultTemplateDirectory, 99 | TopLevelTemplateName 100 | ) 101 | ) 102 | 103 | var pathLevelCompiler = prepareTemplate( 104 | path.join( 105 | options.templates ? options.templates : DefaultTemplateDirectory, 106 | PathTemplateName 107 | ) 108 | ) 109 | 110 | processedPath.operations.forEach(function (processedOp, ndx, arr) { 111 | var opLevelTest = compileOperationLevel(processedOp, processedPath, 112 | processed, options) 113 | 114 | if (opLevelTest !== null) { 115 | tests.push(opLevelTest) 116 | } 117 | }) 118 | 119 | var pathLevelDescribe = pathLevelCompiler({ 120 | 'pathLevelDescription': processedPath.pathLevelDescription, 121 | 'tests': tests 122 | }) 123 | 124 | return requireLevelCompiler({'test': pathLevelDescribe}) 125 | } 126 | 127 | /** 128 | * GeneratedOp is the result of procressing an operation & compiling the operation-level test code 129 | * @typedef {object} GeneratedOp 130 | * @memberof compilation 131 | * @property {string} operationLevelDescription the operation level description to use incompilation 132 | * @property {compilation.GeneratedTransaction[]} operationLevelTests Generated test file contents 133 | */ 134 | 135 | /** 136 | * Compiles the operation level 'describe' content 137 | * @function compileOperationLevel 138 | * @memberof compilation 139 | * @instance 140 | * @param {processing.ProcessedOp} processedOp the processed operation object to be compiled 141 | * @param {processing.ProcessedPath} processedPath the parent processed path object 142 | * @param {processing.ProcessedSpec} processed the entire processed API spec 143 | * @param {object} options for use in test compilation 144 | * @return {compilation.GeneratedOp} 145 | */ 146 | function compileOperationLevel(processedOp, processedPath, processed, options) { 147 | var tests = [] 148 | var operationLevelCompiler = prepareTemplate( 149 | path.join( 150 | options.templates ? options.templates : DefaultTemplateDirectory, 151 | OperationTemplateName 152 | ) 153 | ) 154 | 155 | processedOp.transactions.forEach(function (transaction, ndx, arr) { 156 | var transactionTest = compileTransactionLevel(transaction, processedOp, 157 | processedPath, processed, options) 158 | 159 | tests.push(transactionTest) 160 | }) 161 | 162 | return operationLevelCompiler({ 163 | 'operationLevelDescription': processedOp.operationLevelDescription, 164 | 'operationLevelTests': tests 165 | }) 166 | } 167 | 168 | /** 169 | * GeneratedTransaction is the compiled unit test code for a specific transaction 170 | * @typedef {string} GeneratedTransaction 171 | * @memberof compilation 172 | */ 173 | 174 | /** 175 | * Compiles the operation level 'describe' content 176 | * @function compileTransactionLevel 177 | * @memberof compilation 178 | * @instance 179 | * @param {processing.ProcessedTransaction} transaction the processed transaction object to be compiled 180 | * @param {processing.ProcessedOp} processedOp the parent processed operation object 181 | * @param {processing.ProcessedPath} processedPath the parent processed path object 182 | * @param {processing.ProcessedSpec} processed the entire processed API spec 183 | * @param {object} options for use in test compilation 184 | * @return {compilation.GeneratedTransaction} 185 | */ 186 | function compileTransactionLevel(transaction, processedOp, processedPath, 187 | processed, options) { 188 | var transactionCompiler = prepareTemplate( 189 | path.join( 190 | options.templates ? options.templates : DefaultTemplateDirectory, 191 | TransactionTemplateName 192 | ) 193 | ) 194 | 195 | return transactionCompiler(transaction) 196 | } 197 | 198 | /** 199 | * prepares a handlebars template for the template given by the path 200 | * @function prepareTemplate 201 | * @memberof compilation 202 | * @instance 203 | * @param {string} templatePath path to the template to load 204 | * @return {function} 205 | */ 206 | function prepareTemplate(templatePath) { 207 | var source = fs.readFileSync(templatePath, 'utf8'); 208 | return handlebars.compile(source, {noEscape: true}) 209 | } 210 | -------------------------------------------------------------------------------- /lib/handlebar-helpers.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | /** 18 | * @namespace templateHelpers 19 | */ 20 | 21 | module.exports = { 22 | notEmptyObject: notEmptyObject, 23 | json: json, 24 | isNotDefaultStatusCode: isNotDefaultStatusCode 25 | } 26 | 27 | /** 28 | * determines if the given object is empty or not 29 | * @function notEmptyObject 30 | * @memberof templateHelpers 31 | * @instance 32 | * @param {object} obj object to be evaluated 33 | * @return {boolean} 34 | */ 35 | function notEmptyObject(obj) { 36 | if (arguments.length < 1) { 37 | throw new Error('Handlebars Helper \'notEmptyObject\' needs 1 parameter'); 38 | } 39 | 40 | return obj !== undefined && Object.keys(obj).length !== 0 41 | } 42 | 43 | /** 44 | * stringifies the given JSON object 45 | * @function json 46 | * @memberof templateHelpers 47 | * @instance 48 | * @param {object} obj JSON object to be stringified 49 | * @return {string} 50 | */ 51 | function json(obj) { 52 | if (arguments.length < 1) { 53 | throw new Error('Handlebars Helper \'json\' needs 1 parameter'); 54 | } 55 | 56 | return JSON.stringify(obj) 57 | } 58 | 59 | /** 60 | * determines if the given status code is the 'default' status 61 | * @function isNotDefaultStatusCode 62 | * @memberof templateHelpers 63 | * @instance 64 | * @param {(string | number)} code status code to check 65 | * @return {boolean} 66 | */ 67 | function isNotDefaultStatusCode(code) { 68 | if (arguments.length < 1) { 69 | throw new Error( 70 | 'Handlebars Helper \'isNotDefaultStatusCode\' needs 1 parameter'); 71 | } 72 | 73 | return code !== 'default' 74 | } -------------------------------------------------------------------------------- /lib/process.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | var util = require('util') 18 | var helpers = require('./handlebar-helpers') 19 | var merge2 = require('./util').merge2; 20 | 21 | /** 22 | * @namespace processing 23 | */ 24 | 25 | module.exports = process 26 | 27 | /** 28 | * ProcessedSpec is the fully traversed spec processed for unit test compilation 29 | * @typedef {object} ProcessedSpec 30 | * @memberof processing 31 | * @property {string} host Hostname to be used in the test requests; derived from the options or spec 32 | * @property {string} scheme HTTP schema to be used in the test requests; derived from the spec 33 | * @property {string} basePath Base path as defined by the spec 34 | * @property {string[]} consumes Global content type 'consumes' collection 35 | * @property {ProcessedPath[]} tests Collection of paths processed for test compilation 36 | */ 37 | 38 | /** 39 | * Processes the given API spec, creating test data artifacts 40 | * @function process 41 | * @memberof processing 42 | * @instance 43 | * @param {object} api API spec object to be processed 44 | * @param {object} options options to be used during processing 45 | * @return {processing.ProcessedSpec} 46 | */ 47 | function process(api, options) { 48 | var processedSpec = { 49 | 'host': (options.host !== undefined ? options.host : (api.host !== undefined 50 | ? api.host : 'localhost:5000')), 51 | 'scheme': (options.scheme !== undefined ? options.scheme : (api.schemes 52 | !== undefined ? api.schemes[0] : 'http')), 53 | 'basePath': (api.basePath !== undefined ? api.basePath : ''), 54 | 'consumes': (api.consumes !== undefined ? api.consumes : []), 55 | 'produces': (api.produces !== undefined ? api.produces : []) 56 | } 57 | 58 | var processedPaths = processPaths(api, processedSpec, options) 59 | if (processedPaths.length == 0) { 60 | console.log('no paths to process in spec') 61 | return null 62 | } 63 | 64 | processedSpec.tests = processedPaths 65 | 66 | return processedSpec 67 | } 68 | 69 | /** 70 | * ProcessedPath is the fully traversed path resource ready for unit test compilation 71 | * @typedef {object} ProcessedPath 72 | * @memberof processing 73 | * @property {string} name name to be used in generation of a file name; based on the path 74 | * @property {string} pathLevelDescription the brief description to use at the top level 'describe' block 75 | * @property {processing.ProcessedOp[]} operations Collection of operations processed for test compilation 76 | */ 77 | 78 | /** 79 | * Processes the paths defined by the api spec, or indicated by the options 80 | * @function processPaths 81 | * @memberof processing 82 | * @instance 83 | * @param {object} api parsed OpenAPI spec object 84 | * @param {object} topLevel global spec properties 85 | * @param {object} options options to use during processing 86 | * @return {processing.ProcessedPath[]} 87 | */ 88 | function processPaths(api, topLevel, options) { 89 | var data = [] 90 | var targetPaths = []; 91 | 92 | if (options.paths !== undefined) { 93 | options.paths.forEach(function (path, ndx, arr) { 94 | targetPaths.push(api.getPath(path)) 95 | }) 96 | } else { 97 | targetPaths = api.getPaths(); 98 | } 99 | 100 | if (options.statusCodes !== undefined) { 101 | var tempPaths = []; 102 | options.statusCodes.forEach(function (code, ndx, arr) { 103 | for (var ndx = 0; ndx < targetPaths.length; ndx++) { 104 | var opList = targetPaths[ndx].getOperations(); 105 | for (var opNdx = 0; opNdx < opList.length; opNdx++) { 106 | if (opList[opNdx].getResponse(code)) { 107 | tempPaths.push(targetPaths[ndx]) 108 | break; 109 | } 110 | } 111 | } 112 | }); 113 | 114 | targetPaths = tempPaths; 115 | } 116 | 117 | targetPaths.forEach(function (pathObj, ndx, arr) { 118 | var ops = processOperations(api, pathObj, topLevel, options) 119 | if (ops.length !== 0) { 120 | data.push({ 121 | 'name': pathObj.path.replace(/\//g, '-').substring(1), 122 | 'pathLevelDescription': 'tests for ' + pathObj.path, 123 | 'operations': ops 124 | }) 125 | } 126 | }) 127 | 128 | return data 129 | } 130 | 131 | /** 132 | * ProcessedOp is the fully traversed path operation ready for unit test compilation 133 | * @typedef {object} ProcessedOp 134 | * @memberof processing 135 | * @instance 136 | * @property {string} operationLevelDescription the brief description to use at the inner 'describe' block 137 | * @property {processing.ProcessedTransaction[]} transactions Collection of transactions processed for test compilation 138 | */ 139 | 140 | /** 141 | * Processes the operations of the given Path object 142 | * @function processOperations 143 | * @memberof processing 144 | * @instance 145 | * @param {object} api parsed OpenAPI spec object 146 | * @param {object} parentPath sway Path object being processed 147 | * @param {object} topLevel global spec properties 148 | * @param {object} options options to use during processing 149 | * @return {processing.ProcessedOp[]} 150 | */ 151 | function processOperations(api, parentPath, topLevel, options) { 152 | var ops = [] 153 | parentPath.getOperations().forEach(function (op, ndx, arr) { 154 | var transactions = processTransactions(api, op, parentPath, topLevel, 155 | options) 156 | if (transactions.length !== 0) { 157 | ops.push({ 158 | 'operationLevelDescription': 'tests for ' + op.method, 159 | 'transactions': transactions 160 | }) 161 | } 162 | }) 163 | 164 | return ops 165 | } 166 | 167 | /** 168 | * ProcessedTransaction is an HTTP transaction (request/response pair) processed for test compilation 169 | * @typedef {object} ProcessedTransaction 170 | * @memberof processing 171 | * @property {string} testLevelDescription the brief description to use in the test 'it' block 172 | * @property {string} scheme copied from the globally defined HTTP schema 173 | * @property {string} host copied from the globally defined hostname or as specified in the options 174 | * @property {string} path fully qualified path built with the base path; includes path param substitutions 175 | * @property {op} op the REST operation to use 176 | * @property {object} body the request body params 177 | * @property {object} query the request query params 178 | * @property {object} formData the request form data params 179 | * @property {ExpectedResponse} expected the expected response to use for test validation 180 | * @property {boolean} hasSamples flag indicating if the expected response includes sample values 181 | * @property {string[]} consumes based on globally defined conumes or operation defined types 182 | */ 183 | 184 | /** 185 | * Processes the transactions for a given Path + Operation 186 | * @function processTransactions 187 | * @memberof processing 188 | * @instance 189 | * @param {object} api parsed OpenAPI spec object 190 | * @param {object} parentOp parent sway Operation object being processed 191 | * @param {object} parentPath parent sway Path object being processed 192 | * @param {object} topLevel global spec properties 193 | * @param {object} options options to use during processing 194 | * @return {processing.ProcessedTransaction[]} 195 | */ 196 | function processTransactions(api, parentOp, parentPath, topLevel, options) { 197 | var transactions = [] 198 | 199 | parentOp.getResponses().forEach(function (res, ndx, arr) { 200 | if (options.statusCodes && options.statusCodes.indexOf(res.statusCode) 201 | === -1) { 202 | // skip unwanted status codes 203 | return; 204 | } 205 | 206 | // count multiple status code entries 207 | var count = 1; 208 | if (options.customValues 209 | && options.customValues[parentPath.path] 210 | && options.customValues[parentPath.path][parentOp.method] 211 | && options.customValues[parentPath.path][parentOp.method][res.statusCode] 212 | && Array.isArray(options.customValues[parentPath.path][parentOp.method][res.statusCode])) { 213 | count = options.customValues[parentPath.path][parentOp.method][res.statusCode].length; 214 | } 215 | 216 | // generate transactions for all custom values 217 | for (let index = 0; index < count; index++) { 218 | var params = processParams(res.statusCode, parentOp, parentPath, options, index) 219 | var expected = processResponse(res, parentOp.method, parentPath.path, options, index) 220 | 221 | var hasValue = options.samples 222 | if (expected.custom) { 223 | delete expected.custom 224 | hasValue = true 225 | } 226 | 227 | transactions.push({ 228 | 'testLevelDescription': util.format('should respond %s for "%s"', 229 | res.statusCode, escapeSingleQuotes(replaceNewlines(res.description))), 230 | 'scheme': topLevel.scheme, 231 | 'host': topLevel.host, 232 | 'path': (topLevel.basePath + params.path), 233 | 'op': parentOp.method, 234 | 'body': params.body, 235 | 'query': params.query, 236 | 'formData': params.formData, 237 | 'expected': expected, 238 | 'hasValue': hasValue, 239 | 'headers': merge2(params.headers, 240 | processHeaders(res.statusCode, parentOp, parentPath, topLevel, 241 | options, index)) 242 | }) 243 | } 244 | }) 245 | 246 | return transactions 247 | } 248 | 249 | /** 250 | * ProcessedParams is an object representing the processed request parameters for unit test compilation 251 | * @typedef {object} ProcessedParams 252 | * @memberof processing 253 | * @property {string} path processed path with path parameter sample substitutions 254 | * @property {object} body the request body params 255 | * @property {object} query the request query params 256 | * @property {object} formData the request form data params 257 | */ 258 | 259 | /** 260 | * Processes the parameters of a Path + Operation for use in a test 261 | * @function processParams 262 | * @memberof processing 263 | * @instance 264 | * @param {number} code response status code being processed 265 | * @param {object} op sway Operation object that's being processed 266 | * @param {object} path sway Path object that's being process 267 | * @param {object} options options to use during processing 268 | * @return {processing.ProcessedParams} 269 | */ 270 | function processParams(code, op, path, options, index) { 271 | var opCustomQuery = lookupCustomQueryValue(code, op.method, path.path, options, index); 272 | var params = { 273 | 'body': {}, 274 | 'query': opCustomQuery || {}, 275 | 'formData': {}, 276 | 'path': path.path, 277 | 'headers': {} 278 | } 279 | 280 | op.getParameters().forEach(function (param, ndx, arr) { 281 | var customValue = lookupCustomValue(param.name, param.in, code, op.method, 282 | path.path, options, index) 283 | // Skip optional parameter with specifically nulled custom value or in-line example 284 | if ((customValue === null || param.example === null) && (!param.required)) { 285 | return 286 | } 287 | // If multiple examples are present, use the first one 288 | if (param.examples) { 289 | param.example = param.examples[Object.keys(param.examples)[0]].value; 290 | } 291 | 292 | if (param.in === 'body') { 293 | params.body = customValue !== undefined ? customValue : param.example !== undefined ? param.example : param.getSample() 294 | } else if (param.in === 'query' && !opCustomQuery) { 295 | params.query[param.name] = param.example !== undefined ? param.example : param.getSample(); 296 | } else if (param.in === 'formData') { 297 | try { 298 | params.formData[param.name] = customValue !== undefined ? customValue 299 | : param.example !== undefined ? param.example : param.getSample() 300 | } catch (e) { // this will be because of formData property of type 'file' 301 | params.formData[param.name] = '{fileUpload}' 302 | } 303 | } else if (param.in === 'path') { 304 | params.path = pathify(params.path, param, customValue) 305 | } else if (param.in === 'header') { 306 | params.headers[param.name] = customValue !== undefined ? customValue 307 | : param.example !== undefined ? param.example : param.getSample() 308 | } 309 | }) 310 | 311 | path.getParameters().forEach(function (param, ndx, arr) { 312 | var customValue = lookupCustomValue(param.name, param.in, code, op.method, 313 | path.path, options, index) 314 | // Skip optional parameter with specifically nulled custom value or in-line example 315 | if ((customValue === null || param.example === null) && (!param.required)) { 316 | return 317 | } 318 | params.path = pathify(params.path, param, customValue) 319 | }) 320 | 321 | return params 322 | } 323 | 324 | /** 325 | * resolves any custom values available for the given parameter 326 | * @function lookupCustomValue 327 | * @memberof processing 328 | * @instance 329 | * @param {string} name parameter name being looked up 330 | * @param {string} location where the parameter is in the request/response 331 | * @param {number} code response status code being evaluated 332 | * @param {string} op the operation method being processed 333 | * @param {string} path API path being processed 334 | * @param {object} options for use in looking up the custom value 335 | * @return {any} 336 | */ 337 | function lookupCustomValue(name, location, code, op, path, options, index) { 338 | var levels = [path, op, code] 339 | var val = undefined; 340 | 341 | if (options.customValues) { 342 | var curr = options.customValues 343 | 344 | var ndx = 0 345 | do { 346 | if (curr[location]) { 347 | if (curr[location][name] !== undefined) { 348 | val = curr[location][name] 349 | } 350 | } 351 | 352 | curr = Array.isArray(curr[levels[ndx]]) ? curr[levels[ndx]][index] : curr[levels[ndx]]; 353 | } while (ndx++ < levels.length && curr) 354 | } 355 | 356 | return val 357 | } 358 | 359 | /** 360 | * resolves any custom query values available for the given operation and path 361 | * @function lookupCustomQueryValue 362 | * @memberof processing 363 | * @instance 364 | * @param {number} code response status code being evaluated 365 | * @param {string} op the operation method being processed 366 | * @param {string} path API path being processed 367 | * @param {object} options for use in looking up the custom value 368 | * @return {any} 369 | */ 370 | function lookupCustomQueryValue(code, op, path, options, index) { 371 | var levels = [path, op, code]; 372 | var val = undefined; 373 | 374 | if (options.customValues) { 375 | var curr = options.customValues; 376 | 377 | var ndx = 0 378 | do { 379 | if (curr.query !== undefined) { 380 | val = curr.query 381 | } 382 | 383 | curr = Array.isArray(curr[levels[ndx]]) ? curr[levels[ndx]][index] : curr[levels[ndx]]; 384 | } while (ndx++ < levels.length && curr) 385 | } 386 | 387 | return val 388 | } 389 | 390 | /** 391 | * Determines the request headers for a transaction 392 | * @function processHeaders 393 | * @memberof processing 394 | * @instance 395 | * @param {string} responseCode response code being processed 396 | * @param {object} op parent operation object 397 | * @param {object} path parent path object 398 | * @param {object} top global properties 399 | * @param {object} options for use in processing headers 400 | * @return {object} 401 | */ 402 | function processHeaders(responseCode, op, path, top, options, index) { 403 | var headers = {}; 404 | var levels = [path.path, op.method, responseCode] 405 | 406 | // handle consumes 407 | if (options.consumes) { // a specific content-type was targeted 408 | if (op.definitionFullyResolved.consumes) { 409 | // this operation overrides global consumes, and does contain the requested content-type 410 | if (op.definitionFullyResolved.consumes.indexOf(options.consumes) != -1) { 411 | headers['Content-Type'] = options.consumes; 412 | } else { // we will just use the first one because this doesn't match 413 | headers['Content-Type'] = op.definitionFullyResolved.consumes[0]; 414 | } 415 | 416 | } else if (top.consumes.length > 0 && top.consumes.indexOf(options.consumes) 417 | != -1) { 418 | headers['Content-Type'] = options.consumes; 419 | } else if (top.consumes.length === 1) { // unless there is a singular different global consumes, just use that one 420 | headers['Content-Type'] = top.consumes[0]; 421 | } 422 | } else if (op.definitionFullyResolved.consumes) { // we will just use the first one if none are specified 423 | headers['Content-Type'] = op.definitionFullyResolved.consumes[0]; 424 | } else if (top.consumes.length > 0) { 425 | headers['Content-Type'] = top.consumes[0]; 426 | } 427 | 428 | // handle produces 429 | if (options.produces) { 430 | if (op.definitionFullyResolved.produces) { 431 | if (op.definitionFullyResolved.produces.indexOf(options.produces) != -1) { 432 | headers['Accept'] = options.produces; 433 | } else { 434 | headers['Accept'] = op.definitionFullyResolved.produces[0]; 435 | } 436 | 437 | } else if (top.produces.length > 0 && top.produces.indexOf(options.produces) 438 | != -1) { 439 | headers['Accept'] = options.produces; 440 | } else if (top.produces.length === 1) { 441 | headers['Accept'] = top.produces[0]; 442 | } 443 | } else if (op.definitionFullyResolved.produces) { 444 | headers['Accept'] = op.definitionFullyResolved.produces[0]; 445 | } else if (top.produces.length > 0) { 446 | headers['Accept'] = top.produces[0]; 447 | } 448 | 449 | // check custom request values for any desired headers; cascading merge 450 | if (options.customValues) { 451 | var curr = options.customValues 452 | 453 | var ndx = 0 454 | do { 455 | if (curr['header'] !== undefined) { 456 | headers = merge2(curr['header'], headers) 457 | } 458 | 459 | curr = Array.isArray(curr[levels[ndx]]) ? curr[levels[ndx]][index] : curr[levels[ndx]]; 460 | } while (ndx++ < levels.length && curr !== undefined) 461 | } 462 | 463 | return headers 464 | } 465 | 466 | /** 467 | * ExpectedResponse is the expected response generated based on the API spec 468 | * @typedef {object} ExpectedResponse 469 | * @memberof processing 470 | * @property {number} statusCode expected response status code 471 | * @property {boolean} custom indicates if the value was a custom injection 472 | * @property {object} res expected response body, if applicable; can be a generated sample 473 | */ 474 | 475 | /** 476 | * Processes a sway Response for use in a test 477 | * @function processResponse 478 | * @memberof processing 479 | * @instance 480 | * @param {object} res sway Response object being processed 481 | * @param {string} op the operation method being processed 482 | * @param {string} path API path being processed 483 | * @param {object} options options to use during processing 484 | * @return {processing.ExpectedResponse} 485 | */ 486 | function processResponse(res, op, path, options, index) { 487 | var expected = { 488 | 'statusCode': res.statusCode, 489 | 'custom': false 490 | } 491 | 492 | if (options.customValues 493 | && options.customValues[path] 494 | && options.customValues[path][op] 495 | && options.customValues[path][op][res.statusCode]) { 496 | 497 | var statusCodeOptions = options.customValues[path][op][res.statusCode]; 498 | if (Array.isArray(statusCodeOptions)) { 499 | statusCodeOptions = statusCodeOptions[index]; 500 | } 501 | 502 | if (statusCodeOptions.response) { 503 | 504 | expected['res'] = statusCodeOptions.response 505 | expected.custom = true; 506 | 507 | return expected 508 | } 509 | } 510 | 511 | var res = options.samples ? res.getSample() 512 | : res.definitionFullyResolved.schema 513 | if (res !== undefined) { 514 | expected['res'] = res 515 | } 516 | 517 | return expected 518 | } 519 | 520 | /** 521 | * replaces the path paremeter in the given URL path with a sample value 522 | * @function pathify 523 | * @memberof processing 524 | * @instance 525 | * @param {string} path URL path to be pathified 526 | * @param {object} param sway Parameter object to use in pathify 527 | * @param {(string|number)} value a custom value to use in the pathify 528 | * @return {string} 529 | */ 530 | function pathify(path, param, value) { 531 | var regex = new RegExp('(\/.*){' + param.name + '}(\/.*)*', 'g') 532 | var sample = value !== undefined ? value : param.example !== undefined ? param.example : param.getSample() 533 | if (isNumberType(param.definition.type)) { 534 | // prevent negative sample values for numbers 535 | while (sample < 0) { 536 | sample = param.getSample() 537 | } 538 | } 539 | 540 | return path.replace(regex, '$1' + sample + '$2').replace(/ /g, ''); 541 | } 542 | 543 | /** 544 | * evaluates if the given type is an OpenAPI number type or not 545 | * @function isNumberType 546 | * @memberof processing 547 | * @instance 548 | * @param {string} type type to be evaluated 549 | * @return {boolean} 550 | */ 551 | function isNumberType(type) { 552 | return (type === 'integer' || type === 'float' || type === 'long' || type 553 | === 'double') 554 | } 555 | 556 | /** 557 | * Determines the proper type for the Query parameter 558 | * @function determineQueryType 559 | * @memberof processing 560 | * @instance 561 | * @param {object} param sway Parameter object to investigate 562 | * @return {string} 563 | */ 564 | function determineQueryType(param) { 565 | var val = param.name; // default to the name if all else fails 566 | 567 | if (param.definitionFullyResolved.type === 'array') { 568 | val = param.definitionFullyResolved.items.type + '[]' 569 | } else { 570 | val = param.definitionFullyResolved.type 571 | } 572 | return '{' + val + '}' 573 | } 574 | 575 | /** 576 | * Used to replace newlines with a space in each response.description 577 | * @function replaceNewlines 578 | * @memberof processing 579 | * @instance 580 | * @param {string} str string to replace newlines with spaces 581 | * @return {string} 582 | */ 583 | function replaceNewlines(str) { 584 | return str.replace(/\r?\n|\r/g, " ") 585 | } 586 | 587 | /** 588 | * Used to escape single quotes in each response.description 589 | * @function escapeSingleQuotes 590 | * @memberof processing 591 | * @instance 592 | * @param {string} str string to escape single quotes 593 | * @return {string} 594 | */ 595 | function escapeSingleQuotes(str) { 596 | return str.replace(/'/g, "\\'") 597 | } 598 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | module.exports = { 18 | merge2: merge2 19 | } 20 | 21 | /** 22 | * merges two objects, with obj1 taking override precedent 23 | * @function merge2 24 | * @instance 25 | * @param {object} obj1 dominant object to merge with 26 | * @param {object} obj2 subordinate object to merge with 27 | * @return {object} 28 | */ 29 | function merge2(obj1, obj2) { 30 | if (!obj1) { 31 | return obj2; 32 | } else if (!obj2) { 33 | return obj1; 34 | } 35 | 36 | var result = obj2 37 | 38 | Object.keys(obj1).forEach(function (key, ndx, arr) { 39 | result[key] = obj1[key]; 40 | }); 41 | 42 | return result 43 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oatts", 3 | "version": "1.6.0", 4 | "description": "An Open API Test Template Generator", 5 | "keywords": [ 6 | "Swagger", 7 | "OpenAPI", 8 | "Test", 9 | "API" 10 | ], 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/google/oatts.git" 14 | }, 15 | "main": "index.js", 16 | "bin": { 17 | "oatts": "./bin/cli.js" 18 | }, 19 | "scripts": { 20 | "gen-docs": "./node_modules/.bin/jsdoc -c jsdoc.config.json .", 21 | "test": "mocha --recursive" 22 | }, 23 | "author": "Noah Dietz", 24 | "license": "Apache-2.0", 25 | "dependencies": { 26 | "commander": "^2.20.0", 27 | "handlebars": "^4.1.2", 28 | "sway": "^2.0.6" 29 | }, 30 | "devDependencies": { 31 | "chai": "^3.5.0", 32 | "docdash": "^0.4.0", 33 | "jsdoc": "^3.6.3", 34 | "mocha": "^5.2.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /templates/operationLevel.handlebars: -------------------------------------------------------------------------------- 1 | describe('{{operationLevelDescription}}', function() { 2 | {{#each operationLevelTests}} 3 | {{this}}{{#unless @last}} 4 | 5 | {{/unless}} 6 | {{/each}} 7 | 8 | }); -------------------------------------------------------------------------------- /templates/pathLevel.handlebars: -------------------------------------------------------------------------------- 1 | describe('{{pathLevelDescription}}', function() { 2 | {{#each tests}} 3 | {{this}}{{#unless @last}} 4 | 5 | {{/unless}} 6 | {{/each}} 7 | 8 | }); -------------------------------------------------------------------------------- /templates/topLevel.handlebars: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var mocha = require('mocha'); 3 | var chakram = require('chakram'); 4 | var request = chakram.request; 5 | var expect = chakram.expect; 6 | 7 | {{test}} -------------------------------------------------------------------------------- /templates/transactionLevel.handlebars: -------------------------------------------------------------------------------- 1 | it('{{testLevelDescription}}', function() { 2 | var response = request('{{op}}', '{{scheme}}://{{host}}{{path}}', { {{#if (notEmptyObject query)}} 3 | 'qs': {{json query}},{{/if}}{{#if (notEmptyObject body)}} 4 | 'body': {{json body}},{{/if}}{{#if (notEmptyObject formData)}} 5 | 'formData': {{json formData}},{{/if}}{{#if (notEmptyObject headers)}} 6 | 'headers': {{json headers}},{{/if}} 7 | 'time': true 8 | }); 9 | 10 | {{#if (isNotDefaultStatusCode expected.statusCode)}} 11 | expect(response).to.have.status({{expected.statusCode}}); 12 | {{/if}} 13 | {{#if expected.res}} 14 | {{#if hasValue}} 15 | expect(response).to.comprise.of.json({{json expected.res}}); 16 | {{else}} 17 | expect(response).to.have.schema({{json expected.res}}); 18 | {{/if}} 19 | {{/if}} 20 | return chakram.wait(); 21 | }); 22 | -------------------------------------------------------------------------------- /test/compile/documents/customTemplates/operationLevel.handlebars: -------------------------------------------------------------------------------- 1 | {{operationLevelDescription}} 2 | {{#each operationLevelTests}} 3 | {{this}}{{#unless @last}} 4 | 5 | {{/unless}} 6 | {{/each}} -------------------------------------------------------------------------------- /test/compile/documents/customTemplates/pathLevel.handlebars: -------------------------------------------------------------------------------- 1 | {{pathLevelDescription}} 2 | {{#each tests}} 3 | {{this}}{{#unless @last}} 4 | 5 | {{/unless}} 6 | {{/each}} -------------------------------------------------------------------------------- /test/compile/documents/customTemplates/topLevel.handlebars: -------------------------------------------------------------------------------- 1 | {{test}} -------------------------------------------------------------------------------- /test/compile/documents/customTemplates/transactionLevel.handlebars: -------------------------------------------------------------------------------- 1 | {{testLevelDescription}} 2 | {{op}} 3 | {{scheme}} 4 | {{host}} 5 | {{path}} 6 | {{json query}} 7 | {{json body}} 8 | {{json formData}} 9 | {{json headers}} 10 | {{expected.statusCode}} 11 | {{json expected.res}} -------------------------------------------------------------------------------- /test/compile/test.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | var expect = require('chai').expect 16 | var compile = require('../../lib/compile.js') 17 | var mocha = require('mocha') 18 | var util = require('util') 19 | var fullProcessed = require('./documents/fullProcessed.json') 20 | 21 | describe('compile', function () { 22 | describe('no options', function () { 23 | it('should compile all processed correctly', function (done) { 24 | try { 25 | var tests = compile(fullProcessed, {}) 26 | 27 | expect(tests).to.not.be.null 28 | tests.forEach(function (test, ndx, arr) { 29 | expect(test).to.have.property('filename') 30 | expect(test).to.have.property('contents') 31 | }); 32 | 33 | done() 34 | } catch (err) { 35 | done(err) 36 | } 37 | }) 38 | 39 | it('should return null', function (done) { 40 | try { 41 | var tests = compile({'tests': []}, {}) 42 | 43 | expect(tests).to.be.null 44 | 45 | done() 46 | } catch (err) { 47 | done(err) 48 | } 49 | }) 50 | }) 51 | 52 | describe('custom templates option', function () { 53 | it('should compile all processed correctly into custom templates', 54 | function (done) { 55 | try { 56 | var tests = compile(fullProcessed, 57 | {'templates': __dirname + '/documents/customTemplates'}) 58 | 59 | expect(tests).to.not.be.null 60 | tests.forEach(function (test, ndx, arr) { 61 | expect(test).to.have.property('filename') 62 | expect(test).to.have.property('contents') 63 | }) 64 | 65 | done() 66 | } catch (err) { 67 | done(err) 68 | } 69 | }) 70 | }) 71 | }) -------------------------------------------------------------------------------- /test/process/documents/customValuesTest.json: -------------------------------------------------------------------------------- 1 | { 2 | "header": { 3 | "GlobalShow": "global", 4 | "PathOverride": "global", 5 | "OpOverrideFromGlobal": "global", 6 | "StatusCodeOverrideFromGlobal": "global" 7 | }, 8 | "path": { 9 | "petId": 0 10 | }, 11 | "/pet/findByStatus": { 12 | "header": { 13 | "PathShow": "path", 14 | "PathOverride": "path", 15 | "OpOverrideFromPath": "path", 16 | "StatusCodeOverrideFromPath": "path" 17 | }, 18 | "get": { 19 | "header": { 20 | "OpShow": "op", 21 | "OpOverrideFromGlobal": "op", 22 | "OpOverrideFromPath": "op", 23 | "StatusCodeOverrideFromOp": "op" 24 | }, 25 | "query": { 26 | "status": [ 27 | "notanenum1", 28 | "notanenum2" 29 | ] 30 | }, 31 | "200": [ 32 | { 33 | "header": { 34 | "StatusCodeShow": "statusCode", 35 | "StatusCodeOverrideFromGlobal": "statusCode", 36 | "StatusCodeOverrideFromPath": "statusCode", 37 | "StatusCodeOverrideFromOp": "statusCode" 38 | }, 39 | "query": { 40 | "status": [ 41 | "notanenum3" 42 | ] 43 | }, 44 | "response": [ 45 | { 46 | "name": "sparky", 47 | "photoUrls": [ 48 | "url1", 49 | "url2", 50 | "url3" 51 | ] 52 | }, 53 | { 54 | "name": "champ", 55 | "photoUrls": [ 56 | "url4", 57 | "url5", 58 | "url6" 59 | ] 60 | } 61 | ] 62 | }, 63 | { 64 | "header": { 65 | "StatusCodeShow": "statusCode", 66 | "StatusCodeOverrideFromGlobal": "statusCode", 67 | "StatusCodeOverrideFromPath": "statusCode", 68 | "StatusCodeOverrideFromOp": "statusCode" 69 | }, 70 | "query": { 71 | "status": [ 72 | "notanenum4" 73 | ] 74 | }, 75 | "response": [ 76 | { 77 | "name": "black", 78 | "photoUrls": [ 79 | "url7", 80 | "url8", 81 | "url9" 82 | ] 83 | }, 84 | { 85 | "name": "red", 86 | "photoUrls": [ 87 | "url10", 88 | "url11", 89 | "url12" 90 | ] 91 | } 92 | ] 93 | } 94 | ], 95 | "400": { 96 | "header": { 97 | "StatusCodeShow": "statusCode", 98 | "StatusCodeOverrideFromGlobal": "statusCode", 99 | "StatusCodeOverrideFromPath": "statusCode", 100 | "StatusCodeOverrideFromOp": "statusCode" 101 | } 102 | } 103 | } 104 | }, 105 | "/pet": { 106 | "post": { 107 | "201": { 108 | "body": { 109 | "body": { 110 | "name": "doggie", 111 | "photoUrls": [ 112 | "url1", 113 | "url2", 114 | "url3" 115 | ] 116 | } 117 | } 118 | } 119 | } 120 | }, 121 | "/pet/{petId}": { 122 | "get": { 123 | "path": { 124 | "petId": 123 125 | }, 126 | "200": { 127 | "path": { 128 | "petId": 321 129 | } 130 | } 131 | }, 132 | "post": { 133 | "405": { 134 | "path": { 135 | "petId": 321 136 | }, 137 | "formData": { 138 | "name": null, 139 | "status": "pending" 140 | } 141 | } 142 | } 143 | } 144 | } -------------------------------------------------------------------------------- /test/process/documents/swagger.yaml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | description: "This is a sample server Petstore server. You can find out more about\ 4 | \ Swagger at http://swagger.io or on irc.freenode.net,\ 5 | \ #swagger. For this sample, you can use the api key \"special-key\" to test\ 6 | \ the authorization filters" 7 | version: "1.0.0" 8 | title: "Swagger Petstore" 9 | termsOfService: "http://helloreverb.com/terms/" 10 | contact: 11 | email: "apiteam@swagger.io" 12 | license: 13 | name: "Apache 2.0" 14 | url: "http://www.apache.org/licenses/LICENSE-2.0.html" 15 | host: "petstore.swagger.io" 16 | basePath: "/v2" 17 | tags: 18 | - name: "pet" 19 | description: "Everything about your Pets" 20 | externalDocs: 21 | description: "Find out more" 22 | url: "http://swagger.io" 23 | - name: "store" 24 | description: "Access to Petstore orders" 25 | - name: "user" 26 | description: "Operations about user" 27 | externalDocs: 28 | description: "Find out more about our store" 29 | url: "http://swagger.io" 30 | schemes: 31 | - "http" 32 | paths: 33 | /pet: 34 | post: 35 | tags: 36 | - "pet" 37 | summary: "Add a new pet to the store" 38 | description: "" 39 | operationId: "addPet" 40 | consumes: 41 | - "application/json" 42 | - "application/xml" 43 | produces: 44 | - "application/xml" 45 | - "application/json" 46 | parameters: 47 | - in: "body" 48 | name: "body" 49 | description: "Pet object that needs to be added to the store" 50 | required: true 51 | schema: 52 | $ref: "#/definitions/Pet" 53 | responses: 54 | 201: 55 | description: "Successfully added pet" 56 | 405: 57 | description: "Invalid input" 58 | put: 59 | tags: 60 | - "pet" 61 | summary: "Update an existing pet" 62 | description: "" 63 | operationId: "updatePet" 64 | consumes: 65 | - "application/json" 66 | - "application/xml" 67 | produces: 68 | - "application/xml" 69 | - "application/json" 70 | parameters: 71 | - in: "body" 72 | name: "body" 73 | description: "Pet object that needs to be added to the store" 74 | required: true 75 | schema: 76 | $ref: "#/definitions/Pet" 77 | responses: 78 | 400: 79 | description: "Invalid ID supplied" 80 | 404: 81 | description: "Pet not found" 82 | 405: 83 | description: "Validation exception" 84 | /pet/findByStatus: 85 | get: 86 | tags: 87 | - "pet" 88 | summary: "Finds Pets by status" 89 | description: "Multiple status values can be provided with comma seperated strings" 90 | operationId: "findPetsByStatus" 91 | produces: 92 | - "application/xml" 93 | - "application/json" 94 | parameters: 95 | - name: "status" 96 | in: "query" 97 | description: "Status values that need to be considered for filter" 98 | required: true 99 | type: "array" 100 | example: ["pending"] 101 | items: 102 | type: "string" 103 | enum: 104 | - "available" 105 | - "pending" 106 | - "sold" 107 | default: "available" 108 | collectionFormat: "multi" 109 | - name: "petName" 110 | in: "query" 111 | description: "petName value that can be considered for filter" 112 | required: false 113 | type: "string" 114 | example: "toto" 115 | responses: 116 | 200: 117 | description: "successful operation" 118 | schema: 119 | type: "array" 120 | items: 121 | $ref: "#/definitions/Pet" 122 | 400: 123 | description: "Invalid status value" 124 | /pet/findByTags: 125 | get: 126 | tags: 127 | - "pet" 128 | summary: "Finds Pets by tags" 129 | description: "Muliple tags can be provided with comma seperated strings. Use\ 130 | \ tag1, tag2, tag3 for testing." 131 | operationId: "findPetsByTags" 132 | produces: 133 | - "application/xml" 134 | - "application/json" 135 | parameters: 136 | - name: "tags" 137 | in: "query" 138 | description: "Tags to filter by" 139 | required: true 140 | type: "array" 141 | items: 142 | type: "string" 143 | collectionFormat: "multi" 144 | responses: 145 | 200: 146 | description: "successful operation" 147 | schema: 148 | type: "array" 149 | items: 150 | $ref: "#/definitions/Pet" 151 | 400: 152 | description: "Invalid tag value" 153 | /pet/{petId}: 154 | parameters: 155 | - name: "petId" 156 | in: "path" 157 | description: "ID of pet to return" 158 | required: true 159 | type: "integer" 160 | format: "int64" 161 | examples: 162 | two: 163 | value: 2 164 | five: 165 | value: 5 166 | get: 167 | tags: 168 | - "pet" 169 | summary: "Find pet by ID" 170 | description: "Returns a single pet" 171 | operationId: "getPetById" 172 | produces: 173 | - "application/xml" 174 | - "application/json" 175 | responses: 176 | 200: 177 | description: "successful operation" 178 | schema: 179 | $ref: "#/definitions/Pet" 180 | 400: 181 | description: "Invalid ID supplied" 182 | 404: 183 | description: "Pet not found" 184 | post: 185 | tags: 186 | - "pet" 187 | summary: "Updates a pet in the store with form data" 188 | description: "" 189 | operationId: "updatePetWithForm" 190 | consumes: 191 | - "application/x-www-form-urlencoded" 192 | produces: 193 | - "application/xml" 194 | - "application/json" 195 | parameters: 196 | - name: "name" 197 | in: "formData" 198 | description: "Updated name of the pet" 199 | required: false 200 | type: "string" 201 | example: "Rantanplan" 202 | - name: "status" 203 | in: "formData" 204 | description: "Updated status of the pet" 205 | required: false 206 | type: "string" 207 | example: "sold" 208 | responses: 209 | 405: 210 | description: "Invalid input" 211 | delete: 212 | tags: 213 | - "pet" 214 | summary: "Deletes a pet" 215 | description: "" 216 | operationId: "deletePet" 217 | produces: 218 | - "application/xml" 219 | - "application/json" 220 | parameters: 221 | - name: "api_key" 222 | in: "header" 223 | description: "" 224 | required: false 225 | type: "string" 226 | default: "" 227 | example: "test_api_key" 228 | responses: 229 | 400: 230 | description: "Invalid pet value" 231 | /pet/{petId}/uploadImage: 232 | post: 233 | tags: 234 | - "pet" 235 | summary: "uploads an image" 236 | description: "" 237 | operationId: "uploadFile" 238 | consumes: 239 | - "multipart/form-data" 240 | produces: 241 | - "application/json" 242 | parameters: 243 | - name: "petId" 244 | in: "path" 245 | description: "ID of pet to update" 246 | required: true 247 | type: "integer" 248 | format: "int64" 249 | example: 5 250 | - name: "additionalMetadata" 251 | in: "formData" 252 | description: "Additional data to pass to server" 253 | required: false 254 | type: "string" 255 | example: null 256 | - name: "file" 257 | in: "formData" 258 | description: "file to upload" 259 | required: false 260 | type: "file" 261 | example: null 262 | responses: 263 | 200: 264 | description: "successful operation" 265 | schema: 266 | $ref: "#/definitions/ApiResponse" 267 | /store/inventory: 268 | get: 269 | tags: 270 | - "store" 271 | summary: "Returns pet inventories by status" 272 | description: "Returns a map of status codes to quantities" 273 | operationId: "getInventory" 274 | produces: 275 | - "application/json" 276 | parameters: [] 277 | responses: 278 | 200: 279 | description: "successful operation" 280 | schema: 281 | type: "object" 282 | additionalProperties: 283 | type: "integer" 284 | format: "int32" 285 | security: 286 | - api_key: [] 287 | /store/order: 288 | post: 289 | tags: 290 | - "store" 291 | summary: "Place an order for a pet" 292 | description: "" 293 | operationId: "placeOrder" 294 | produces: 295 | - "application/xml" 296 | - "application/json" 297 | parameters: 298 | - in: "body" 299 | name: "body" 300 | description: "order placed for purchasing the pet" 301 | required: true 302 | schema: 303 | $ref: "#/definitions/Order" 304 | responses: 305 | 200: 306 | description: "successful operation" 307 | schema: 308 | $ref: "#/definitions/Order" 309 | 400: 310 | description: "Invalid Order" 311 | security: 312 | - api_key: [] 313 | /store/order/{orderId}: 314 | get: 315 | tags: 316 | - "store" 317 | summary: "Find purchase order by ID" 318 | description: "For valid response try integer IDs with value <= 5 or > 10. Other\ 319 | \ values will generated exceptions" 320 | operationId: "getOrderById" 321 | produces: 322 | - "application/xml" 323 | - "application/json" 324 | parameters: 325 | - name: "orderId" 326 | in: "path" 327 | description: "ID of pet that needs to be fetched" 328 | required: true 329 | type: "string" 330 | maximum: 5.0 331 | minimum: 1.0 332 | responses: 333 | 200: 334 | description: "successful operation" 335 | schema: 336 | $ref: "#/definitions/Order" 337 | 400: 338 | description: "Invalid ID supplied" 339 | 404: 340 | description: "Order not found" 341 | security: 342 | - api_key: [] 343 | delete: 344 | tags: 345 | - "store" 346 | summary: "Delete purchase order by ID" 347 | description: "For valid response try integer IDs with value < 1000. Anything\ 348 | \ above 1000 or nonintegers will generate API errors" 349 | operationId: "deleteOrder" 350 | produces: 351 | - "application/xml" 352 | - "application/json" 353 | parameters: 354 | - name: "orderId" 355 | in: "path" 356 | description: "ID of the order that needs to be deleted" 357 | required: true 358 | type: "string" 359 | minimum: 1.0 360 | responses: 361 | 400: 362 | description: "Invalid ID supplied" 363 | 404: 364 | description: "Order not found" 365 | security: 366 | - api_key: [] 367 | /user: 368 | post: 369 | tags: 370 | - "user" 371 | summary: "Create user" 372 | description: "This can only be done by the logged in user." 373 | operationId: "createUser" 374 | produces: 375 | - "application/xml" 376 | - "application/json" 377 | parameters: 378 | - in: "body" 379 | name: "body" 380 | description: "Created user object" 381 | required: true 382 | schema: 383 | $ref: "#/definitions/User" 384 | responses: 385 | default: 386 | description: "successful operation" 387 | security: 388 | - api_key: [] 389 | /user/createWithArray: 390 | post: 391 | tags: 392 | - "user" 393 | summary: "Creates list of users with given input array" 394 | description: "" 395 | operationId: "createUsersWithArrayInput" 396 | produces: 397 | - "application/xml" 398 | - "application/json" 399 | parameters: 400 | - in: "body" 401 | name: "body" 402 | description: "List of user object" 403 | required: true 404 | schema: 405 | type: "array" 406 | items: 407 | $ref: "#/definitions/User" 408 | responses: 409 | default: 410 | description: "successful operation" 411 | security: 412 | - api_key: [] 413 | /user/createWithList: 414 | post: 415 | tags: 416 | - "user" 417 | summary: "Creates list of users with given input array" 418 | description: "" 419 | operationId: "createUsersWithListInput" 420 | produces: 421 | - "application/xml" 422 | - "application/json" 423 | parameters: 424 | - in: "body" 425 | name: "body" 426 | description: "List of user object" 427 | required: true 428 | schema: 429 | type: "array" 430 | items: 431 | $ref: "#/definitions/User" 432 | responses: 433 | default: 434 | description: "successful operation" 435 | security: 436 | - api_key: [] 437 | /user/login: 438 | get: 439 | tags: 440 | - "user" 441 | summary: "Logs user into the system" 442 | description: "" 443 | operationId: "loginUser" 444 | produces: 445 | - "application/xml" 446 | - "application/json" 447 | parameters: 448 | - name: "username" 449 | in: "query" 450 | description: "The user name for login" 451 | required: true 452 | type: "string" 453 | - name: "password" 454 | in: "query" 455 | description: "The password for login in clear text" 456 | required: true 457 | type: "string" 458 | responses: 459 | 200: 460 | description: "successful operation" 461 | schema: 462 | type: "string" 463 | headers: 464 | X-Rate-Limit: 465 | type: "integer" 466 | format: "int32" 467 | description: "calls per hour allowed by the user" 468 | X-Expires-After: 469 | type: "string" 470 | format: "date-time" 471 | description: "date in UTC when toekn expires" 472 | 400: 473 | description: "Invalid username/password supplied" 474 | security: 475 | - api_key: [] 476 | /user/logout: 477 | get: 478 | tags: 479 | - "user" 480 | summary: "Logs out current logged in user session" 481 | description: "" 482 | operationId: "logoutUser" 483 | produces: 484 | - "application/xml" 485 | - "application/json" 486 | parameters: [] 487 | responses: 488 | default: 489 | description: "successful operation" 490 | security: 491 | - api_key: [] 492 | /user/{username}: 493 | get: 494 | tags: 495 | - "user" 496 | summary: "Get user by user name" 497 | description: "" 498 | operationId: "getUserByName" 499 | produces: 500 | - "application/xml" 501 | - "application/json" 502 | parameters: 503 | - name: "username" 504 | in: "path" 505 | description: "The name that needs to be fetched. Use user1 for testing. " 506 | required: true 507 | type: "string" 508 | responses: 509 | 200: 510 | description: "successful operation" 511 | schema: 512 | $ref: "#/definitions/User" 513 | 400: 514 | description: "Invalid username supplied" 515 | 404: 516 | description: "User not found" 517 | security: 518 | - api_key: [] 519 | put: 520 | tags: 521 | - "user" 522 | summary: "Updated user" 523 | description: "This can only be done by the logged in user." 524 | operationId: "updateUser" 525 | produces: 526 | - "application/xml" 527 | - "application/json" 528 | parameters: 529 | - name: "username" 530 | in: "path" 531 | description: "name that need to be deleted" 532 | required: true 533 | type: "string" 534 | - in: "body" 535 | name: "body" 536 | description: "Updated user object" 537 | required: true 538 | schema: 539 | $ref: "#/definitions/User" 540 | responses: 541 | 400: 542 | description: "Invalid user supplied" 543 | 404: 544 | description: "User not found" 545 | security: 546 | - api_key: [] 547 | delete: 548 | tags: 549 | - "user" 550 | summary: "Delete user" 551 | description: "This can only be done by the logged in user." 552 | operationId: "deleteUser" 553 | produces: 554 | - "application/xml" 555 | - "application/json" 556 | parameters: 557 | - name: "username" 558 | in: "path" 559 | description: "The name that needs to be deleted" 560 | required: true 561 | type: "string" 562 | responses: 563 | 400: 564 | description: "Invalid username supplied" 565 | 404: 566 | description: "User not found" 567 | security: 568 | - api_key: [] 569 | securityDefinitions: 570 | petstore_auth: 571 | type: "oauth2" 572 | authorizationUrl: "http://petstore.swagger.io/api/oauth/dialog" 573 | flow: "implicit" 574 | scopes: 575 | write:pets: "modify pets in your account" 576 | read:pets: "read your pets" 577 | api_key: 578 | type: "apiKey" 579 | name: "api_key" 580 | in: "header" 581 | security: 582 | - petstore_auth: 583 | - "read:pets" 584 | - "write:pets" 585 | definitions: 586 | Order: 587 | properties: 588 | id: 589 | type: "integer" 590 | format: "int64" 591 | petId: 592 | type: "integer" 593 | format: "int64" 594 | quantity: 595 | type: "integer" 596 | format: "int32" 597 | shipDate: 598 | type: "string" 599 | format: "date-time" 600 | status: 601 | type: "string" 602 | description: "Order Status" 603 | enum: 604 | - "placed" 605 | - "approved" 606 | - "delivered" 607 | complete: 608 | type: "boolean" 609 | default: false 610 | type: "object" 611 | xml: 612 | name: "Order" 613 | User: 614 | properties: 615 | id: 616 | type: "integer" 617 | format: "int64" 618 | username: 619 | type: "string" 620 | firstName: 621 | type: "string" 622 | lastName: 623 | type: "string" 624 | email: 625 | type: "string" 626 | password: 627 | type: "string" 628 | phone: 629 | type: "string" 630 | userStatus: 631 | type: "integer" 632 | format: "int32" 633 | description: "User Status" 634 | type: "object" 635 | xml: 636 | name: "User" 637 | Category: 638 | properties: 639 | id: 640 | type: "integer" 641 | format: "int64" 642 | name: 643 | type: "string" 644 | type: "object" 645 | xml: 646 | name: "Category" 647 | Tag: 648 | properties: 649 | id: 650 | type: "integer" 651 | format: "int64" 652 | name: 653 | type: "string" 654 | type: "object" 655 | xml: 656 | name: "Tag" 657 | Pet: 658 | required: 659 | - "name" 660 | - "photoUrls" 661 | properties: 662 | id: 663 | type: "integer" 664 | format: "int64" 665 | category: 666 | $ref: "#/definitions/Category" 667 | name: 668 | type: "string" 669 | example: "doggie" 670 | photoUrls: 671 | type: "array" 672 | xml: 673 | name: "photoUrl" 674 | wrapped: true 675 | items: 676 | type: "string" 677 | tags: 678 | type: "array" 679 | xml: 680 | name: "tag" 681 | wrapped: true 682 | items: 683 | $ref: "#/definitions/Tag" 684 | status: 685 | type: "string" 686 | description: "pet status in the store" 687 | enum: 688 | - "available" 689 | - "pending" 690 | - "sold" 691 | type: "object" 692 | xml: 693 | name: "Pet" 694 | ApiResponse: 695 | properties: 696 | code: 697 | type: "integer" 698 | format: "int32" 699 | type: 700 | type: "string" 701 | message: 702 | type: "string" 703 | type: "object" 704 | externalDocs: 705 | description: "Find out more about Swagger" 706 | url: "http://swagger.io" 707 | -------------------------------------------------------------------------------- /test/process/test.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | var expect = require('chai').expect 16 | var process = require('../../lib/process.js') 17 | var mocha = require('mocha') 18 | var util = require('util') 19 | var sway = require('sway') 20 | var specPath = './test/process/documents/swagger.yaml' 21 | 22 | describe('process', function () { 23 | var api; 24 | 25 | before(function (done) { 26 | sway.create({ 'definition': specPath }) 27 | .then(function (spec) { 28 | api = spec; 29 | }) 30 | .then(done, done); 31 | }) 32 | 33 | describe('no options', function () { 34 | it('should process all correctly', function (done) { 35 | try { 36 | var data = process(api, {}) 37 | expect(data).to.not.be.null 38 | expect(data.host).to.equal("petstore.swagger.io", 39 | "host property did not match the spec") 40 | expect(data.scheme).to.equal("http") 41 | expect(data.tests).to.not.be.empty 42 | expect(data.tests.length).to.equal(api.getPaths().length) 43 | done() 44 | } catch (err) { 45 | done(err) 46 | } 47 | }) 48 | }) 49 | 50 | describe('option-based', function () { 51 | it('should process just \'/pet\' correctly', function (done) { 52 | try { 53 | var data = process(api, { 'paths': ['/pet'] }) 54 | expect(data).to.not.be.null 55 | expect(data.host).to.equal("petstore.swagger.io", 56 | "host property did not match the spec") 57 | expect(data.scheme).to.equal("http") 58 | expect(data.consumes).to.be.empty 59 | expect(data.tests.length).to.equal( 60 | 1, "returned tests set was expected to have 1 item, but it had " 61 | + data.tests.length) 62 | done() 63 | } catch (err) { 64 | done(err) 65 | } 66 | }) 67 | 68 | it('should process with option.host correctly', function (done) { 69 | var optionHost = 'test.acme.net' 70 | try { 71 | var data = process(api, { 'host': optionHost }) 72 | expect(data).to.not.be.null 73 | expect(data.host).to.equal( 74 | optionHost, "generated host property expected to be " + optionHost 75 | + " but was " + data.host) 76 | expect(data.scheme).to.equal("http") 77 | expect(data.tests).to.not.be.empty 78 | done() 79 | } catch (err) { 80 | done(err) 81 | } 82 | }) 83 | 84 | it('should process \'/pet/{petId}\' & \'/pet\' with option.samples active correctly', 85 | function (done) { 86 | try { 87 | var data = process(api, 88 | { 'samples': true, 'paths': ['/pet', '/pet/{petId}'] }) 89 | expect(data).to.not.be.null 90 | expect(data.host).to.equal("petstore.swagger.io", 91 | "host property did not match the spec") 92 | expect(data.scheme).to.equal("http") 93 | expect(data.tests.length).to.equal(2) 94 | done() 95 | } catch (err) { 96 | done(err) 97 | } 98 | }) 99 | 100 | it('should process \'/pet\' with first operation consumes/produces', 101 | function (done) { 102 | try { 103 | var data = process(api, { 'samples': true, 'paths': ['/pet'] }) 104 | expect(data).to.not.be.null 105 | expect(data.host).to.equal("petstore.swagger.io", 106 | "host property did not match the spec") 107 | expect(data.scheme).to.equal("http") 108 | expect(data.tests).to.not.be.empty 109 | expect( 110 | data.tests[0].operations[0].transactions[0].headers['Content-Type']).to.equal( 111 | 'application/json') 112 | expect( 113 | data.tests[0].operations[0].transactions[0].headers['Accept']).to.equal( 114 | 'application/xml') 115 | done() 116 | } catch (err) { 117 | done(err) 118 | } 119 | }) 120 | 121 | it('should process \'/pet/findByStatus\' correctly with customValues headers, query param, & response body', 122 | function (done) { 123 | var expectedHeaders = { 124 | GlobalShow: 'global', 125 | PathOverride: 'path', 126 | OpOverrideFromGlobal: 'op', 127 | PathShow: 'path', 128 | OpOverrideFromPath: 'op', 129 | OpShow: 'op', 130 | Accept: 'application/xml', 131 | StatusCodeShow: "statusCode", 132 | StatusCodeOverrideFromGlobal: "statusCode", 133 | StatusCodeOverrideFromPath: "statusCode", 134 | StatusCodeOverrideFromOp: "statusCode" 135 | } 136 | 137 | try { 138 | var customVals = require('./documents/customValuesTest.json') 139 | var data = process(api, { 140 | 'customValues': customVals, 141 | 'paths': ['/pet/findByStatus'] 142 | }) 143 | 144 | expect(data).to.not.be.null 145 | expect(data.host).to.equal("petstore.swagger.io", 146 | "host property did not match the spec") 147 | expect(data.scheme).to.equal("http") 148 | expect(data.tests).to.not.be.empty 149 | data.tests[0].operations[0].transactions.forEach( 150 | function (item, ndx, arr) { 151 | expect(item.headers).to.deep.equal(expectedHeaders) 152 | }) 153 | 154 | expect( 155 | data.tests[0].operations[0].transactions[0].query).to.deep.equal( 156 | customVals['/pet/findByStatus']['get']['200'][0]['query']) 157 | expect( 158 | data.tests[0].operations[0].transactions[0].expected.res).to.deep.equal( 159 | customVals['/pet/findByStatus']['get']['200'][0]['response']) 160 | expect( 161 | data.tests[0].operations[0].transactions[1].query).to.deep.equal( 162 | customVals['/pet/findByStatus']['get']['200'][1]['query']) 163 | expect( 164 | data.tests[0].operations[0].transactions[1].expected.res).to.deep.equal( 165 | customVals['/pet/findByStatus']['get']['200'][1]['response']) 166 | expect( 167 | data.tests[0].operations[0].transactions[2].query).to.deep.equal( 168 | customVals['/pet/findByStatus']['get']['query']) 169 | 170 | done() 171 | } catch (err) { 172 | done(err) 173 | } 174 | }) 175 | 176 | it('should process \'/pet/findByStatus\' correctly with in-line example query param, with explicit exclusion', 177 | function (done) { 178 | try { 179 | var data = process(api, { 180 | 'customValues': { 181 | '/pet/findByStatus': { 182 | 'query': { 183 | petName: 'some other name' 184 | }, 185 | 'get': { 186 | 'query': { 187 | petName: 'your pet name here <3' 188 | } 189 | } 190 | } 191 | }, 192 | 'paths': ['/pet/findByStatus'] 193 | }) 194 | 195 | expect(data).to.not.be.null 196 | expect(data.host).to.equal("petstore.swagger.io", 197 | "host property did not match the spec") 198 | expect(data.scheme).to.equal("http") 199 | expect(data.tests).to.not.be.empty 200 | expect( 201 | data.tests[0].operations[0].transactions[0].query).to.deep.equal( 202 | { petName: 'your pet name here <3' }) 203 | done() 204 | } catch (err) { 205 | done(err) 206 | } 207 | }) 208 | 209 | it('should process \'/pet/findByStatus\' correctly with in-line example query param', 210 | function (done) { 211 | try { 212 | var data = process(api, { 213 | 'paths': ['/pet/findByStatus'] 214 | }) 215 | 216 | expect(data).to.not.be.null 217 | expect(data.host).to.equal("petstore.swagger.io", 218 | "host property did not match the spec") 219 | expect(data.scheme).to.equal("http") 220 | expect(data.tests).to.not.be.empty 221 | expect( 222 | data.tests[0].operations[0].transactions[0].query).to.deep.equal( 223 | { status: ['pending'], petName: 'toto' }) 224 | done() 225 | } catch (err) { 226 | done(err) 227 | } 228 | }) 229 | 230 | it('should process \'/pet/{petId}/uploadImage\' correctly with in-line null example formData param', 231 | function (done) { 232 | try { 233 | var data = process(api, { 234 | 'paths': ['/pet/{petId}/uploadImage'] 235 | }) 236 | 237 | expect(data).to.not.be.null 238 | expect(data.host).to.equal("petstore.swagger.io", 239 | "host property did not match the spec") 240 | expect(data.scheme).to.equal("http") 241 | expect(data.tests).to.not.be.empty 242 | expect( 243 | data.tests[0].operations[0].transactions[0].path).to.equal('/v2/pet/' 244 | + 5 + '/uploadImage') 245 | expect( 246 | data.tests[0].operations[0].transactions[0].query).to.deep.equal( 247 | {}) 248 | 249 | done() 250 | } catch (err) { 251 | done(err) 252 | } 253 | }) 254 | 255 | it('should process \'/pet\' correctly with custom body params from a file', 256 | function (done) { 257 | try { 258 | var customVals = require('./documents/customValuesTest.json') 259 | var data = process(api, { 260 | 'customValues': customVals, 261 | 'paths': ['/pet'] 262 | }) 263 | 264 | expect(data).to.not.be.null 265 | expect(data.host).to.equal("petstore.swagger.io", 266 | "host property did not match the spec") 267 | expect(data.scheme).to.equal("http") 268 | expect(data.tests).to.not.be.empty 269 | expect( 270 | data.tests[0].operations[0].transactions[0].body).to.deep.equal( 271 | customVals['/pet']['post']['201']['body']['body']) 272 | 273 | done() 274 | } catch (err) { 275 | done(err) 276 | } 277 | }) 278 | 279 | it('should process \'/pet/{petId}\' correctly with custom path params from a file', 280 | function (done) { 281 | try { 282 | var customVals = require('./documents/customValuesTest.json') 283 | var data = process(api, { 284 | 'customValues': customVals, 285 | 'paths': ['/pet/{petId}'] 286 | }) 287 | 288 | expect(data).to.not.be.null 289 | expect(data.host).to.equal("petstore.swagger.io", 290 | "host property did not match the spec") 291 | expect(data.scheme).to.equal("http") 292 | expect(data.tests).to.not.be.empty 293 | expect( 294 | data.tests[0].operations[0].transactions[0].path).to.equal('/v2/pet/' 295 | + customVals['/pet/{petId}']['get']['200']['path']['petId']) 296 | expect( 297 | data.tests[0].operations[0].transactions[1].path).to.equal('/v2/pet/' 298 | + customVals['/pet/{petId}']['get']['path']['petId']) 299 | expect( 300 | data.tests[0].operations[1].transactions[0].path).to.equal('/v2/pet/' 301 | + customVals['/pet/{petId}']['post']['405']['path']['petId']) 302 | expect( 303 | data.tests[0].operations[1].transactions[0].formData).to.deep.equal({ status: 'pending' }) 304 | done() 305 | } catch (err) { 306 | done(err) 307 | } 308 | }) 309 | 310 | it('should process \'/pet/{petId}\' with in-line example path and formData param', 311 | function (done) { 312 | try { 313 | var data = process(api, { 314 | 'paths': ['/pet/{petId}'] 315 | }) 316 | 317 | expect(data).to.not.be.null 318 | expect(data.host).to.equal("petstore.swagger.io", 319 | "host property did not match the spec") 320 | expect(data.scheme).to.equal("http") 321 | expect(data.tests).to.not.be.empty 322 | expect( 323 | data.tests[0].operations[0].transactions[0].path).to.equal('/v2/pet/' 324 | + 2) 325 | expect(data.tests[0].operations[1].transactions[0].formData).to.deep.equal( 326 | { name: 'Rantanplan', status: 'sold' }) 327 | done() 328 | } catch (err) { 329 | done(err) 330 | } 331 | }) 332 | 333 | it('should process /pet/{petId} correctly with header params', 334 | function (done) { 335 | try { 336 | var data = process(api, { 337 | 'paths': ['/pet/{petId}'] 338 | }) 339 | 340 | expect(data).to.not.be.null 341 | expect(data.tests).to.not.be.empty 342 | expect( 343 | data.tests[0].operations[2].transactions[0].headers).to.have.property( 344 | 'api_key') 345 | 346 | done() 347 | } catch (err) { 348 | done(err) 349 | } 350 | }) 351 | 352 | it('should process /pet/{petId} correctly with in-line example header params', 353 | function (done) { 354 | try { 355 | var data = process(api, { 356 | 'paths': ['/pet/{petId}'] 357 | }) 358 | 359 | expect(data).to.not.be.null 360 | expect(data.tests).to.not.be.empty 361 | expect( 362 | data.tests[0].operations[2].transactions[0].headers).to.have.property( 363 | 'api_key') 364 | expect( 365 | data.tests[0].operations[2].transactions[0].headers['api_key']).to.equal('test_api_key') 366 | done() 367 | } catch (err) { 368 | done(err) 369 | } 370 | }) 371 | 372 | it('should only process /pet 405 tests', function (done) { 373 | try { 374 | var data = process(api, { 375 | 'paths': ['/pet'], 376 | 'statusCodes': ['405'] 377 | }) 378 | 379 | expect(data).to.not.be.null 380 | expect(data.tests).to.not.be.empty 381 | expect(data.tests.length).to.equal(1) 382 | expect(data.tests[0].name).to.equal('pet') 383 | 384 | done() 385 | } catch (err) { 386 | done(err) 387 | } 388 | }) 389 | 390 | it('should not have spaces in path params of type string', function (done) { 391 | try { 392 | var data = process(api, { 393 | 'paths': ['/store/order/{orderId}'], 394 | 'statusCodes': ['200'] 395 | }) 396 | 397 | expect(data).to.not.be.null 398 | expect(data.tests).to.not.be.empty 399 | expect(data.tests[0].operations[0].transactions[0].path.includes( 400 | ' '), 'Actual: ' + data.tests[0].operations[0].transactions[0].path 401 | + ' ').to.be.false; 402 | 403 | done(); 404 | } catch (err) { 405 | done(err) 406 | } 407 | }) 408 | }) 409 | }) 410 | --------------------------------------------------------------------------------