diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c823b77..6bacabe 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -38,7 +38,7 @@ tests:debian: - apt-get -qqy update - apt-get -qqy install --no-install-recommends mat2 python3-flask python3-coverage python3-pip python3-setuptools - pip3 install wheel - - pip3 install -r requirements.txt + - pip3 install -r requirements.txt -r requirements-test.txt - python3-coverage run --branch --include main.py,matweb/*.py -m unittest discover -s test - python3-coverage report -m diff --git a/main.py b/main.py index 74a03fc..2fc23b1 100644 --- a/main.py +++ b/main.py @@ -1,5 +1,6 @@ import os import jinja2 +import yaml from matweb import utils, rest_api, frontend from flask import Flask, request @@ -30,17 +31,31 @@ def create_app(test_config=None): app.register_blueprint(rest_api.api_bp) app.json_encoder = LazyJSONEncoder + dirname = os.path.dirname(__file__) + with open(os.path.join(dirname, 'matweb/oas/components.yml')) as file: + components = yaml.full_load(file) + template = dict( - schemes=['https', 'http'], - host=LazyString(lambda: request.host), - basePath='/', + servers=[ + { + 'url': LazyString(lambda: request.host_url), + 'description': 'References the current running server' + } + ], info={ 'title': 'Mat2 Web API', 'version': '1', 'description': 'Mat2 Web RESTful API documentation', - } + }, + components=components ) - Swagger(app, template=template) + swagger_config = Swagger.DEFAULT_CONFIG + swagger_config['swagger_ui_bundle_js'] = '//unpkg.com/swagger-ui-dist@3/swagger-ui-bundle.js' + swagger_config['swagger_ui_standalone_preset_js'] = '//unpkg.com/swagger-ui-dist@3/swagger-ui-standalone-preset.js' + swagger_config['swagger_ui_css'] = '//unpkg.com/swagger-ui-dist@3/swagger-ui.css' + swagger_config['openapi'] = "3.0.3" + Swagger(app, template=template, config=swagger_config) + CORS(app, resources={r"/api/*": {"origins": utils.get_allow_origin_header_value()}}) return app diff --git a/matweb/oas/bulk.yml b/matweb/oas/bulk.yml index 08f346c..bbfe02e 100644 --- a/matweb/oas/bulk.yml +++ b/matweb/oas/bulk.yml @@ -6,47 +6,32 @@ description: "This endpoint allows you to bulk download several files which you uploaded beforehand. Note that the `download_list` MUST contain more than two files. The max length is configurable (default is 10)." -consumes: - - "application/json" -produces: - - "application/json" -parameters: -- in: "body" - name: "body" +requestBody: description: "The files that will be combined for one single download" required: true - schema: - $ref: '#/definitions/BulkBody' + content: + application/json: + schema: + $ref: '#/components/schemas/BulkBody' responses: 201: description: "A new resource to download all files as one archive" - schema: - $ref: '#/definitions/UploadResponse' + content: + application/json: + schema: + $ref: '#/components/schemas/UploadResponse' 400: description: "Invalid input" - schema: - $ref: '#/definitions/ErrorResponse' + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/ErrorAtLeastTwoResponse' + - $ref: '#/components/schemas/ErrorResponse' 500: description: "Unable to clean the file" - schema: - $ref: '#/definitions/ErrorResponse' - -definitions: - BulkBody: - type: "object" - properties: - download_list: - type: "array" - description: "An object containing the files you want to create a bulk download for" - items: - $ref: '#/definitions/BulkFile' - BulkFile: - type: "object" - properties: - file_name: - type: "string" - key: - type: "string" - secret: - type: "string" \ No newline at end of file + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' \ No newline at end of file diff --git a/matweb/oas/components.yml b/matweb/oas/components.yml new file mode 100644 index 0000000..82d07fb --- /dev/null +++ b/matweb/oas/components.yml @@ -0,0 +1,68 @@ +schemas: + BulkBody: + type: "object" + properties: + download_list: + type: "array" + description: "An object containing the files you want to create a bulk download for" + items: + $ref: '#/components/schemas/BulkFile' + BulkFile: + type: "object" + properties: + file_name: + type: "string" + key: + type: "string" + secret: + type: "string" + ErrorAtLeastTwoResponse: + type: "object" + properties: + message: + type: "object" + description: "A description of the error" + properties: + download_list: + type: "array" + items: + type: "string" + example: "Min length is 2" + UploadResponse: + type: "object" + properties: + inactive_after_sec: + type: "integer" + format: "int64" + description: "Defines after how many seconds the download wont be available" + output_filename: + type: "string" + description: "The resulting filename after metadata removal" + mime: + type: "string" + description: "The mime type of the cleaned file" + key: + type: "string" + description: "A key used to guarantee file integrity" + secret: + type: "string" + description: "A secret used to guarantee file integrity" + meta: + type: "object" + description: "An object of the removed metadata where key indicates the metadata type" + items: + type: "string" + meta_after: + type: "object" + description: "An object of the remaining metadata where key indicates the metadata type" + items: + type: "string" + download_link: + type: "string" + description: "The link to download the cleaned file" + ErrorResponse: + type: "object" + properties: + message: + type: "string" + description: "A description of the error" \ No newline at end of file diff --git a/matweb/oas/download.yml b/matweb/oas/download.yml index 899d103..e4a0ca4 100644 --- a/matweb/oas/download.yml +++ b/matweb/oas/download.yml @@ -2,30 +2,39 @@ tags: - "File Download" summary: 'Download a single cleaned file or zip archive containing multiple files' -consumes: - - "application/json" -produces: - - "*/*" parameters: -- name: "key" - in: "path" - description: "A key generated for that resource" - required: true - type: "string" -- name: "secret" - in: "path" - description: "A secret generated for that resource" - required: true - type: "string" -- name: "filename" - in: "path" - description: "the filename of the cleaned file" - required: true - type: "string" + - name: "key" + in: "path" + description: "A key generated for that resource" + required: true + schema: + type: "string" + - name: "secret" + in: "path" + description: "A secret generated for that resource" + required: true + schema: + type: "string" + - name: "filename" + in: "path" + description: "the filename of the cleaned file" + required: true + schema: + type: "string" responses: 200: - description: "" + description: "The cleaned file as attachment so a browser downloads the file directly" + content: + "*/*": + schema: + format: binary + 400: + description: "Invalid input" + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' 404: description: "The filename, key or secret are wrong or the link is too old and the file has been deleted" \ No newline at end of file diff --git a/matweb/oas/extension.yml b/matweb/oas/extension.yml index 3e31034..41314a7 100644 --- a/matweb/oas/extension.yml +++ b/matweb/oas/extension.yml @@ -2,16 +2,14 @@ tags: - "Supported Extensions" summary: 'Returns a list of all supported file extensions' -consumes: - - "application/json" -produces: - - "application/json" responses: 200: description: "A list of all supported file extensions" - schema: - type: "array" - items: - type: "string" - example: ".jpeg" \ No newline at end of file + content: + application/json: + schema: + type: "array" + items: + type: "string" + example: ".jpeg" \ No newline at end of file diff --git a/matweb/oas/upload.yml b/matweb/oas/upload.yml index 71cb87b..4133d08 100644 --- a/matweb/oas/upload.yml +++ b/matweb/oas/upload.yml @@ -2,79 +2,43 @@ tags: - "File Upload (Metadata removal)" summary: 'Upload a single file which will be cleaned from metadata' -consumes: - - "application/json" -produces: - - "application/json" -parameters: -- in: "body" - name: "body" +requestBody: description: "The file that will be cleaned from metadata. Note that the file must be base64 encoded" required: true - schema: - type: "object" - properties: - file_name: - type: "string" - example: 'my_example.jpg' - file: - type: "string" - example: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==' + content: + application/json: + schema: + type: "object" + properties: + file_name: + type: "string" + example: 'my_example.jpg' + file: + type: "string" + example: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==' responses: 201: description: "An object containing all info about the cleaned file" - schema: - $ref: '#/definitions/UploadResponse' + content: + application/json: + schema: + $ref: '#/components/schemas/UploadResponse' 400: description: "Invalid input" - schema: - $ref: '#/definitions/ErrorResponse' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' 415: description: "Unsupported file type" - schema: - $ref: '#/definitions/ErrorResponse' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' 500: description: "Unable to clean the file" - schema: - $ref: '#/definitions/ErrorResponse' - -definitions: - UploadResponse: - type: "object" - properties: - inactive_after_sec: - type: "integer" - format: "int64" - description: "Defines after how many seconds the download wont be available" - output_filename: - type: "string" - description: "The resulting filename after metadata removal" - mime: - type: "string" - description: "The mime type of the cleaned file" - key: - type: "string" - description: "A key used to guarantee file integrity" - secret: - type: "string" - description: "A secret used to guarantee file integrity" - meta: - type: "object" - description: "An object of the removed metadata where key indicates the metadata type" - items: - type: "string" - meta_after: - type: "object" - description: "An object of the remaining metadata where key indicates the metadata type" - items: - type: "string" - download_link: - type: "string" - description: "The link to download the cleaned file" - ErrorResponse: - type: "object" - properties: - message: - type: "string" - description: "A description of the error" \ No newline at end of file + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' \ No newline at end of file diff --git a/requirements-test.txt b/requirements-test.txt new file mode 100644 index 0000000..ca44124 --- /dev/null +++ b/requirements-test.txt @@ -0,0 +1 @@ +openapi-spec-validator==0.2.8 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 72ad125..b505376 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,4 +8,4 @@ Flask-Cors==3.0.8 Cerberus==1.3.2 Flask-Testing==0.8.0 blinker==1.4 -flasgger==0.9.4 \ No newline at end of file +iknl-flasgger==0.9.2.post1 \ No newline at end of file diff --git a/test/test_api.py b/test/test_api.py index 039245a..be9cb29 100644 --- a/test/test_api.py +++ b/test/test_api.py @@ -7,6 +7,7 @@ import zipfile from six import BytesIO from unittest.mock import patch +from openapi_spec_validator import validate_spec import main @@ -420,6 +421,9 @@ class Mat2APITestCase(unittest.TestCase): self.assertEqual(400, request.status_code) self.assertEqual("Failed decoding file", error_message) + def test_valid_opena_api_spec(self): + spec = self.app.get('apispec_1.json').get_json() + validate_spec(spec) if __name__ == '__main__': unittest.main()