├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── doc ├── bundle-html.png └── logo.png ├── example ├── example.json └── example.yaml ├── requirements.txt ├── setup.py ├── src ├── __init__.py ├── commands │ ├── __init__.py │ ├── bundle.py │ ├── list.py │ ├── resolve.py │ └── scaffold.py ├── main.py ├── models │ ├── __init__.py │ ├── route.py │ └── route_collection.py ├── resources │ ├── __init__.py │ └── template.html └── utils │ ├── __init__.py │ ├── export.py │ ├── file_control.py │ ├── repository.py │ └── resolver.py └── tests ├── __init__.py └── unit ├── __init__.py ├── commands ├── __init__.py ├── bundle │ ├── __init__.py │ ├── resources │ │ ├── expected │ │ │ ├── with-header.yaml │ │ │ └── without-header.yaml │ │ └── inputs │ │ │ ├── file1.json │ │ │ ├── file2.yaml │ │ │ └── template-header.yaml │ └── test_bundle.py ├── resolve │ ├── __init__.py │ ├── resources │ │ ├── expected │ │ │ ├── duplicated-spec.yaml │ │ │ └── find-a-spec.yaml │ │ └── inputs │ │ │ ├── duplicated-spec-1.yaml │ │ │ ├── duplicated-spec-2.yaml │ │ │ └── find-a-spec.yaml │ └── test_resolve.py └── scaffold │ ├── __init__.py │ ├── resources │ ├── expected-default-output.json │ └── expected-multiple-response-output.json │ └── test_scaffold.py └── utils ├── __init__.py ├── file_control ├── __init__.py ├── resources │ ├── file.json │ ├── file.txt │ ├── file.yaml │ └── file.yml └── test_file_control.py ├── repository ├── __init__.py ├── resources │ ├── multi-file │ │ ├── file1.json │ │ └── file2.yaml │ ├── sample.yaml │ ├── with-path-ref.yaml │ └── with-path-referred.yaml └── test_repository.py └── resolver ├── __init__.py ├── resources ├── expected │ ├── escape_characters.yaml │ ├── local_components.yaml │ ├── nested_reference.yaml │ ├── remote_components.yaml │ └── remote_file.yaml └── inputs │ ├── escape_characters.yaml │ ├── local_components.yaml │ ├── nested_reference.yaml │ ├── remote_components.yaml │ ├── remote_components_referred.yaml │ ├── remote_file.yaml │ └── remote_file_referred.yaml └── test_resolver_resolve.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | 4 | ######################################################### 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | target/ 75 | 76 | # Jupyter Notebook 77 | .ipynb_checkpoints 78 | 79 | # pyenv 80 | .python-version 81 | 82 | # celery beat schedule file 83 | celerybeat-schedule 84 | 85 | # SageMath parsed files 86 | *.sage.py 87 | 88 | # Environments 89 | .env 90 | .venv 91 | env/ 92 | venv/ 93 | ENV/ 94 | env.bak/ 95 | venv.bak/ 96 | 97 | # Spyder project settings 98 | .spyderproject 99 | .spyproject 100 | 101 | # Rope project settings 102 | .ropeproject 103 | 104 | # mkdocs documentation 105 | /site 106 | 107 | # mypy 108 | .mypy_cache/ 109 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | os: 3 | - linux 4 | python: 5 | - '2.7' 6 | - '3.4' 7 | - '3.5' 8 | - '3.6' 9 | - '3.7' 10 | - '3.8-dev' 11 | install: 12 | - pip install -r requirements.txt 13 | script: 14 | - python -m unittest discover -s tests/unit -p "test_*.py" 15 | - python setup.py install 16 | - openapi-cli-tool list `find ./example` 17 | - openapi-cli-tool bundle -t html `find ./example` 18 | deploy: 19 | provider: pypi 20 | distributions: sdist bdist_wheel 21 | user: hakopako 22 | password: 23 | secure: XHAZIK0o9WRfkZ3SIUrSStB6QOvSZFXccuYXD/GUEJrbtSlgxaaOQZWlpn1hr/e2ZtggEduZMsnHaF63A76XZBL5rA8wukK1sjIZ5+ERX9cPlmswTJdfFayf2QvfHlSYEuW479LVRy5ob2ZL/KvtNO5PQcOoqSGBfbOiukfMGPgn0kgpW27QKRZMnF45sMTwpCfSOpAwKTeRMtbiM7Pj7criqEw4v1NdTk/HljTTJNJUHGhs8xjFsvj5C07aIZTTJwAhnwsKomnYpV948n8zoQNyuyZVp+dmcXV8VIdWBGqRc6bA8zguKcc32CdwOnrMhMmXWIktM0TOf4afm44S47nLwZym5xcjug+w8j6sLhthu7z2U3/HbM18Zm+INFTdMZH4l5jCx9sG6k9TyoWgU04BdM6rrild+7PVL9108WPIjYXWsbsrD8BsF6TGUts0kOiaqoDAvLSv8lQmRIHnYyhoGvY9zpAj3xNdq6g7QlcP0Q7Pq39pXrcSZLKGM00d9RGCCWSl0JHhfIQSkJNAMcVbWTdOdNq/miRK/hdBcB2eHwGhYqzn1CSiYjIe1eBOOtQYkZbHWyvQS5seVRfbuZGbPzCzEjLFadCg4171ED4/qrS0uyGFs/g4e70e1Yu5FrFR9/FN7S+Hc1x68Gqe0xHN3FFNYOUyq/LaPg2cA6c= 24 | skip_existing: true 25 | on: 26 | tags: true 27 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.3.0 (December 13, 2019) 2 | 3 | - Support $ref individual paths (#18) 4 | - Bug fix (#18) 5 | 6 | 7 | # 0.2.1 (October 30, 2019) 8 | 9 | - Fix TypeError for resovler command #14 10 | 11 | 12 | # 0.2.0 (October 2, 2019) 13 | 14 | - Updated performance 15 | - Updated file path arguments to multiple filenames with space separation. 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Aya Shimada 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | test: 3 | python -m unittest discover -v -s tests/unit -p "test_*.py" 4 | 5 | 6 | build: 7 | python setup.py install 8 | 9 | clean: 10 | find ./ -name "*.pyc" -exec rm {} \; 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![openapi-cli-tool](https://raw.githubusercontent.com/hakopako/openapi-cli-tool/master/doc/logo.png) 2 | 3 | 4 | [![Build Status](https://travis-ci.com/hakopako/openapi-cli-tool.svg?branch=master)](https://travis-ci.com/hakopako/openapi-cli-tool) 5 | 6 | 7 | 8 | 9 | # openapi-cli-tool 10 | OpenAPI (Swagger 3.x) CLI Tool. 11 | 12 | - Supports multiple file extensions (json|yaml|yml). 13 | - Can list up defined API paths. 14 | - Display an API specification which is resolved (`$ref`). 15 | - Bundle multi-file into one (output to json|yaml|html). 16 | - OAS interactive scaffolding. 17 | 18 | # Requirements 19 | 20 | Python 2.7, 3.4 <=. 21 | 22 | # Installation 23 | 24 | With pip: 25 | 26 | ```bash 27 | $ pip install openapi-cli-tool 28 | ``` 29 | Manually: 30 | Clone the repository and execute the Python installation command on your machine. 31 | 32 | ```bash 33 | $ pip -r requirements.txt install 34 | $ python setup.py install 35 | ``` 36 | 37 | Then `openapi-cli-tool` command is installed. 38 | 39 | # Usage 40 | 41 | ``` 42 | $ openapi-cli-tool --help 43 | Usage: openapi-cli-tool [OPTIONS] COMMAND [ARGS]... 44 | 45 | Options: 46 | --help Show this message and exit. 47 | 48 | Commands: 49 | bundle Bundle multiple files into one. 50 | list List up API paths in a file or directory. 51 | resolve Display `$ref` resolved API specification. 52 | scaffold Interactively create a simple OpenAPI Specification. 53 | ``` 54 | 55 | ## Bundle 56 | 57 | Bundle multi-file specifications into one, regardless of file extension (json|yaml|yml). 58 | 59 | ``` 60 | $ openapi-cli-tool bundle --help 61 | Usage: openapi-cli-tool bundle [OPTIONS] FILE_PATHS 62 | 63 | Bundle multiple files into one. 64 | 65 | Options: 66 | -f, --file TEXT Load common objects such as info and servers from a 67 | specific file. Default is a file which is the top of list 68 | command result. 69 | -t, --type TEXT Export data type. {json|yaml|html} [default: json] 70 | --help Show this message and exit. 71 | ``` 72 | 73 | example: 74 | ``` 75 | $ openapi-cli-tool bundle -t html file1.json file2.yaml` > ./specification.html 76 | ``` 77 | 78 | In the html file, an unpkg version of [swagger-ui](https://github.com/swagger-api/swagger-ui) is embedded. Rendered screenshot below: 79 | 80 | 81 | ![bundle-html-img](https://raw.githubusercontent.com/hakopako/openapi-cli-tool/master/doc/bundle-html.png) 82 | 83 | 84 | ## List 85 | 86 | List up API paths from a file/directory regardless of the file extension (json|yaml|yml). 87 | 88 | ```bash 89 | $ openapi-cli-tool list `find ./spec` 90 | 91 | Method Path File 92 | -------- --------- ------------------------------------------ 93 | PUT /avatar ./tests/resources/spec/sample.yml 94 | GET /follwers ./tests/resources/spec/folder1/sample2.yaml 95 | POST /follwers ./tests/resources/spec/folder1/sample2.yaml 96 | PUT /follwers ./tests/resources/spec/folder1/sample2.yaml 97 | POST /pets ./tests/resources/spec/sample.yml 98 | GET /posts ./tests/resources/spec/folder1/sample.json 99 | POST /posts ./tests/resources/spec/folder1/sample.json 100 | GET /users ./tests/resources/spec/folder1/sample.json 101 | POST /users ./tests/resources/spec/folder1/sample.json 102 | ``` 103 | 104 | 105 | ## Resolve 106 | 107 | Display an API specification which is resolved from a multi-file API specification via $ref pointers. 108 | 109 | ``` 110 | Usage: openapi-cli-tool resolve [OPTIONS] METHOD PATH FILE_PATHS 111 | 112 | Display `$ref` resolved API specification. 113 | 114 | Options: 115 | -t, --type TEXT Export data type. {json|yaml} [default: json] 116 | --help Show this message and exit. 117 | ``` 118 | 119 | example: 120 | ```bash 121 | $ openapi-cli-tool resolve post /cats `find ./tests/resources/spec` 122 | ``` 123 | 124 | 125 | ## Scaffold 126 | 127 | Interactively input information of your API. 128 | A simple OpenAPI Specification is generated from your prompt. 129 | 130 | ```bash 131 | $ openapi-cli-tool scaffold 132 | 133 | Please enter title [""]: sample 134 | Please enter version [v1.0]: 135 | Please enter license [Apache 2.0]: 136 | Please enter server url [http://example.com]: 137 | Please enter path [/]: /example 138 | Please enter method for /example [get|post|put|delete|head|option|trace]: get 139 | Please enter description for get /example [""]: sample get endpoint 140 | Please enter response code for get /example [200]: 141 | ``` 142 | -------------------------------------------------------------------------------- /doc/bundle-html.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakopako/openapi-cli-tool/446f428f0d0c36842bab405e7efef65a9a713b90/doc/bundle-html.png -------------------------------------------------------------------------------- /doc/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakopako/openapi-cli-tool/446f428f0d0c36842bab405e7efef65a9a713b90/doc/logo.png -------------------------------------------------------------------------------- /example/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "version": "v1.0", 4 | "license": { 5 | "name": "Apache 2.0" 6 | }, 7 | "title": "" 8 | }, 9 | "paths": { 10 | "/users": { 11 | "post": { 12 | "description": "", 13 | "responses": { 14 | "200": { 15 | "content": { 16 | "application/json": { 17 | "schema": {} 18 | } 19 | }, 20 | "description": "succeed to register a new user" 21 | }, 22 | "400": { 23 | "content": { 24 | "application/json": { 25 | "schema": {} 26 | } 27 | }, 28 | "description": "failed to register a new user" 29 | } 30 | } 31 | }, 32 | "get": { 33 | "description": "", 34 | "responses": { 35 | "200": { 36 | "content": { 37 | "application/json": { 38 | "schema": {} 39 | } 40 | }, 41 | "description": "succeed to return all users info" 42 | }, 43 | "400": { 44 | "content": { 45 | "application/json": { 46 | "schema": {} 47 | } 48 | }, 49 | "description": "failed to return all users infon" 50 | } 51 | } 52 | } 53 | } 54 | }, 55 | "servers": [ 56 | { 57 | "url": "http://example.com" 58 | } 59 | ], 60 | "openapi": "3.0.0" 61 | } 62 | -------------------------------------------------------------------------------- /example/example.yaml: -------------------------------------------------------------------------------- 1 | info: 2 | version: v1.0 3 | license: 4 | name: Apache 2.0 5 | title: '' 6 | paths: 7 | /followers: 8 | post: 9 | description: '' 10 | responses: 11 | '200': 12 | content: 13 | application/json: 14 | schema: 15 | $ref: '#/components/schemas/Follower' 16 | description: succeed to register a new follwer 17 | '400': 18 | content: 19 | application/json: 20 | schema: {} 21 | description: failed to register a new follwer 22 | put: 23 | description: '' 24 | responses: 25 | '200': 26 | content: 27 | application/json: 28 | schema: 29 | $ref: '#/components/schemas/Follower' 30 | description: succeed to update a follwer 31 | '400': 32 | content: 33 | application/json: 34 | schema: {} 35 | description: failed to update a follwer 36 | 37 | servers: 38 | - url: 'http://example.com' 39 | openapi: 3.0.0 40 | 41 | 42 | components: 43 | schemas: 44 | Follower: 45 | type: object 46 | properties: 47 | name: 48 | type: string 49 | fav_number: 50 | type: integer 51 | required: 52 | - name 53 | - fav_number 54 | additionalProperties: false -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | click==7.0 2 | pyyaml==5.1 3 | tabulate==0.8.3 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name='openapi-cli-tool', 5 | description="openapi cli tool", 6 | long_description=open('README.md').read(), 7 | long_description_content_type='text/markdown', 8 | version='0.3.0', 9 | author="Ayaka Shimada", 10 | author_email='aya.a.shimada@gmail.com', 11 | url='https://github.com/hakopako/openapi-cli-tool', 12 | license='MIT', 13 | install_requires=open('requirements.txt').readlines(), 14 | packages=find_packages(exclude=["tests", "tests.*"]), 15 | package_data={'': ['*.html']}, 16 | include_package_data=True, 17 | entry_points={ 18 | 'console_scripts': [ 19 | 'openapi-cli-tool = src.main:main', 20 | ], 21 | }, 22 | zip_safe=False 23 | ) 24 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakopako/openapi-cli-tool/446f428f0d0c36842bab405e7efef65a9a713b90/src/__init__.py -------------------------------------------------------------------------------- /src/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakopako/openapi-cli-tool/446f428f0d0c36842bab405e7efef65a9a713b90/src/commands/__init__.py -------------------------------------------------------------------------------- /src/commands/bundle.py: -------------------------------------------------------------------------------- 1 | from src.utils.resolver import resolver 2 | from src.utils.export import export_json, export_yaml, export_html 3 | from src.utils.repository import Repository 4 | 5 | 6 | def run_bundle(spec_paths, filename=None): 7 | repository = Repository(spec_paths) 8 | collection = repository.routes 9 | if collection.len() == 0 and filename is None: 10 | return {} 11 | elif collection.len() > 0 and filename is None: 12 | header_file = collection.get()[0].file 13 | elif filename is not None: 14 | header_file = filename 15 | try: 16 | data = repository.file_control.load_dict_from_file(header_file) 17 | if isinstance(data, dict): 18 | data['paths'] = {} 19 | data.pop('components', None) 20 | else: 21 | raise Exception('Parse Error: ' + header_file) 22 | except Exception as e: 23 | print(e) 24 | return {} 25 | 26 | for route in collection.get(): 27 | if route.url not in data['paths']: 28 | data['paths'][route.url] = {} 29 | resolved_spec = resolver(route.file, route.spec, repository.file_control) 30 | data['paths'][route.url].update({route.method.lower(): resolved_spec}) 31 | 32 | return data 33 | 34 | 35 | def bundle(spec_paths, type, filename=None): 36 | specs = run_bundle(spec_paths, filename) 37 | if type == 'json': 38 | export_json(specs) 39 | elif type == 'yaml': 40 | export_yaml(specs) 41 | elif type == 'html': 42 | export_html(specs) 43 | -------------------------------------------------------------------------------- /src/commands/list.py: -------------------------------------------------------------------------------- 1 | from src.utils.repository import Repository 2 | from src.utils.export import export_table 3 | 4 | 5 | def list(paths): 6 | repository = Repository(paths) 7 | export_table(repository.routes.to_list(), ['Method', 'Path', 'File']) 8 | -------------------------------------------------------------------------------- /src/commands/resolve.py: -------------------------------------------------------------------------------- 1 | from src.utils.resolver import resolver 2 | from src.utils.export import export_json, export_yaml 3 | from src.utils.repository import Repository 4 | 5 | 6 | def run_resolve(method, path, spec_paths): 7 | repository = Repository(spec_paths) 8 | collection = repository.routes 9 | specs = [resolver(route.file, route.spec, repository.file_control) for route in collection.get() if route.method == method.upper() and route.url == path] 10 | return specs 11 | 12 | 13 | def resolve(method, path, spec_paths, type): 14 | specs = run_resolve(method, path, spec_paths) 15 | if len(specs) == 0: 16 | print("Not found") 17 | if type == 'json': 18 | export_json(specs) 19 | elif type == 'yaml': 20 | export_yaml(specs) 21 | if len(specs) > 1: 22 | print("\nWARNING: multiple specifications found for " + method + ' ' + path) 23 | -------------------------------------------------------------------------------- /src/commands/scaffold.py: -------------------------------------------------------------------------------- 1 | from src.utils.export import export_json 2 | 3 | 4 | data = { 5 | 'openapi': '3.0.0', 6 | 'info': {'title': '', 'version': '', 'license': {'name': ''}}, 7 | 'servers': [{'url': ''}], 8 | 'paths': {} 9 | } 10 | 11 | 12 | def data_input(message, default): 13 | try: 14 | return raw_input(message) or default 15 | except NameError: 16 | return input(message) or default 17 | 18 | 19 | def run_scaffold(): 20 | data['info']['title'] = data_input('Please enter title [""]: ', '') 21 | data['info']['version'] = data_input('Please enter version [v1.0]: ', 'v1.0') 22 | data['info']['license']['name'] = data_input('Please enter license [Apache 2.0]: ', 'Apache 2.0') 23 | data['servers'][0]['url'] = data_input('Please enter server url [http://example.com]: ', 'http://example.com') 24 | 25 | path_name = data_input('Please enter path [/]: ', '/') 26 | data['paths'][path_name] = dict() 27 | 28 | while len(data['paths'][path_name]) == 0 or \ 29 | data_input('Add more request method for %s ? Y/n [n]: ' % path_name, 'n') == 'Y': 30 | path_method = '' 31 | while path_method not in ['get', 'post', 'put', 'delete', 'head', 'option', 'trace']: 32 | path_method = data_input('Please enter method for %s [get|post|put|delete|head|option|trace]: ' % path_name, 33 | '') 34 | path_desc = data_input('Please enter description for %s %s [""]: ' % (path_method, path_name), '') 35 | 36 | data['paths'][path_name][path_method] = { 37 | 'description': path_desc, 38 | 'responses': {} 39 | } 40 | 41 | while len(data['paths'][path_name][path_method]['responses']) == 0 or \ 42 | data_input('Add more response for %s %s ? Y/n [n]: ' % (path_method, path_name), 'n') == 'Y': 43 | response_code = data_input('Please enter response code for %s %s [200]: ' % (path_method, path_name), '200') 44 | response_desc = data_input('Please enter response description for %s: ' % response_code, '') 45 | response_content_type = data_input( 46 | 'Please enter response content-type for %s %s [application/json]: ' % (path_method, path_name), 47 | 'application/json') 48 | 49 | data['paths'][path_name][path_method]['responses'][response_code] = { 50 | 'description': response_desc, 51 | 'content': { 52 | response_content_type: {"schema": {}} 53 | } 54 | } 55 | return data 56 | 57 | 58 | def scaffold(): 59 | result = run_scaffold() 60 | export_json(result) 61 | -------------------------------------------------------------------------------- /src/main.py: -------------------------------------------------------------------------------- 1 | import click 2 | from src.commands.scaffold import scaffold 3 | from src.commands.list import list 4 | from src.commands.resolve import resolve 5 | from src.commands.bundle import bundle 6 | 7 | 8 | def validate_resolve_type(ctx, param, value): 9 | if value in ['json', 'yaml']: 10 | return value 11 | else: 12 | raise click.BadParameter('type need to be json or yaml.') 13 | 14 | 15 | def validate_bundle_type(ctx, param, value): 16 | if value in ['json', 'yaml', 'html']: 17 | return value 18 | else: 19 | raise click.BadParameter('type need to be json, yaml or html.') 20 | 21 | 22 | @click.group() 23 | def main(): 24 | pass 25 | 26 | 27 | @main.command('list', help='List up API paths in a file or directory.') 28 | @click.argument('file_paths', nargs=-1, type=click.Path(exists=True)) 29 | def cmd_list(file_paths): 30 | list(file_paths) 31 | 32 | 33 | @main.command('resolve', help='Display `$ref` resolved API specification.') 34 | @click.argument('method') 35 | @click.argument('path') 36 | @click.argument('file_paths', nargs=-1, type=click.Path(exists=True)) 37 | @click.option('-t', '--type', 'type', default='json', show_default=True, callback=validate_resolve_type, help='Export data type. {json|yaml}') 38 | def cmd_resolve(method, path, file_paths, type): 39 | resolve(method, path, file_paths, type) 40 | 41 | 42 | @main.command('bundle', help='Bundle multiple files into one.') 43 | @click.option('-f', '--file', 'file', help='Load common objects such as info and servers from a specific file. Default is a file which is the top of list command result.') 44 | @click.option('-t', '--type', 'type', default='json', show_default=True, callback=validate_bundle_type, help='Export data type. {json|yaml|html}') 45 | @click.argument('file_paths', nargs=-1, type=click.Path(exists=True)) 46 | def cmd_bundle(file, type, file_paths): 47 | bundle(file_paths, type, file) 48 | 49 | 50 | @main.command('scaffold', help='Interactively create a simple OpenAPI Specification.') 51 | def cmd_scaffold(): 52 | scaffold() 53 | 54 | 55 | if __name__ == '__main__': 56 | main() 57 | -------------------------------------------------------------------------------- /src/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakopako/openapi-cli-tool/446f428f0d0c36842bab405e7efef65a9a713b90/src/models/__init__.py -------------------------------------------------------------------------------- /src/models/route.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class Route: 4 | 5 | def __init__(self, method, url, file, spec={}): 6 | self.method = method 7 | self.url = url 8 | self.file = file 9 | self.spec = spec 10 | 11 | def __repr__(self): 12 | return 'method='+self.method+' url='+self.url+' file='+self.file+' spec='+str(self.spec) -------------------------------------------------------------------------------- /src/models/route_collection.py: -------------------------------------------------------------------------------- 1 | class RouteCollection: 2 | 3 | def __init__(self): 4 | self.collection = [] 5 | 6 | 7 | def to_list(self): 8 | result = [] 9 | for route in self.collection: 10 | result.append([route.method, route.url, route.file]) 11 | return result 12 | 13 | 14 | def add(self, route): 15 | self.collection.append(route) 16 | 17 | 18 | def get(self): 19 | return self.collection 20 | 21 | 22 | def sort(self): 23 | try: 24 | self.collection.sort(lambda x,y: cmp(x.url, y.url), lambda x,y: cmp(x.method, y.method)) 25 | except: 26 | self.collection.sort(key=lambda x: (x.url, x.method)) 27 | 28 | 29 | def len(self): 30 | return len(self.collection) 31 | -------------------------------------------------------------------------------- /src/resources/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakopako/openapi-cli-tool/446f428f0d0c36842bab405e7efef65a9a713b90/src/resources/__init__.py -------------------------------------------------------------------------------- /src/resources/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | ###TITLE### 4 | 5 | 6 | 7 |
8 | 9 | 22 | 23 | -------------------------------------------------------------------------------- /src/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakopako/openapi-cli-tool/446f428f0d0c36842bab405e7efef65a9a713b90/src/utils/__init__.py -------------------------------------------------------------------------------- /src/utils/export.py: -------------------------------------------------------------------------------- 1 | import json 2 | import yaml 3 | from tabulate import tabulate 4 | from src.utils.file_control import load_file 5 | from pkg_resources import resource_filename 6 | 7 | 8 | def export_json(export_data): 9 | result = json.dumps(export_data, indent=2) 10 | print(result) 11 | 12 | 13 | def export_yaml(export_data): 14 | result = yaml.safe_dump(export_data, default_flow_style=False) 15 | print(result) 16 | 17 | 18 | def export_table(export_data, headers): 19 | print(tabulate(export_data, headers=headers)) 20 | 21 | 22 | def export_html(export_data): 23 | result = json.dumps(export_data) 24 | template = load_file(resource_filename('src.resources', 'template.html')) 25 | html = template.replace('###TITLE###', export_data['info']['title']).replace('###SPEC###', result) 26 | print(html) 27 | -------------------------------------------------------------------------------- /src/utils/file_control.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import yaml 4 | from copy import deepcopy 5 | 6 | 7 | def load_file(file_path): 8 | try: 9 | r = open(file_path, 'r') 10 | content = r.read() 11 | r.close() 12 | return content 13 | except: 14 | return '' 15 | 16 | 17 | class FileControl: 18 | 19 | def __init__(self): 20 | self.spec_dict = {} 21 | 22 | def load_dict_from_file(self, file_path): 23 | if file_path in self.spec_dict: 24 | return deepcopy(self.spec_dict[file_path]) 25 | try: 26 | r = open(file_path, 'r') 27 | content = r.read() 28 | r.close() 29 | _, file_extension = os.path.splitext(file_path) 30 | spec = "" 31 | if not os.path.exists(file_path): 32 | raise Exception('File not found.') 33 | if file_extension == '.json': 34 | spec = json.loads(content) 35 | elif file_extension in ['.yaml', '.yml']: 36 | spec = yaml.safe_load(content) 37 | else: 38 | raise Exception('Unknown extension.') 39 | self.spec_dict[file_path] = spec 40 | return spec 41 | except Exception as e: 42 | raise Exception('File Read Error (' + file_path + '): ' + str(e)) 43 | -------------------------------------------------------------------------------- /src/utils/repository.py: -------------------------------------------------------------------------------- 1 | import os 2 | from src.utils.file_control import FileControl 3 | from src.utils.resolver import resolve_once 4 | from src.models.route import Route 5 | from src.models.route_collection import RouteCollection 6 | 7 | 8 | class Repository: 9 | 10 | def __init__(self, file_path, file_control = FileControl()): 11 | self.file_control = file_control 12 | self.routes = self.generate(file_path) 13 | 14 | def _set_from_file(self, collection, file): 15 | content = self.file_control.load_dict_from_file(file) 16 | if 'paths' not in content or len(content['paths']) == 0: 17 | return 18 | for path, spec in content['paths'].items(): 19 | spec = resolve_once(file, spec, self.file_control) 20 | for method in spec: 21 | collection.add( 22 | Route( 23 | method.upper(), 24 | path, 25 | file, 26 | spec[method] 27 | )) 28 | 29 | def generate(self, file_path): 30 | collection = RouteCollection() 31 | files = [p for p in file_path if os.path.isfile(p)] 32 | for file in files: 33 | try: 34 | self._set_from_file(collection, file) 35 | except Exception as e: 36 | raise Exception(e) 37 | collection.sort() 38 | return collection 39 | -------------------------------------------------------------------------------- /src/utils/resolver.py: -------------------------------------------------------------------------------- 1 | import os 2 | from src.utils.file_control import FileControl 3 | 4 | 5 | def _resolve_escape_character(value): 6 | return value.replace("~0", "~").replace("~1", "/") 7 | 8 | 9 | def _find_reference(link, base_file_path, file_control): 10 | if '#/' in link: 11 | file_path, item_path = link.split('#/') 12 | items = item_path.split('/') 13 | else: 14 | file_path, items = link, [] 15 | spec_file = base_file_path if file_path == '' else os.path.join(os.path.dirname(base_file_path), file_path) 16 | try: 17 | spec = file_control.load_dict_from_file(spec_file) 18 | for key in items: 19 | spec = spec[_resolve_escape_character(key)] 20 | return spec, spec_file 21 | except Exception as e: 22 | print('Failed to load reference. file=' + base_file_path + ' $ref=' + link + ' (' + str(e) + ')') 23 | return '', spec_file 24 | 25 | 26 | def resolver(file_path, data, file_control = FileControl()): 27 | if not isinstance(data, dict): 28 | return data 29 | 30 | for key, value in data.items(): 31 | if key == '$ref': 32 | new_value, base_file_path = _find_reference(value, file_path, file_control) 33 | data = resolver(base_file_path, new_value, file_control) 34 | else: 35 | new_value = resolver(file_path, value, file_control) 36 | data[key] = new_value 37 | return data 38 | 39 | 40 | def resolve_once(file_path, data, file_control = FileControl()): 41 | if not isinstance(data, dict): 42 | return data 43 | 44 | if '$ref' in data: 45 | new_value, _ = _find_reference(data['$ref'], file_path, file_control) 46 | return new_value 47 | return data 48 | 49 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from os.path import dirname, abspath, join, sep 3 | src = dirname(dirname(abspath(__file__))) 4 | assert src.split(sep)[-1].lower() == 'src' 5 | sys.path.append(src) 6 | print('src folder appended to path: ', src) 7 | -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakopako/openapi-cli-tool/446f428f0d0c36842bab405e7efef65a9a713b90/tests/unit/__init__.py -------------------------------------------------------------------------------- /tests/unit/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakopako/openapi-cli-tool/446f428f0d0c36842bab405e7efef65a9a713b90/tests/unit/commands/__init__.py -------------------------------------------------------------------------------- /tests/unit/commands/bundle/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakopako/openapi-cli-tool/446f428f0d0c36842bab405e7efef65a9a713b90/tests/unit/commands/bundle/__init__.py -------------------------------------------------------------------------------- /tests/unit/commands/bundle/resources/expected/with-header.yaml: -------------------------------------------------------------------------------- 1 | info: 2 | version: v1.0 3 | license: 4 | name: Apache 2.0 5 | url: '' 6 | title: 'API with Common Header' 7 | paths: 8 | "/followers": 9 | post: 10 | description: '' 11 | responses: 12 | '200': 13 | content: 14 | application/json: 15 | schema: 16 | type: object 17 | properties: 18 | name: 19 | type: string 20 | fav_number: 21 | type: integer 22 | required: 23 | - name 24 | - fav_number 25 | additionalProperties: false 26 | description: succeed to register a new follwer 27 | '400': 28 | content: 29 | application/json: 30 | schema: {} 31 | description: failed to register a new follwer 32 | put: 33 | description: '' 34 | responses: 35 | '200': 36 | content: 37 | application/json: 38 | schema: 39 | type: object 40 | properties: 41 | name: 42 | type: string 43 | fav_number: 44 | type: integer 45 | required: 46 | - name 47 | - fav_number 48 | additionalProperties: false 49 | description: succeed to update a follwer 50 | '400': 51 | content: 52 | application/json: 53 | schema: {} 54 | description: failed to update a follwer 55 | "/users": 56 | post: 57 | description: '' 58 | responses: 59 | '200': 60 | content: 61 | application/json: 62 | schema: {} 63 | description: succeed to register a new user 64 | '400': 65 | content: 66 | application/json: 67 | schema: {} 68 | description: failed to register a new user 69 | get: 70 | description: '' 71 | responses: 72 | '200': 73 | content: 74 | application/json: 75 | schema: {} 76 | description: succeed to return all users info 77 | '400': 78 | content: 79 | application/json: 80 | schema: {} 81 | description: failed to return all users infon 82 | 83 | servers: 84 | - url: http://example.com 85 | - url: http://localhost 86 | openapi: 3.0.0 87 | -------------------------------------------------------------------------------- /tests/unit/commands/bundle/resources/expected/without-header.yaml: -------------------------------------------------------------------------------- 1 | info: 2 | version: v1.0 3 | license: 4 | name: Apache 2.0 5 | title: '' 6 | paths: 7 | "/followers": 8 | post: 9 | description: '' 10 | responses: 11 | '200': 12 | content: 13 | application/json: 14 | schema: 15 | type: object 16 | properties: 17 | name: 18 | type: string 19 | fav_number: 20 | type: integer 21 | required: 22 | - name 23 | - fav_number 24 | additionalProperties: false 25 | description: succeed to register a new follwer 26 | '400': 27 | content: 28 | application/json: 29 | schema: {} 30 | description: failed to register a new follwer 31 | put: 32 | description: '' 33 | responses: 34 | '200': 35 | content: 36 | application/json: 37 | schema: 38 | type: object 39 | properties: 40 | name: 41 | type: string 42 | fav_number: 43 | type: integer 44 | required: 45 | - name 46 | - fav_number 47 | additionalProperties: false 48 | description: succeed to update a follwer 49 | '400': 50 | content: 51 | application/json: 52 | schema: {} 53 | description: failed to update a follwer 54 | "/users": 55 | post: 56 | description: '' 57 | responses: 58 | '200': 59 | content: 60 | application/json: 61 | schema: {} 62 | description: succeed to register a new user 63 | '400': 64 | content: 65 | application/json: 66 | schema: {} 67 | description: failed to register a new user 68 | get: 69 | description: '' 70 | responses: 71 | '200': 72 | content: 73 | application/json: 74 | schema: {} 75 | description: succeed to return all users info 76 | '400': 77 | content: 78 | application/json: 79 | schema: {} 80 | description: failed to return all users infon 81 | 82 | servers: 83 | - url: http://example.com 84 | openapi: 3.0.0 85 | -------------------------------------------------------------------------------- /tests/unit/commands/bundle/resources/inputs/file1.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "version": "v1.0", 4 | "license": { 5 | "name": "Apache 2.0" 6 | }, 7 | "title": "" 8 | }, 9 | "paths": { 10 | "/users": { 11 | "post": { 12 | "description": "", 13 | "responses": { 14 | "200": { 15 | "content": { 16 | "application/json": { 17 | "schema": {} 18 | } 19 | }, 20 | "description": "succeed to register a new user" 21 | }, 22 | "400": { 23 | "content": { 24 | "application/json": { 25 | "schema": {} 26 | } 27 | }, 28 | "description": "failed to register a new user" 29 | } 30 | } 31 | }, 32 | "get": { 33 | "description": "", 34 | "responses": { 35 | "200": { 36 | "content": { 37 | "application/json": { 38 | "schema": {} 39 | } 40 | }, 41 | "description": "succeed to return all users info" 42 | }, 43 | "400": { 44 | "content": { 45 | "application/json": { 46 | "schema": {} 47 | } 48 | }, 49 | "description": "failed to return all users infon" 50 | } 51 | } 52 | } 53 | } 54 | }, 55 | "servers": [ 56 | { 57 | "url": "http://example.com" 58 | } 59 | ], 60 | "openapi": "3.0.0" 61 | } 62 | -------------------------------------------------------------------------------- /tests/unit/commands/bundle/resources/inputs/file2.yaml: -------------------------------------------------------------------------------- 1 | info: 2 | version: v1.0 3 | license: 4 | name: Apache 2.0 5 | title: '' 6 | paths: 7 | /followers: 8 | post: 9 | description: '' 10 | responses: 11 | '200': 12 | content: 13 | application/json: 14 | schema: 15 | $ref: '#/components/schemas/Follower' 16 | description: succeed to register a new follwer 17 | '400': 18 | content: 19 | application/json: 20 | schema: {} 21 | description: failed to register a new follwer 22 | put: 23 | description: '' 24 | responses: 25 | '200': 26 | content: 27 | application/json: 28 | schema: 29 | $ref: '#/components/schemas/Follower' 30 | description: succeed to update a follwer 31 | '400': 32 | content: 33 | application/json: 34 | schema: {} 35 | description: failed to update a follwer 36 | 37 | servers: 38 | - url: 'http://example.com' 39 | openapi: 3.0.0 40 | 41 | 42 | components: 43 | schemas: 44 | Follower: 45 | type: object 46 | properties: 47 | name: 48 | type: string 49 | fav_number: 50 | type: integer 51 | required: 52 | - name 53 | - fav_number 54 | additionalProperties: false -------------------------------------------------------------------------------- /tests/unit/commands/bundle/resources/inputs/template-header.yaml: -------------------------------------------------------------------------------- 1 | info: 2 | version: v1.0 3 | license: 4 | name: Apache 2.0 5 | url: '' 6 | title: API with Common Header 7 | paths: {} 8 | servers: 9 | - url: http://example.com 10 | - url: http://localhost 11 | openapi: 3.0.0 12 | -------------------------------------------------------------------------------- /tests/unit/commands/bundle/test_bundle.py: -------------------------------------------------------------------------------- 1 | import os 2 | import yaml 3 | import unittest 4 | from src.commands.bundle import run_bundle 5 | 6 | 7 | def read_file(file_path): 8 | r = open(file_path, 'r') 9 | content = r.read() 10 | r.close() 11 | return yaml.safe_load(content) 12 | 13 | 14 | class TestBundle(unittest.TestCase): 15 | 16 | def _data_provider(self): 17 | current = os.path.dirname(os.path.abspath(__file__)) 18 | return { 19 | 'spec path without header file': { 20 | 'input': { 21 | 'files': [ 22 | os.path.join(current, 'resources/inputs/file1.json'), 23 | os.path.join(current, 'resources/inputs/file2.yaml'), 24 | ], 25 | 'header': None 26 | }, 27 | 'expected': os.path.join(current, 'resources/expected/without-header.yaml') 28 | }, 29 | 'spec path with header file': { 30 | 'input': { 31 | 'files': [ 32 | os.path.join(current, 'resources/inputs/file1.json'), 33 | os.path.join(current, 'resources/inputs/file2.yaml'), 34 | ], 35 | 'header': os.path.join(current, 'resources/inputs/template-header.yaml') 36 | }, 37 | 'expected': os.path.join(current, 'resources/expected/with-header.yaml') 38 | }, 39 | } 40 | 41 | def test_bundle(self): 42 | for key, value in self._data_provider().items(): 43 | actual = run_bundle(value['input']['files'], value['input']['header']) 44 | expected = read_file(value['expected']) 45 | self.assertEqual(actual, expected, key) 46 | -------------------------------------------------------------------------------- /tests/unit/commands/resolve/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakopako/openapi-cli-tool/446f428f0d0c36842bab405e7efef65a9a713b90/tests/unit/commands/resolve/__init__.py -------------------------------------------------------------------------------- /tests/unit/commands/resolve/resources/expected/duplicated-spec.yaml: -------------------------------------------------------------------------------- 1 | - summary: Add a new pet 2 | requestBody: 3 | description: Optional description in *Markdown* 4 | required: true 5 | content: 6 | application/json: 7 | schema: 8 | type: object 9 | properties: 10 | name: 11 | type: string 12 | fav_number: 13 | type: integer 14 | required: 15 | - name 16 | - fav_number 17 | additionalProperties: false 18 | text/plain: 19 | schema: 20 | type: string 21 | responses: 22 | '201': 23 | description: Created 24 | 25 | - summary: Upload an avatar 26 | requestBody: 27 | content: 28 | image/*: 29 | schema: 30 | type: string 31 | format: binary 32 | multipart/form-data: 33 | schema: 34 | type: object 35 | properties: 36 | filename: 37 | type: array 38 | items: 39 | type: string 40 | format: binary 41 | responses: 42 | '201': 43 | description: Updated 44 | 45 | 46 | -------------------------------------------------------------------------------- /tests/unit/commands/resolve/resources/expected/find-a-spec.yaml: -------------------------------------------------------------------------------- 1 | - summary: Add a new pet 2 | requestBody: 3 | description: Optional description in *Markdown* 4 | required: true 5 | content: 6 | application/json: 7 | schema: 8 | type: object 9 | properties: 10 | name: 11 | type: string 12 | fav_number: 13 | type: integer 14 | required: 15 | - name 16 | - fav_number 17 | additionalProperties: false 18 | text/plain: 19 | schema: 20 | type: string 21 | responses: 22 | '201': 23 | description: Created 24 | 25 | -------------------------------------------------------------------------------- /tests/unit/commands/resolve/resources/inputs/duplicated-spec-1.yaml: -------------------------------------------------------------------------------- 1 | paths: 2 | /pets: 3 | post: 4 | summary: Add a new pet 5 | requestBody: 6 | description: Optional description in *Markdown* 7 | required: true 8 | content: 9 | application/json: 10 | schema: 11 | $ref: '#/components/schemas/Pet' 12 | text/plain: 13 | schema: 14 | type: string 15 | responses: 16 | '201': 17 | description: Created 18 | 19 | components: 20 | schemas: 21 | Pet: 22 | type: object 23 | properties: 24 | name: 25 | type: string 26 | fav_number: 27 | type: integer 28 | required: 29 | - name 30 | - fav_number 31 | additionalProperties: false 32 | 33 | -------------------------------------------------------------------------------- /tests/unit/commands/resolve/resources/inputs/duplicated-spec-2.yaml: -------------------------------------------------------------------------------- 1 | paths: 2 | /pets: 3 | post: 4 | summary: Upload an avatar 5 | requestBody: 6 | content: 7 | image/*: 8 | schema: 9 | type: string 10 | format: binary 11 | multipart/form-data: 12 | schema: 13 | type: object 14 | properties: 15 | filename: 16 | type: array 17 | items: 18 | type: string 19 | format: binary 20 | responses: 21 | '201': 22 | description: Updated 23 | -------------------------------------------------------------------------------- /tests/unit/commands/resolve/resources/inputs/find-a-spec.yaml: -------------------------------------------------------------------------------- 1 | paths: 2 | /pets: 3 | post: 4 | summary: Add a new pet 5 | requestBody: 6 | description: Optional description in *Markdown* 7 | required: true 8 | content: 9 | application/json: 10 | schema: 11 | $ref: '#/components/schemas/Pet' 12 | text/plain: 13 | schema: 14 | type: string 15 | responses: 16 | '201': 17 | description: Created 18 | 19 | /avatar: 20 | put: 21 | summary: Upload an avatar 22 | requestBody: 23 | content: 24 | image/*: 25 | schema: 26 | type: string 27 | format: binary 28 | multipart/form-data: 29 | schema: 30 | type: object 31 | properties: 32 | filename: 33 | type: array 34 | items: 35 | type: string 36 | format: binary 37 | responses: 38 | '201': 39 | description: Updated 40 | 41 | components: 42 | schemas: 43 | Pet: 44 | type: object 45 | properties: 46 | name: 47 | type: string 48 | fav_number: 49 | type: integer 50 | required: 51 | - name 52 | - fav_number 53 | additionalProperties: false 54 | 55 | -------------------------------------------------------------------------------- /tests/unit/commands/resolve/test_resolve.py: -------------------------------------------------------------------------------- 1 | import os 2 | import yaml 3 | import subprocess 4 | import unittest 5 | from src.commands.resolve import run_resolve 6 | 7 | 8 | def read_file(file_path): 9 | r = open(file_path, 'r') 10 | content = r.read() 11 | r.close() 12 | return yaml.safe_load(content) 13 | 14 | 15 | class TestResolve(unittest.TestCase): 16 | 17 | def _data_provider(self): 18 | current = os.path.dirname(os.path.abspath(__file__)) 19 | return { 20 | 'spec not found': { 21 | 'input': { 22 | 'method': 'get', 23 | 'path': '/cats', 24 | 'files': ['./not-exist-spec-path'] 25 | }, 26 | 'expected': [] 27 | }, 28 | 'find a spec': { 29 | 'input': { 30 | 'method': 'post', 31 | 'path': '/pets', 32 | 'files': [os.path.join(current, 'resources/inputs/find-a-spec.yaml')] 33 | }, 34 | 'expected': os.path.join(current, 'resources/expected/find-a-spec.yaml') 35 | }, 36 | 'duplicated spec': { 37 | 'input': { 38 | 'method': 'post', 39 | 'path': '/pets', 40 | 'files': [ 41 | os.path.join(current, 'resources/inputs/duplicated-spec-1.yaml'), 42 | os.path.join(current, 'resources/inputs/duplicated-spec-2.yaml') 43 | ] 44 | }, 45 | 'expected': os.path.join(current, 'resources/expected/duplicated-spec.yaml') 46 | } 47 | } 48 | 49 | def test_resolve(self): 50 | for key, value in self._data_provider().items(): 51 | actual = run_resolve( 52 | value['input']['method'], 53 | value['input']['path'], 54 | value['input']['files'] 55 | ) 56 | if isinstance(value['expected'], str) and os.path.isfile(value['expected']): 57 | expected = read_file(value['expected']) 58 | self.assertEqual(actual, expected, key) 59 | else: 60 | self.assertEqual(actual, value['expected'], key) 61 | -------------------------------------------------------------------------------- /tests/unit/commands/scaffold/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakopako/openapi-cli-tool/446f428f0d0c36842bab405e7efef65a9a713b90/tests/unit/commands/scaffold/__init__.py -------------------------------------------------------------------------------- /tests/unit/commands/scaffold/resources/expected-default-output.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "version": "v1.0", 4 | "license": { 5 | "name": "Apache 2.0" 6 | }, 7 | "title": "" 8 | }, 9 | "paths": { 10 | "/users": { 11 | "get": { 12 | "description": "", 13 | "responses": { 14 | "200": { 15 | "content": { 16 | "application/json": { 17 | "schema": {} 18 | } 19 | }, 20 | "description": "succeed to return all users info" 21 | } 22 | } 23 | } 24 | } 25 | }, 26 | "servers": [ 27 | { 28 | "url": "http://example.com" 29 | } 30 | ], 31 | "openapi": "3.0.0" 32 | } 33 | -------------------------------------------------------------------------------- /tests/unit/commands/scaffold/resources/expected-multiple-response-output.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "version": "v1.0", 4 | "license": { 5 | "name": "Apache 2.0" 6 | }, 7 | "title": "" 8 | }, 9 | "paths": { 10 | "/users": { 11 | "post": { 12 | "description": "", 13 | "responses": { 14 | "200": { 15 | "content": { 16 | "application/json": { 17 | "schema": {} 18 | } 19 | }, 20 | "description": "succeed to register a new user" 21 | }, 22 | "400": { 23 | "content": { 24 | "application/json": { 25 | "schema": {} 26 | } 27 | }, 28 | "description": "failed to register a new user" 29 | } 30 | } 31 | }, 32 | "get": { 33 | "description": "", 34 | "responses": { 35 | "200": { 36 | "content": { 37 | "application/json": { 38 | "schema": {} 39 | } 40 | }, 41 | "description": "succeed to return all users info" 42 | }, 43 | "400": { 44 | "content": { 45 | "application/json": { 46 | "schema": {} 47 | } 48 | }, 49 | "description": "failed to return all users infon" 50 | } 51 | } 52 | } 53 | } 54 | }, 55 | "servers": [ 56 | { 57 | "url": "http://example.com" 58 | } 59 | ], 60 | "openapi": "3.0.0" 61 | } 62 | -------------------------------------------------------------------------------- /tests/unit/commands/scaffold/test_scaffold.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | import sys 4 | import json 5 | 6 | try: 7 | from StringIO import StringIO 8 | except ImportError: 9 | from io import StringIO 10 | from src.commands.scaffold import run_scaffold 11 | 12 | 13 | def read_file(file_path): 14 | r = open(file_path, 'r') 15 | content = r.read() 16 | r.close() 17 | return json.loads(content) 18 | 19 | 20 | class TestScaffold(unittest.TestCase): 21 | 22 | def _data_provider(self): 23 | current = os.path.dirname(os.path.abspath(__file__)) 24 | return { 25 | 'default input': { 26 | 'input': 27 | "\n" + # Please enter title [""]: 28 | "\n" + # Please enter version [v1.0]: 29 | "\n" + # Please enter license [Apache 2.0]: 30 | "\n" + # Please enter server url [http://example.com]: 31 | "/users\n" + # Please enter path [/]: 32 | "get\n" + # Please enter method for /users [get|post|put|delete|head|option|trace] 33 | "\n" + # Please enter description for get /users [""]: 34 | "\n" + # Please enter response code for get /users [200]: 35 | "succeed to return all users info\n" + # Please enter response description for 200: 36 | "application/json\n" + # Please enter response content-type for get /users [application/json]: 37 | "\n" + # Add more response for get /users ? Y/n [n]: 38 | "\n", # Add more request method for /users ? Y/n [n]: 39 | 'expected': os.path.join(current, 'resources/expected-default-output.json') 40 | }, 41 | 'multiple response input': { 42 | 'input': 43 | "\n" + # Please enter title [""]: 44 | "\n" + # Please enter version [v1.0]: 45 | "\n" + # Please enter license [Apache 2.0]: 46 | "\n" + # Please enter server url [http://example.com]: 47 | "/users\n" + # Please enter path [/]: 48 | "get\n" + # Please enter method for /users [get|post|put|delete|head|option|trace] 49 | "\n" + # Please enter description for get /users [""]: 50 | "\n" + # Please enter response code for get /users [200]: 51 | "succeed to return all users info\n" + # Please enter response description for 200: 52 | "application/json\n" + # Please enter response content-type for get /users [application/json]: 53 | "Y\n" + # Add more response for get /users ? Y/n [n]: 54 | "400\n" + # Please enter response code for get /users [200]: 55 | "failed to return all users infon\n" + # Please enter response description for 200: 56 | "application/json\n" + # Please enter response content-type for get /users [application/json]: 57 | "\n" + # Add more response for get /users ? Y/n [n]: 58 | "Y\n" + # Add more request method for /users ? Y/n [n]: 59 | "post\n" + # Please enter method for /users [get|post|put|delete|head|option|trace] 60 | "\n" + # Please enter description for post /users [""]: 61 | "\n" + # Please enter response code for post /users [200]: 62 | "succeed to register a new user\n" + # Please enter response description for 200: 63 | "application/json\n" + # Please enter response content-type for post /users [application/json]: 64 | "Y\n" + # Add more response for %s %s ? Y/n [n]: 65 | "400\n" + # Please enter response code for get /users [200]: 66 | "failed to register a new user\n" + # Please enter response description for 200: 67 | "application/json\n" + # Please enter response content-type for post /users [application/json]: 68 | "\n" + # Add more response for post /users ? Y/n [n]: 69 | "\n", # Add more request method for /users ? Y/n [n]: 70 | 'expected': os.path.join(current, 'resources/expected-multiple-response-output.json') 71 | } 72 | } 73 | 74 | def test_scaffold(self): 75 | original_sysin = sys.stdin 76 | oritinal_sysout = sys.stdout 77 | for key, value in self._data_provider().items(): 78 | sys.stdin = StringIO(value['input']) 79 | sys.stdout = StringIO() 80 | actual = run_scaffold() 81 | expected = read_file(value['expected']) 82 | self.assertEqual(actual, expected, key) 83 | sys.stdin = original_sysin 84 | sys.stdout = oritinal_sysout 85 | -------------------------------------------------------------------------------- /tests/unit/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakopako/openapi-cli-tool/446f428f0d0c36842bab405e7efef65a9a713b90/tests/unit/utils/__init__.py -------------------------------------------------------------------------------- /tests/unit/utils/file_control/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakopako/openapi-cli-tool/446f428f0d0c36842bab405e7efef65a9a713b90/tests/unit/utils/file_control/__init__.py -------------------------------------------------------------------------------- /tests/unit/utils/file_control/resources/file.json: -------------------------------------------------------------------------------- 1 | {"message": "ok"} -------------------------------------------------------------------------------- /tests/unit/utils/file_control/resources/file.txt: -------------------------------------------------------------------------------- 1 | this file should not be imported. -------------------------------------------------------------------------------- /tests/unit/utils/file_control/resources/file.yaml: -------------------------------------------------------------------------------- 1 | message: ok 2 | -------------------------------------------------------------------------------- /tests/unit/utils/file_control/resources/file.yml: -------------------------------------------------------------------------------- 1 | message: ok -------------------------------------------------------------------------------- /tests/unit/utils/file_control/test_file_control.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | from src.utils.file_control import FileControl 4 | 5 | 6 | class TestFileControl(unittest.TestCase): 7 | 8 | def _success_data_provider(self): 9 | current = os.path.dirname(os.path.abspath(__file__)) 10 | return { 11 | 'input yaml extension': { 12 | 'input': os.path.join(current, 'resources/file.yaml'), 13 | 'expected': {'message': 'ok'} 14 | }, 15 | 'input yml extension': { 16 | 'input': os.path.join(current, 'resources/file.yml'), 17 | 'expected': {'message': 'ok'} 18 | }, 19 | 'input json extension': { 20 | 'input': os.path.join(current, 'resources/file.json'), 21 | 'expected': {'message': 'ok'} 22 | } 23 | } 24 | 25 | def _exception_data_provider(self): 26 | current = os.path.dirname(os.path.abspath(__file__)) 27 | return { 28 | 'file not found': { 29 | 'input': './not-exist-spec-path' 30 | }, 31 | 'input txt extension': { 32 | 'input': os.path.join(current, 'resources/file.txt') 33 | } 34 | } 35 | 36 | def test_success_file_control(self): 37 | for key, value in self._success_data_provider().items(): 38 | file_control = FileControl() 39 | actual = file_control.load_dict_from_file(value['input']) 40 | self.assertEqual(actual, value['expected'], key) 41 | 42 | @unittest.skip("need to be fixed") 43 | def test_exception_file_control(self): 44 | for key, value in self._exception_data_provider().items(): 45 | file_control = FileControl() 46 | self.assertRaises(Exception, file_control.load_dict_from_file(value['input'])) 47 | -------------------------------------------------------------------------------- /tests/unit/utils/repository/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakopako/openapi-cli-tool/446f428f0d0c36842bab405e7efef65a9a713b90/tests/unit/utils/repository/__init__.py -------------------------------------------------------------------------------- /tests/unit/utils/repository/resources/multi-file/file1.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "version": "v1.0", 4 | "license": { 5 | "name": "Apache 2.0" 6 | }, 7 | "title": "" 8 | }, 9 | "paths": { 10 | "/users": { 11 | "post": { 12 | "description": "", 13 | "responses": { 14 | "200": { 15 | "content": { 16 | "application/json": { 17 | "schema": {} 18 | } 19 | }, 20 | "description": "succeed to register a new user" 21 | }, 22 | "400": { 23 | "content": { 24 | "application/json": { 25 | "schema": {} 26 | } 27 | }, 28 | "description": "failed to register a new user" 29 | } 30 | } 31 | }, 32 | "get": { 33 | "description": "", 34 | "responses": { 35 | "200": { 36 | "content": { 37 | "application/json": { 38 | "schema": {} 39 | } 40 | }, 41 | "description": "succeed to return all users info" 42 | }, 43 | "400": { 44 | "content": { 45 | "application/json": { 46 | "schema": {} 47 | } 48 | }, 49 | "description": "failed to return all users infon" 50 | } 51 | } 52 | } 53 | }, 54 | "/posts": { 55 | "post": { 56 | "description": "", 57 | "responses": { 58 | "200": { 59 | "content": { 60 | "application/json": { 61 | "schema": {} 62 | } 63 | }, 64 | "description": "succeed to post a new content" 65 | }, 66 | "400": { 67 | "content": { 68 | "application/json": { 69 | "schema": {} 70 | } 71 | }, 72 | "description": "failed to post a new content" 73 | } 74 | } 75 | }, 76 | "get": { 77 | "description": "", 78 | "responses": { 79 | "200": { 80 | "content": { 81 | "application/json": { 82 | "schema": {} 83 | } 84 | }, 85 | "description": "succeed to return all post info" 86 | }, 87 | "400": { 88 | "content": { 89 | "application/json": { 90 | "schema": {} 91 | } 92 | }, 93 | "description": "failed to return all post infon" 94 | } 95 | } 96 | } 97 | } 98 | }, 99 | "servers": [ 100 | { 101 | "url": "http://example.com" 102 | } 103 | ], 104 | "openapi": "3.0.0" 105 | } 106 | -------------------------------------------------------------------------------- /tests/unit/utils/repository/resources/multi-file/file2.yaml: -------------------------------------------------------------------------------- 1 | info: 2 | version: v1.0 3 | license: 4 | name: Apache 2.0 5 | title: '' 6 | paths: 7 | /follwers: 8 | post: 9 | description: '' 10 | responses: 11 | '200': 12 | content: 13 | application/json: 14 | schema: {} 15 | description: succeed to register a new follwer 16 | '400': 17 | content: 18 | application/json: 19 | schema: {} 20 | description: failed to register a new follwer 21 | put: 22 | description: '' 23 | responses: 24 | '200': 25 | content: 26 | application/json: 27 | schema: {} 28 | description: succeed to update a follwer 29 | '400': 30 | content: 31 | application/json: 32 | schema: {} 33 | description: failed to update a follwer 34 | get: 35 | description: '' 36 | responses: 37 | '200': 38 | content: 39 | application/json: 40 | schema: {} 41 | description: succeed to return all follwers info 42 | '400': 43 | content: 44 | application/json: 45 | schema: {} 46 | description: failed to return all follwers infon 47 | 48 | servers: 49 | - url: 'http://example.com' 50 | openapi: 3.0.0 51 | -------------------------------------------------------------------------------- /tests/unit/utils/repository/resources/sample.yaml: -------------------------------------------------------------------------------- 1 | openapi: '3.0.0' 2 | 3 | info: 4 | title: 'requestBody' 5 | description: 'https://swagger.io/docs/specification/describing-request-body/' 6 | version: 1.0.0 7 | 8 | servers: 9 | - url: 'http://localhost/' 10 | description: 'Test server' 11 | 12 | paths: 13 | /pets: 14 | post: 15 | summary: Add a new pet 16 | requestBody: 17 | description: Optional description in *Markdown* 18 | required: true 19 | content: 20 | application/json: 21 | schema: 22 | $ref: '#/components/schemas/Pet' 23 | application/xml: 24 | schema: 25 | $ref: '#/components/schemas/Pet' 26 | application/x-www-form-urlencoded: 27 | schema: 28 | $ref: '#/components/schemas/PetForm' 29 | text/plain: 30 | schema: 31 | type: string 32 | responses: 33 | '201': 34 | description: Created 35 | 36 | /avatar: 37 | put: 38 | summary: Upload an avatar 39 | requestBody: 40 | content: 41 | image/*: # Can be image/png, image/svg, image/gif, etc. 42 | schema: 43 | type: string 44 | format: binary 45 | multipart/form-data: 46 | schema: 47 | type: object 48 | properties: 49 | filename: 50 | type: array 51 | items: 52 | type: string 53 | format: binary 54 | responses: 55 | '201': 56 | description: Updated 57 | 58 | components: 59 | schemas: 60 | Pet: 61 | type: object 62 | properties: 63 | name: 64 | type: string 65 | fav_number: 66 | type: integer 67 | required: 68 | - name 69 | - fav_number 70 | additionalProperties: false 71 | PetForm: 72 | type: object 73 | properties: 74 | name: 75 | type: string 76 | fav_number: 77 | type: string 78 | required: 79 | - name 80 | - fav_number 81 | additionalProperties: false 82 | -------------------------------------------------------------------------------- /tests/unit/utils/repository/resources/with-path-ref.yaml: -------------------------------------------------------------------------------- 1 | openapi: '3.0.0' 2 | 3 | info: 4 | title: 'requestBody' 5 | description: 'https://swagger.io/docs/specification/describing-request-body/' 6 | version: 1.0.0 7 | 8 | servers: 9 | - url: 'http://localhost/' 10 | description: 'Test server' 11 | 12 | paths: 13 | /with-path-referred: 14 | $ref: './with-path-referred.yaml' 15 | description: 'this field should be ignored.' 16 | 17 | -------------------------------------------------------------------------------- /tests/unit/utils/repository/resources/with-path-referred.yaml: -------------------------------------------------------------------------------- 1 | post: 2 | summary: Add a new dog 3 | requestBody: 4 | description: Optional description in *Markdown* 5 | required: true 6 | content: 7 | application/json: 8 | schema: 9 | type: string 10 | responses: 11 | '201': 12 | description: Created 13 | get: 14 | summary: get dogs 15 | responses: 16 | '200': 17 | description: Success -------------------------------------------------------------------------------- /tests/unit/utils/repository/test_repository.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | import subprocess 4 | from src.utils.repository import Repository 5 | 6 | class TestRepository(unittest.TestCase): 7 | 8 | def _data_provider(self): 9 | current = os.path.dirname(os.path.abspath(__file__)) 10 | return { 11 | 'spec not found': { 12 | 'input': ['./not-exist-spec-path'], 13 | 'expected': [] 14 | }, 15 | 'input multi file': { 16 | 'input': [ 17 | os.path.join(current, 'resources/multi-file/file1.json'), 18 | os.path.join(current, 'resources/multi-file/file2.yaml') 19 | ], 20 | 'expected': [ 21 | ['GET', '/follwers', os.path.join(current, 'resources/multi-file/file2.yaml')], 22 | ['POST', '/follwers', os.path.join(current, 'resources/multi-file/file2.yaml')], 23 | ['PUT', '/follwers', os.path.join(current, 'resources/multi-file/file2.yaml')], 24 | ['GET', '/posts', os.path.join(current, 'resources/multi-file/file1.json')], 25 | ['POST', '/posts', os.path.join(current, 'resources/multi-file/file1.json')], 26 | ['GET', '/users', os.path.join(current, 'resources/multi-file/file1.json')], 27 | ['POST', '/users', os.path.join(current, 'resources/multi-file/file1.json')] 28 | ] 29 | }, 30 | 'input a file path': { 31 | 'input': [os.path.join(current, 'resources/sample.yaml')], 32 | 'expected': [ 33 | ['PUT', '/avatar', os.path.join(current, 'resources/sample.yaml')], 34 | ['POST', '/pets', os.path.join(current, 'resources/sample.yaml')] 35 | ] 36 | }, 37 | 'input path ref': { 38 | 'input': [os.path.join(current, 'resources/with-path-ref.yaml')], 39 | 'expected': [ 40 | ['GET', '/with-path-referred', os.path.join(current, 'resources/with-path-ref.yaml')], 41 | ['POST', '/with-path-referred', os.path.join(current, 'resources/with-path-ref.yaml')] 42 | ] 43 | } 44 | } 45 | 46 | 47 | def test_success_repository(self): 48 | for key, value in self._data_provider().items(): 49 | repository = Repository(value['input']) 50 | actual = repository.routes 51 | 52 | self.assertEqual(actual.to_list(), value['expected'], key) 53 | -------------------------------------------------------------------------------- /tests/unit/utils/resolver/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakopako/openapi-cli-tool/446f428f0d0c36842bab405e7efef65a9a713b90/tests/unit/utils/resolver/__init__.py -------------------------------------------------------------------------------- /tests/unit/utils/resolver/resources/expected/escape_characters.yaml: -------------------------------------------------------------------------------- 1 | application/json: 2 | schema: 3 | type: object 4 | properties: 5 | name: 6 | type: string 7 | fav_number: 8 | type: integer 9 | required: 10 | - name 11 | - fav_number 12 | additionalProperties: false 13 | text/plain: 14 | schema: 15 | type: string 16 | 17 | components: 18 | schemas: 19 | /blogs/{blog_id}/new~posts: 20 | type: object 21 | properties: 22 | name: 23 | type: string 24 | fav_number: 25 | type: integer 26 | required: 27 | - name 28 | - fav_number 29 | additionalProperties: false 30 | -------------------------------------------------------------------------------- /tests/unit/utils/resolver/resources/expected/local_components.yaml: -------------------------------------------------------------------------------- 1 | 2 | application/json: 3 | schema: 4 | type: object 5 | properties: 6 | name: 7 | type: string 8 | fav_number: 9 | type: integer 10 | required: 11 | - name 12 | - fav_number 13 | additionalProperties: false 14 | application/x-www-form-urlencoded: 15 | schema: 16 | type: object 17 | properties: 18 | name: 19 | type: string 20 | fav_number: 21 | type: string 22 | required: 23 | - name 24 | - fav_number 25 | additionalProperties: false 26 | text/plain: 27 | schema: 28 | type: string 29 | 30 | components: 31 | schemas: 32 | Pet: 33 | type: object 34 | properties: 35 | name: 36 | type: string 37 | fav_number: 38 | type: integer 39 | required: 40 | - name 41 | - fav_number 42 | additionalProperties: false 43 | PetForm: 44 | type: object 45 | properties: 46 | name: 47 | type: string 48 | fav_number: 49 | type: string 50 | required: 51 | - name 52 | - fav_number 53 | additionalProperties: false -------------------------------------------------------------------------------- /tests/unit/utils/resolver/resources/expected/nested_reference.yaml: -------------------------------------------------------------------------------- 1 | application/json: 2 | schema: 3 | type: object 4 | properties: 5 | name: 6 | type: string 7 | fav_number: 8 | type: string 9 | required: 10 | - name 11 | - fav_number 12 | additionalProperties: false 13 | application/x-www-form-urlencoded: 14 | schema: 15 | type: object 16 | properties: 17 | name: 18 | type: string 19 | fav_number: 20 | type: string 21 | required: 22 | - name 23 | - fav_number 24 | additionalProperties: false 25 | text/plain: 26 | schema: 27 | type: string 28 | 29 | components: 30 | schemas: 31 | Pet: 32 | type: object 33 | properties: 34 | name: 35 | type: string 36 | fav_number: 37 | type: string 38 | required: 39 | - name 40 | - fav_number 41 | additionalProperties: false 42 | PetForm: 43 | type: object 44 | properties: 45 | name: 46 | type: string 47 | fav_number: 48 | type: string 49 | required: 50 | - name 51 | - fav_number 52 | additionalProperties: false 53 | -------------------------------------------------------------------------------- /tests/unit/utils/resolver/resources/expected/remote_components.yaml: -------------------------------------------------------------------------------- 1 | application/json: 2 | schema: 3 | type: object 4 | properties: 5 | name: 6 | type: string 7 | fav_number: 8 | type: integer 9 | required: 10 | - name 11 | - fav_number 12 | additionalProperties: false 13 | text/plain: 14 | schema: 15 | type: string -------------------------------------------------------------------------------- /tests/unit/utils/resolver/resources/expected/remote_file.yaml: -------------------------------------------------------------------------------- 1 | application/json: 2 | schema: 3 | type: object 4 | properties: 5 | name: 6 | type: string 7 | fav_number: 8 | type: integer 9 | required: 10 | - name 11 | - fav_number 12 | additionalProperties: false 13 | text/plain: 14 | schema: 15 | type: string -------------------------------------------------------------------------------- /tests/unit/utils/resolver/resources/inputs/escape_characters.yaml: -------------------------------------------------------------------------------- 1 | application/json: 2 | schema: 3 | $ref: '#/components/schemas/~1blogs~1{blog_id}~1new~0posts' 4 | text/plain: 5 | schema: 6 | type: string 7 | 8 | components: 9 | schemas: 10 | /blogs/{blog_id}/new~posts: 11 | type: object 12 | properties: 13 | name: 14 | type: string 15 | fav_number: 16 | type: integer 17 | required: 18 | - name 19 | - fav_number 20 | additionalProperties: false 21 | -------------------------------------------------------------------------------- /tests/unit/utils/resolver/resources/inputs/local_components.yaml: -------------------------------------------------------------------------------- 1 | 2 | application/json: 3 | schema: 4 | $ref: '#/components/schemas/Pet' 5 | application/x-www-form-urlencoded: 6 | schema: 7 | $ref: '#/components/schemas/PetForm' 8 | text/plain: 9 | schema: 10 | type: string 11 | 12 | components: 13 | schemas: 14 | Pet: 15 | type: object 16 | properties: 17 | name: 18 | type: string 19 | fav_number: 20 | type: integer 21 | required: 22 | - name 23 | - fav_number 24 | additionalProperties: false 25 | PetForm: 26 | type: object 27 | properties: 28 | name: 29 | type: string 30 | fav_number: 31 | type: string 32 | required: 33 | - name 34 | - fav_number 35 | additionalProperties: false 36 | -------------------------------------------------------------------------------- /tests/unit/utils/resolver/resources/inputs/nested_reference.yaml: -------------------------------------------------------------------------------- 1 | application/json: 2 | schema: 3 | $ref: '#/components/schemas/Pet' 4 | application/x-www-form-urlencoded: 5 | schema: 6 | $ref: '#/components/schemas/PetForm' 7 | text/plain: 8 | schema: 9 | type: string 10 | 11 | components: 12 | schemas: 13 | Pet: 14 | $ref : '#/components/schemas/PetForm' 15 | PetForm: 16 | type: object 17 | properties: 18 | name: 19 | type: string 20 | fav_number: 21 | type: string 22 | required: 23 | - name 24 | - fav_number 25 | additionalProperties: false 26 | -------------------------------------------------------------------------------- /tests/unit/utils/resolver/resources/inputs/remote_components.yaml: -------------------------------------------------------------------------------- 1 | application/json: 2 | schema: 3 | $ref: './remote_components_referred.yaml#/components/schemas/Pet' 4 | text/plain: 5 | schema: 6 | type: string 7 | -------------------------------------------------------------------------------- /tests/unit/utils/resolver/resources/inputs/remote_components_referred.yaml: -------------------------------------------------------------------------------- 1 | components: 2 | schemas: 3 | Pet: 4 | type: object 5 | properties: 6 | name: 7 | type: string 8 | fav_number: 9 | type: integer 10 | required: 11 | - name 12 | - fav_number 13 | additionalProperties: false -------------------------------------------------------------------------------- /tests/unit/utils/resolver/resources/inputs/remote_file.yaml: -------------------------------------------------------------------------------- 1 | application/json: 2 | schema: 3 | $ref: './remote_file_referred.yaml' 4 | text/plain: 5 | schema: 6 | type: string 7 | -------------------------------------------------------------------------------- /tests/unit/utils/resolver/resources/inputs/remote_file_referred.yaml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | name: 4 | type: string 5 | fav_number: 6 | type: integer 7 | required: 8 | - name 9 | - fav_number 10 | additionalProperties: false -------------------------------------------------------------------------------- /tests/unit/utils/resolver/test_resolver_resolve.py: -------------------------------------------------------------------------------- 1 | import os 2 | import yaml 3 | import unittest 4 | from src.utils.resolver import resolver 5 | 6 | 7 | class TestResolverResolve(unittest.TestCase): 8 | 9 | def read_file(self, file_path): 10 | r = open(file_path, 'r') 11 | content = r.read() 12 | r.close() 13 | return yaml.safe_load(content) 14 | 15 | def _success_data_provider(self): 16 | current = os.path.dirname(os.path.abspath(__file__)) 17 | return { 18 | 'escape characters': { 19 | 'input': os.path.join(current, 'resources/inputs/escape_characters.yaml'), 20 | 'expected': os.path.join(current, 'resources/expected/escape_characters.yaml') 21 | }, 22 | 'nested reference': { 23 | 'input': os.path.join(current, 'resources/inputs/nested_reference.yaml'), 24 | 'expected': os.path.join(current, 'resources/expected/nested_reference.yaml') 25 | }, 26 | 'local components': { 27 | 'input': os.path.join(current, 'resources/inputs/local_components.yaml'), 28 | 'expected': os.path.join(current, 'resources/expected/local_components.yaml') 29 | }, 30 | 'remote components': { 31 | 'input': os.path.join(current, 'resources/inputs/remote_components.yaml'), 32 | 'expected': os.path.join(current, 'resources/expected/remote_components.yaml') 33 | }, 34 | 'remote file': { 35 | 'input': os.path.join(current, 'resources/inputs/remote_file.yaml'), 36 | 'expected': os.path.join(current, 'resources/expected/remote_file.yaml') 37 | }, 38 | 39 | # 'url file': { 40 | # 'input': os.path.join(current, 'resources/file.txt'), 41 | # 'expected': '' 42 | # }, 43 | # 'url components': { 44 | # 'input': os.path.join(current, 'resources/file.txt'), 45 | # 'expected': '' 46 | # } 47 | } 48 | 49 | def _exception_data_provider(self): 50 | current = os.path.dirname(os.path.abspath(__file__)) 51 | return { 52 | 'ref not found': { 53 | 'input': os.path.join(current, 'resources/file.yaml') 54 | } 55 | } 56 | 57 | def test_success_resolve(self): 58 | for key, value in self._success_data_provider().items(): 59 | input_spec = self.read_file(value['input']) 60 | actual = resolver(value['input'], input_spec) 61 | expected = self.read_file(value['expected']) 62 | self.assertEqual(actual, expected, key) 63 | 64 | 65 | @unittest.skip("need to be fixed") 66 | def test_exception_resolve(self): 67 | for key, value in self._exception_data_provider().items(): 68 | self.assertRaises(Exception, resolver(value['input'])) 69 | --------------------------------------------------------------------------------