From 97bfb18c8f37b96ed8e8af92258856cd3a808595 Mon Sep 17 00:00:00 2001 From: Emily Bache Date: Thu, 28 Dec 2023 14:18:54 +0000 Subject: [PATCH 01/14] added openapi.yaml files and means to re-generate them --- greeting/src/greeting.py | 9 +++--- greeting/src/openapi.yaml | 55 ++++++++++++++++++++++++++++++++++++ newsletter/src/newsletter.py | 6 +++- newsletter/src/openapi.yaml | 37 ++++++++++++++++++++++++ users/src/openapi.yaml | 48 +++++++++++++++++++++++++++++++ users/src/users.py | 6 ++-- 6 files changed, 154 insertions(+), 7 deletions(-) create mode 100644 greeting/src/openapi.yaml create mode 100644 newsletter/src/openapi.yaml create mode 100644 users/src/openapi.yaml diff --git a/greeting/src/greeting.py b/greeting/src/greeting.py index b616d49..842bc0b 100644 --- a/greeting/src/greeting.py +++ b/greeting/src/greeting.py @@ -3,7 +3,7 @@ from apiflask import APIFlask, Schema from apiflask.fields import String -#import yaml +import yaml app = APIFlask('greeting', title='Greeting Service') @@ -14,7 +14,6 @@ class PersonData(Schema): @app.get("/formatGreeting") -#@app.output(StringSchema, content_type="text/html", status_code=200) @app.input(PersonData, "query") def format_greeting(query_data): name = query_data.get('name') @@ -32,6 +31,8 @@ def format_greeting(query_data): if __name__ == "__main__": port = 0 if "DYNAMIC_PORTS" in os.environ else 5002 - # with open(os.path.join(os.path.dirname(__file__), "openapi.yaml"), "w") as f: - # yaml.dump(app.spec, f) + if "DUMP_SCHEMA" in os.environ: + print("Writing schema file") + with open(os.path.join(os.path.dirname(__file__), "openapi.yaml"), "w") as f: + yaml.dump(app.spec, f) app.run(port=port) diff --git a/greeting/src/openapi.yaml b/greeting/src/openapi.yaml new file mode 100644 index 0000000..9f68f01 --- /dev/null +++ b/greeting/src/openapi.yaml @@ -0,0 +1,55 @@ +components: + schemas: + ValidationError: + properties: + detail: + properties: + : + properties: + : + items: + type: string + type: array + type: object + type: object + message: + type: string + type: object +info: + title: Greeting Service + version: 0.1.0 +openapi: 3.0.3 +paths: + /formatGreeting: + get: + parameters: + - in: query + name: description + required: false + schema: + type: string + - in: query + name: name + required: true + schema: + type: string + - in: query + name: title + required: false + schema: + type: string + responses: + '200': + content: + text/html: + schema: + type: string + description: Successful response + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' + description: Validation error + summary: Format Greeting +tags: [] diff --git a/newsletter/src/newsletter.py b/newsletter/src/newsletter.py index a9a0601..b993043 100644 --- a/newsletter/src/newsletter.py +++ b/newsletter/src/newsletter.py @@ -4,6 +4,7 @@ from flask_cors import CORS, cross_origin from apiflask import APIFlask, abort +import yaml app = APIFlask('newsletter', title='Newsletter Service') CORS(app) @@ -15,7 +16,6 @@ } @app.get("/sayHello/") -@app.output({ "type": "string" }, content_type="text/html", status_code=200) @cross_origin() def say_hello(name): person = get_person(name) @@ -46,4 +46,8 @@ def _get(url, params=None): if __name__ == "__main__": port = 0 if "DYNAMIC_PORTS" in os.environ else 5010 + if "DUMP_SCHEMA" in os.environ: + print("Writing schema file") + with open(os.path.join(os.path.dirname(__file__), "openapi.yaml"), "w") as f: + yaml.dump(app.spec, f) app.run(port=port) diff --git a/newsletter/src/openapi.yaml b/newsletter/src/openapi.yaml new file mode 100644 index 0000000..7ffcefe --- /dev/null +++ b/newsletter/src/openapi.yaml @@ -0,0 +1,37 @@ +components: + schemas: + HTTPError: + properties: + detail: + type: object + message: + type: string + type: object +info: + title: Newsletter Service + version: 0.1.0 +openapi: 3.0.3 +paths: + /sayHello/{name}: + get: + parameters: + - in: path + name: name + required: true + schema: + type: string + responses: + '200': + content: + text/html: + schema: + type: string + description: Successful response + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPError' + description: Not found + summary: Say Hello +tags: [] diff --git a/users/src/openapi.yaml b/users/src/openapi.yaml new file mode 100644 index 0000000..018c727 --- /dev/null +++ b/users/src/openapi.yaml @@ -0,0 +1,48 @@ +components: + schemas: + HTTPError: + properties: + detail: + type: object + message: + type: string + type: object + PersonOut: + properties: + description: + nullable: true + type: string + name: + type: string + title: + nullable: true + type: string + type: object +info: + title: Users Service + version: 0.1.0 +openapi: 3.0.3 +paths: + /getPerson/{name}: + get: + parameters: + - in: path + name: name + required: true + schema: + type: string + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PersonOut' + description: Successful response + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPError' + description: Not found + summary: Get Person Http +tags: [] diff --git a/users/src/users.py b/users/src/users.py index c4ea78a..9eed349 100644 --- a/users/src/users.py +++ b/users/src/users.py @@ -33,6 +33,8 @@ def get_person_http(name): if __name__ == "__main__": port = 0 if "DYNAMIC_PORTS" in os.environ else 5001 - # with open(os.path.join(os.path.dirname(__file__), "openapi.yaml"), "w") as f: - # yaml.dump(app.spec, f) + if "DUMP_SCHEMA" in os.environ: + print("Writing schema file") + with open(os.path.join(os.path.dirname(__file__), "openapi.yaml"), "w") as f: + yaml.dump(app.spec, f) app.run(port=port) From 9614ba530d5aeb42671d41c02380c05ca0f33971 Mon Sep 17 00:00:00 2001 From: Emily Bache Date: Thu, 28 Dec 2023 14:40:19 +0000 Subject: [PATCH 02/14] added 3 test cases in newsletter.spec and adjusted openapi spec to accommodate a 400 response as correct --- newsletter/src/openapi.yaml | 6 ++++++ specmatic/newsletter.spec | 25 +++++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 specmatic/newsletter.spec diff --git a/newsletter/src/openapi.yaml b/newsletter/src/openapi.yaml index 7ffcefe..7dc4d80 100644 --- a/newsletter/src/openapi.yaml +++ b/newsletter/src/openapi.yaml @@ -27,6 +27,12 @@ paths: schema: type: string description: Successful response + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPError' + description: Not found '404': content: application/json: diff --git a/specmatic/newsletter.spec b/specmatic/newsletter.spec new file mode 100644 index 0000000..d7d4dfe --- /dev/null +++ b/specmatic/newsletter.spec @@ -0,0 +1,25 @@ +Feature: Newsletter API + +Background: + Given openapi ../newsletter/src/openapi.yaml + +Scenario Outline: AddUser + When GET /sayHello/Geoff + Then status 200 + Examples: + | name | + | xxxxx | + +Scenario Outline: BadUser + When GET /sayHello/Insecticide + Then status 400 + Examples: + | name | + | xxxxx | + +Scenario Outline: KnownUser + When GET /sayHello/Charles%20Darwin + Then status 200 + Examples: + | name | + | xxxxx | From 032439994752160bd35c05e4f7a08214eae6ade1 Mon Sep 17 00:00:00 2001 From: Emily Bache Date: Thu, 28 Dec 2023 15:19:15 +0000 Subject: [PATCH 03/14] added stub for users service (not working) --- .../src/{openapi.yaml => greeting-openapi.yaml} | 0 .../{openapi.yaml => newsletter-openapi.yaml} | 0 specmatic/run_tests.bat | 17 +++++++++++++++++ specmatic/start_users_stub.bat | 1 + specmatic/users-openapi_data/expectation.json | 15 +++++++++++++++ users/src/{openapi.yaml => users-openapi.yaml} | 6 ++++++ 6 files changed, 39 insertions(+) rename greeting/src/{openapi.yaml => greeting-openapi.yaml} (100%) rename newsletter/src/{openapi.yaml => newsletter-openapi.yaml} (100%) create mode 100644 specmatic/run_tests.bat create mode 100644 specmatic/start_users_stub.bat create mode 100644 specmatic/users-openapi_data/expectation.json rename users/src/{openapi.yaml => users-openapi.yaml} (85%) diff --git a/greeting/src/openapi.yaml b/greeting/src/greeting-openapi.yaml similarity index 100% rename from greeting/src/openapi.yaml rename to greeting/src/greeting-openapi.yaml diff --git a/newsletter/src/openapi.yaml b/newsletter/src/newsletter-openapi.yaml similarity index 100% rename from newsletter/src/openapi.yaml rename to newsletter/src/newsletter-openapi.yaml diff --git a/specmatic/run_tests.bat b/specmatic/run_tests.bat new file mode 100644 index 0000000..c37932b --- /dev/null +++ b/specmatic/run_tests.bat @@ -0,0 +1,17 @@ + + + +# run against newsletter openapi.yaml directly +C:\openjdk-20.0.2_windows-x64_bin\jdk-20.0.2\bin/java -jar C:\Users\Administrator\Downloads\specmatic.jar test --testBaseURL http://localhost:5010 .\newsletter\src\newsletter-openapi.yaml + +# run against newletter.spec +C:\openjdk-20.0.2_windows-x64_bin\jdk-20.0.2\bin/java -jar C:\Users\Administrator\Downloads\specmatic.jar test --testBaseURL http://localhost:5010 .\specmatic\newsletter.spec + +# run against greeting openapi +C:\openjdk-20.0.2_windows-x64_bin\jdk-20.0.2\bin/java -jar C:\Users\Administrator\Downloads\specmatic.jar test --testBaseURL http://localhost:5002 .\greeting\src\greeting-openapi.yaml + +# run against users openapi +C:\openjdk-20.0.2_windows-x64_bin\jdk-20.0.2\bin/java -jar C:\Users\Administrator\Downloads\specmatic.jar test --testBaseURL http://localhost:5001 .\users\src\users-openapi.yaml + + + diff --git a/specmatic/start_users_stub.bat b/specmatic/start_users_stub.bat new file mode 100644 index 0000000..821c5e1 --- /dev/null +++ b/specmatic/start_users_stub.bat @@ -0,0 +1 @@ +C:\openjdk-20.0.2_windows-x64_bin\jdk-20.0.2\bin/java -jar C:\Users\Administrator\Downloads\specmatic.jar stub .\users\src\users-openapi.yaml diff --git a/specmatic/users-openapi_data/expectation.json b/specmatic/users-openapi_data/expectation.json new file mode 100644 index 0000000..21c6183 --- /dev/null +++ b/specmatic/users-openapi_data/expectation.json @@ -0,0 +1,15 @@ +{ + "http-request": { + "method": "GET", + "path": "/getPerson/Geoff", + "body": "" + }, + "http-response": { + "status": 200, + "body": { + "name": "Geoff", + "title": null + }, + "status-text": "OK" + } +} \ No newline at end of file diff --git a/users/src/openapi.yaml b/users/src/users-openapi.yaml similarity index 85% rename from users/src/openapi.yaml rename to users/src/users-openapi.yaml index 018c727..7a19be2 100644 --- a/users/src/openapi.yaml +++ b/users/src/users-openapi.yaml @@ -38,6 +38,12 @@ paths: schema: $ref: '#/components/schemas/PersonOut' description: Successful response + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPError' + description: Bad Input '404': content: application/json: From c583a207e598630dd2ca3f881d4ad218efff9d23 Mon Sep 17 00:00:00 2001 From: Emily Bache Date: Thu, 28 Dec 2023 15:19:50 +0000 Subject: [PATCH 04/14] ignore build files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 352f896..ebbc3ba 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ texttest/html2ascii venv .project .pydevproject +build/* From dd566a85cc575eee5169da681395751216eba7e4 Mon Sep 17 00:00:00 2001 From: Emily Bache Date: Fri, 29 Dec 2023 10:30:50 +0000 Subject: [PATCH 05/14] make it possible to regenerate spec from code --- greeting/src/greeting.py | 2 +- newsletter/src/newsletter.py | 2 +- users/src/users.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/greeting/src/greeting.py b/greeting/src/greeting.py index 842bc0b..3267437 100644 --- a/greeting/src/greeting.py +++ b/greeting/src/greeting.py @@ -33,6 +33,6 @@ def format_greeting(query_data): port = 0 if "DYNAMIC_PORTS" in os.environ else 5002 if "DUMP_SCHEMA" in os.environ: print("Writing schema file") - with open(os.path.join(os.path.dirname(__file__), "openapi.yaml"), "w") as f: + with open(os.path.join(os.path.dirname(__file__), "greeting-openapi.yaml"), "w") as f: yaml.dump(app.spec, f) app.run(port=port) diff --git a/newsletter/src/newsletter.py b/newsletter/src/newsletter.py index b993043..8e45d51 100644 --- a/newsletter/src/newsletter.py +++ b/newsletter/src/newsletter.py @@ -48,6 +48,6 @@ def _get(url, params=None): port = 0 if "DYNAMIC_PORTS" in os.environ else 5010 if "DUMP_SCHEMA" in os.environ: print("Writing schema file") - with open(os.path.join(os.path.dirname(__file__), "openapi.yaml"), "w") as f: + with open(os.path.join(os.path.dirname(__file__), "newsletter-openapi.yaml"), "w") as f: yaml.dump(app.spec, f) app.run(port=port) diff --git a/users/src/users.py b/users/src/users.py index 9eed349..469e7db 100644 --- a/users/src/users.py +++ b/users/src/users.py @@ -35,6 +35,6 @@ def get_person_http(name): port = 0 if "DYNAMIC_PORTS" in os.environ else 5001 if "DUMP_SCHEMA" in os.environ: print("Writing schema file") - with open(os.path.join(os.path.dirname(__file__), "openapi.yaml"), "w") as f: + with open(os.path.join(os.path.dirname(__file__), "users-openapi.yaml"), "w") as f: yaml.dump(app.spec, f) app.run(port=port) From 271914c0cc136aa35aa9a9c750c1c67a62c16688 Mon Sep 17 00:00:00 2001 From: Naresh Jain Date: Sat, 30 Dec 2023 16:24:27 +0530 Subject: [PATCH 06/14] added specmatic contract tests for newsletter added stub files for user and greeting service moved all openAPI specs to contract folder and added their respective stub files in the same parent folder --- .gitignore | 5 ++- .../src => contracts}/greeting-openapi.yaml | 42 ++++++++++++------- .../src => contracts}/newsletter-openapi.yaml | 25 +++++++---- {users/src => contracts}/users-openapi.yaml | 24 +++++++++++ newsletter/specmatic.json | 14 +++++++ newsletter/src/__init__.py | 0 newsletter/test/__init__.py | 0 .../known_user_greeting_expectations.json | 18 ++++++++ .../unknown_user_greeting_expectations.json | 16 +++++++ .../user/blacklist_user_expectations.json | 13 ++++++ .../data/user/known_user_expectations.json | 14 +++++++ .../data/user/unknown_user_expectations.json | 14 +++++++ newsletter/test/test_contract.py | 30 +++++++++++++ requirements.txt | 2 + specmatic/users-openapi_data/expectation.json | 15 ------- 15 files changed, 194 insertions(+), 38 deletions(-) rename {greeting/src => contracts}/greeting-openapi.yaml (52%) rename {newsletter/src => contracts}/newsletter-openapi.yaml (52%) rename {users/src => contracts}/users-openapi.yaml (59%) create mode 100644 newsletter/specmatic.json create mode 100644 newsletter/src/__init__.py create mode 100644 newsletter/test/__init__.py create mode 100644 newsletter/test/data/greeting/known_user_greeting_expectations.json create mode 100644 newsletter/test/data/greeting/unknown_user_greeting_expectations.json create mode 100644 newsletter/test/data/user/blacklist_user_expectations.json create mode 100644 newsletter/test/data/user/known_user_expectations.json create mode 100644 newsletter/test/data/user/unknown_user_expectations.json create mode 100644 newsletter/test/test_contract.py delete mode 100644 specmatic/users-openapi_data/expectation.json diff --git a/.gitignore b/.gitignore index ebbc3ba..001568e 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,7 @@ texttest/html2ascii venv .project .pydevproject -build/* +**/build/* +.vscode/* +**/.pytest_cache/* +**/.specmatic/* diff --git a/greeting/src/greeting-openapi.yaml b/contracts/greeting-openapi.yaml similarity index 52% rename from greeting/src/greeting-openapi.yaml rename to contracts/greeting-openapi.yaml index 9f68f01..1067658 100644 --- a/greeting/src/greeting-openapi.yaml +++ b/contracts/greeting-openapi.yaml @@ -23,27 +23,39 @@ paths: /formatGreeting: get: parameters: - - in: query - name: description - required: false - schema: - type: string - - in: query - name: name - required: true - schema: - type: string - - in: query - name: title - required: false - schema: - type: string + - in: query + name: description + required: false + schema: + type: string + examples: + SUCCESS: + value: Independent Technical Coach, YouTuber, creator of Samman Coaching, Author + - in: query + name: name + required: true + schema: + type: string + examples: + SUCCESS: + value: EmilyBache + - in: query + name: title + required: false + schema: + type: string + examples: + SUCCESS: + value: Miss. responses: '200': content: text/html: schema: type: string + examples: + SUCCESS: + value: Hello, Miss. EmilyBache. Independent Technical Coach, YouTuber, creator of Samman Coaching, Author is my favourite person. description: Successful response '400': content: diff --git a/newsletter/src/newsletter-openapi.yaml b/contracts/newsletter-openapi.yaml similarity index 52% rename from newsletter/src/newsletter-openapi.yaml rename to contracts/newsletter-openapi.yaml index 7dc4d80..e56475f 100644 --- a/newsletter/src/newsletter-openapi.yaml +++ b/contracts/newsletter-openapi.yaml @@ -20,24 +20,35 @@ paths: required: true schema: type: string + examples: + SUCCESS: + value: "EmilyBache" + BLACKLISTED: + value: "DDT" + Unknown: + value: "Unknown" responses: '200': content: text/html: schema: type: string + examples: + SUCCESS: + value: "Hello, Miss. EmilyBache. Independent Technical Coach, YouTuber, creator of Samman Coaching, Author is my favourite person." + Unknown: + value: "Hello, Unknown." description: Successful response '400': content: application/json: schema: $ref: '#/components/schemas/HTTPError' - description: Not found - '404': - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPError' - description: Not found + examples: + BLACKLISTED: + value: + detail: { "error": "Blacklisted name" } + message: "Bad request" + description: Bad request summary: Say Hello tags: [] diff --git a/users/src/users-openapi.yaml b/contracts/users-openapi.yaml similarity index 59% rename from users/src/users-openapi.yaml rename to contracts/users-openapi.yaml index 7a19be2..6e47be2 100644 --- a/users/src/users-openapi.yaml +++ b/contracts/users-openapi.yaml @@ -4,9 +4,13 @@ components: properties: detail: type: object + additionalProperties: true message: type: string type: object + example: + detail: { "error": "Invalid input" } + message: "Bad request" PersonOut: properties: description: @@ -18,6 +22,10 @@ components: nullable: true type: string type: object + example: + description: "Software Engineer" + name: "John Doe" + title: "Mr." info: title: Users Service version: 0.1.0 @@ -31,18 +39,34 @@ paths: required: true schema: type: string + examples: + SUCCESS: + value: "EmilyBache" + BLACKLISTED: + value: "DDT" responses: '200': content: application/json: schema: $ref: '#/components/schemas/PersonOut' + examples: + SUCCESS: + value: + description: "Independent Technical Coach, YouTuber, creator of Samman Coaching, Author" + name: "EmilyBache" + title: "Miss." description: Successful response '400': content: application/json: schema: $ref: '#/components/schemas/HTTPError' + examples: + BLACKLISTED: + value: + detail: { "error": "Invalid input" } + message: "DDTs are not kind to bees." description: Bad Input '404': content: diff --git a/newsletter/specmatic.json b/newsletter/specmatic.json new file mode 100644 index 0000000..fc5b15c --- /dev/null +++ b/newsletter/specmatic.json @@ -0,0 +1,14 @@ +{ + "sources": [ + { + "provider": "git", + "test": [ + "../contracts/newsletter-openapi.yaml" + ], + "stub": [ + "../contracts/greeting-openapi.yaml", + "../contracts/users-openapi.yaml" + ] + } + ] +} \ No newline at end of file diff --git a/newsletter/src/__init__.py b/newsletter/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/newsletter/test/__init__.py b/newsletter/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/newsletter/test/data/greeting/known_user_greeting_expectations.json b/newsletter/test/data/greeting/known_user_greeting_expectations.json new file mode 100644 index 0000000..6e35254 --- /dev/null +++ b/newsletter/test/data/greeting/known_user_greeting_expectations.json @@ -0,0 +1,18 @@ +{ + "http-request": { + "path": "/formatGreeting", + "method": "GET", + "query": { + "title": "Miss.", + "name": "EmilyBache", + "description": "Independent Technical Coach, YouTuber, creator of Samman Coaching, Author" + } + }, + "http-response": { + "status": 200, + "body": "Hello, Miss. EmilyBache. Independent Technical Coach, YouTuber, creator of Samman Coaching, Author is my favourite person.", + "headers": { + "Content-Type": "text/html" + } + } +} \ No newline at end of file diff --git a/newsletter/test/data/greeting/unknown_user_greeting_expectations.json b/newsletter/test/data/greeting/unknown_user_greeting_expectations.json new file mode 100644 index 0000000..2d34f5f --- /dev/null +++ b/newsletter/test/data/greeting/unknown_user_greeting_expectations.json @@ -0,0 +1,16 @@ +{ + "http-request": { + "path": "/formatGreeting", + "method": "GET", + "query": { + "name": "Unknown" + } + }, + "http-response": { + "status": 200, + "body": "Hello, Unknown!", + "headers": { + "Content-Type": "text/html" + } + } +} \ No newline at end of file diff --git a/newsletter/test/data/user/blacklist_user_expectations.json b/newsletter/test/data/user/blacklist_user_expectations.json new file mode 100644 index 0000000..5e65fad --- /dev/null +++ b/newsletter/test/data/user/blacklist_user_expectations.json @@ -0,0 +1,13 @@ +{ + "http-request": { + "path": "/getPerson/DDT", + "method": "GET" + }, + "http-response": { + "status": 400, + "body": { + "detail": { "error": "Invalid input" }, + "message": "DDTs are not kind to bees." + } + } +} \ No newline at end of file diff --git a/newsletter/test/data/user/known_user_expectations.json b/newsletter/test/data/user/known_user_expectations.json new file mode 100644 index 0000000..4d3f2a8 --- /dev/null +++ b/newsletter/test/data/user/known_user_expectations.json @@ -0,0 +1,14 @@ +{ + "http-request": { + "path": "/getPerson/EmilyBache", + "method": "GET" + }, + "http-response": { + "status": 200, + "body": { + "description": "Independent Technical Coach, YouTuber, creator of Samman Coaching, Author", + "name": "EmilyBache", + "title": "Miss." + } + } +} \ No newline at end of file diff --git a/newsletter/test/data/user/unknown_user_expectations.json b/newsletter/test/data/user/unknown_user_expectations.json new file mode 100644 index 0000000..0df4035 --- /dev/null +++ b/newsletter/test/data/user/unknown_user_expectations.json @@ -0,0 +1,14 @@ +{ + "http-request": { + "path": "/getPerson/Unknown", + "method": "GET" + }, + "http-response": { + "status": 200, + "body": { + "description": null, + "name": "Unknown", + "title": null + } + } +} \ No newline at end of file diff --git a/newsletter/test/test_contract.py b/newsletter/test/test_contract.py new file mode 100644 index 0000000..2c69f66 --- /dev/null +++ b/newsletter/test/test_contract.py @@ -0,0 +1,30 @@ +import pytest +import os +from specmatic.core.specmatic import Specmatic + +from src.newsletter import app + +ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +app_host = "127.0.0.1" +app_port = 5010 +stub_host = "127.0.0.1" +stub_port = 9000 + +os.environ['USERS_URL'] = 'http://' + stub_host + ':' + str(stub_port) +os.environ['GREETING_URL'] = 'http://' + stub_host + ':' + str(stub_port) + +expectation_json_file = ROOT_DIR + '/test/data' + +class TestContract: + pass + +Specmatic() \ + .with_project_root(ROOT_DIR) \ + .with_stub(stub_host, stub_port, args=["--data="+expectation_json_file]) \ + .with_wsgi_app(app, app_host, app_port) \ + .test(TestContract) \ + .run() + +if __name__ == '__main__': + pytest.main() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index a3c3921..8b8ca52 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,3 +9,5 @@ dbtext capturemock pyodbc PyYAML +pytest==7.3.1 +specmatic==0.22.0 \ No newline at end of file diff --git a/specmatic/users-openapi_data/expectation.json b/specmatic/users-openapi_data/expectation.json deleted file mode 100644 index 21c6183..0000000 --- a/specmatic/users-openapi_data/expectation.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "http-request": { - "method": "GET", - "path": "/getPerson/Geoff", - "body": "" - }, - "http-response": { - "status": 200, - "body": { - "name": "Geoff", - "title": null - }, - "status-text": "OK" - } -} \ No newline at end of file From bb7bb92a2b1bf6485b5b3d0a956d7b26d57ab27d Mon Sep 17 00:00:00 2001 From: Hari Krishnan Date: Sat, 30 Dec 2023 17:01:41 +0530 Subject: [PATCH 07/14] upgrading Specmatic python wrapper to 0.23.0, removing redundant additionalProperties free form object marker in users-openapi.yaml --- contracts/users-openapi.yaml | 1 - requirements.txt | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/contracts/users-openapi.yaml b/contracts/users-openapi.yaml index 6e47be2..a1588a5 100644 --- a/contracts/users-openapi.yaml +++ b/contracts/users-openapi.yaml @@ -4,7 +4,6 @@ components: properties: detail: type: object - additionalProperties: true message: type: string type: object diff --git a/requirements.txt b/requirements.txt index 8b8ca52..829c45e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,4 @@ capturemock pyodbc PyYAML pytest==7.3.1 -specmatic==0.22.0 \ No newline at end of file +specmatic==0.23.0 \ No newline at end of file From 737d6974226a460ac3464805d64fe3fedc75afe9 Mon Sep 17 00:00:00 2001 From: Naresh Jain Date: Sat, 30 Dec 2023 21:22:01 +0530 Subject: [PATCH 08/14] Updated README with contract testing section --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 1f0fc8e..09e5d6d 100644 --- a/README.md +++ b/README.md @@ -70,3 +70,10 @@ For each service, (users, newsletter and greeting), here referred to as $SERVICE When they are all running, you should be able to open a browser on the [Newsletter swagger api](http://localhost:5010/docs) to access the functionality. +## Contract Testing with Specmatic +To run the contract tests for newsletter service: + + cd newsletter + pytest test -v -s + +In specmatic.json under newsletter folder, you will notice that we've specified the path to newsletter service's OpenAPI spec under the test section. Similarlly we've specified, user and greeting service's OpenAPI spec path under stub section. This ensures that specmatic creates a stub for both those service dependencies using their OpenAPI specifications. \ No newline at end of file From 165523b748c6bf8fad197296d981f379c4e7fade Mon Sep 17 00:00:00 2001 From: Naresh Jain Date: Sat, 30 Dec 2023 21:23:45 +0530 Subject: [PATCH 09/14] removed specmatic folder as it was not required --- specmatic/newsletter.spec | 25 ------------------------- specmatic/run_tests.bat | 17 ----------------- specmatic/start_users_stub.bat | 1 - 3 files changed, 43 deletions(-) delete mode 100644 specmatic/newsletter.spec delete mode 100644 specmatic/run_tests.bat delete mode 100644 specmatic/start_users_stub.bat diff --git a/specmatic/newsletter.spec b/specmatic/newsletter.spec deleted file mode 100644 index d7d4dfe..0000000 --- a/specmatic/newsletter.spec +++ /dev/null @@ -1,25 +0,0 @@ -Feature: Newsletter API - -Background: - Given openapi ../newsletter/src/openapi.yaml - -Scenario Outline: AddUser - When GET /sayHello/Geoff - Then status 200 - Examples: - | name | - | xxxxx | - -Scenario Outline: BadUser - When GET /sayHello/Insecticide - Then status 400 - Examples: - | name | - | xxxxx | - -Scenario Outline: KnownUser - When GET /sayHello/Charles%20Darwin - Then status 200 - Examples: - | name | - | xxxxx | diff --git a/specmatic/run_tests.bat b/specmatic/run_tests.bat deleted file mode 100644 index c37932b..0000000 --- a/specmatic/run_tests.bat +++ /dev/null @@ -1,17 +0,0 @@ - - - -# run against newsletter openapi.yaml directly -C:\openjdk-20.0.2_windows-x64_bin\jdk-20.0.2\bin/java -jar C:\Users\Administrator\Downloads\specmatic.jar test --testBaseURL http://localhost:5010 .\newsletter\src\newsletter-openapi.yaml - -# run against newletter.spec -C:\openjdk-20.0.2_windows-x64_bin\jdk-20.0.2\bin/java -jar C:\Users\Administrator\Downloads\specmatic.jar test --testBaseURL http://localhost:5010 .\specmatic\newsletter.spec - -# run against greeting openapi -C:\openjdk-20.0.2_windows-x64_bin\jdk-20.0.2\bin/java -jar C:\Users\Administrator\Downloads\specmatic.jar test --testBaseURL http://localhost:5002 .\greeting\src\greeting-openapi.yaml - -# run against users openapi -C:\openjdk-20.0.2_windows-x64_bin\jdk-20.0.2\bin/java -jar C:\Users\Administrator\Downloads\specmatic.jar test --testBaseURL http://localhost:5001 .\users\src\users-openapi.yaml - - - diff --git a/specmatic/start_users_stub.bat b/specmatic/start_users_stub.bat deleted file mode 100644 index 821c5e1..0000000 --- a/specmatic/start_users_stub.bat +++ /dev/null @@ -1 +0,0 @@ -C:\openjdk-20.0.2_windows-x64_bin\jdk-20.0.2\bin/java -jar C:\Users\Administrator\Downloads\specmatic.jar stub .\users\src\users-openapi.yaml From 1ff7da51e3f4c402d36a878a34bc76895b977c6d Mon Sep 17 00:00:00 2001 From: Naresh Jain Date: Sun, 31 Dec 2023 10:37:13 +0530 Subject: [PATCH 10/14] cleaned up Contract test file to remove duplication and improve naming --- newsletter/test/test_contract.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/newsletter/test/test_contract.py b/newsletter/test/test_contract.py index 2c69f66..c4bfd42 100644 --- a/newsletter/test/test_contract.py +++ b/newsletter/test/test_contract.py @@ -1,7 +1,6 @@ -import pytest import os +import pytest from specmatic.core.specmatic import Specmatic - from src.newsletter import app ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -10,18 +9,19 @@ app_port = 5010 stub_host = "127.0.0.1" stub_port = 9000 +stub_url = 'http://' + stub_host + ':' + str(stub_port) -os.environ['USERS_URL'] = 'http://' + stub_host + ':' + str(stub_port) -os.environ['GREETING_URL'] = 'http://' + stub_host + ':' + str(stub_port) +os.environ['USERS_URL'] = stub_url +os.environ['GREETING_URL'] = stub_url -expectation_json_file = ROOT_DIR + '/test/data' +folder_with_stub_expectation_jsons = ROOT_DIR + '/test/data' class TestContract: pass Specmatic() \ .with_project_root(ROOT_DIR) \ - .with_stub(stub_host, stub_port, args=["--data="+expectation_json_file]) \ + .with_stub(stub_host, stub_port, args=["--data="+folder_with_stub_expectation_jsons]) \ .with_wsgi_app(app, app_host, app_port) \ .test(TestContract) \ .run() From 4e0f01304a334275b86b8fa1e9bb0df515c2f881 Mon Sep 17 00:00:00 2001 From: Naresh Jain Date: Tue, 2 Jan 2024 23:40:39 +0530 Subject: [PATCH 11/14] bumped up specmatic version and got rid of the stub expectations files, instead picking up the stub data from the spec itself --- .../known_user_greeting_expectations.json | 18 ------------------ .../unknown_user_greeting_expectations.json | 16 ---------------- .../data/user/blacklist_user_expectations.json | 13 ------------- .../data/user/known_user_expectations.json | 14 -------------- .../data/user/unknown_user_expectations.json | 14 -------------- newsletter/test/test_contract.py | 2 +- requirements.txt | 4 ++-- 7 files changed, 3 insertions(+), 78 deletions(-) delete mode 100644 newsletter/test/data/greeting/known_user_greeting_expectations.json delete mode 100644 newsletter/test/data/greeting/unknown_user_greeting_expectations.json delete mode 100644 newsletter/test/data/user/blacklist_user_expectations.json delete mode 100644 newsletter/test/data/user/known_user_expectations.json delete mode 100644 newsletter/test/data/user/unknown_user_expectations.json diff --git a/newsletter/test/data/greeting/known_user_greeting_expectations.json b/newsletter/test/data/greeting/known_user_greeting_expectations.json deleted file mode 100644 index 6e35254..0000000 --- a/newsletter/test/data/greeting/known_user_greeting_expectations.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "http-request": { - "path": "/formatGreeting", - "method": "GET", - "query": { - "title": "Miss.", - "name": "EmilyBache", - "description": "Independent Technical Coach, YouTuber, creator of Samman Coaching, Author" - } - }, - "http-response": { - "status": 200, - "body": "Hello, Miss. EmilyBache. Independent Technical Coach, YouTuber, creator of Samman Coaching, Author is my favourite person.", - "headers": { - "Content-Type": "text/html" - } - } -} \ No newline at end of file diff --git a/newsletter/test/data/greeting/unknown_user_greeting_expectations.json b/newsletter/test/data/greeting/unknown_user_greeting_expectations.json deleted file mode 100644 index 2d34f5f..0000000 --- a/newsletter/test/data/greeting/unknown_user_greeting_expectations.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "http-request": { - "path": "/formatGreeting", - "method": "GET", - "query": { - "name": "Unknown" - } - }, - "http-response": { - "status": 200, - "body": "Hello, Unknown!", - "headers": { - "Content-Type": "text/html" - } - } -} \ No newline at end of file diff --git a/newsletter/test/data/user/blacklist_user_expectations.json b/newsletter/test/data/user/blacklist_user_expectations.json deleted file mode 100644 index 5e65fad..0000000 --- a/newsletter/test/data/user/blacklist_user_expectations.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "http-request": { - "path": "/getPerson/DDT", - "method": "GET" - }, - "http-response": { - "status": 400, - "body": { - "detail": { "error": "Invalid input" }, - "message": "DDTs are not kind to bees." - } - } -} \ No newline at end of file diff --git a/newsletter/test/data/user/known_user_expectations.json b/newsletter/test/data/user/known_user_expectations.json deleted file mode 100644 index 4d3f2a8..0000000 --- a/newsletter/test/data/user/known_user_expectations.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "http-request": { - "path": "/getPerson/EmilyBache", - "method": "GET" - }, - "http-response": { - "status": 200, - "body": { - "description": "Independent Technical Coach, YouTuber, creator of Samman Coaching, Author", - "name": "EmilyBache", - "title": "Miss." - } - } -} \ No newline at end of file diff --git a/newsletter/test/data/user/unknown_user_expectations.json b/newsletter/test/data/user/unknown_user_expectations.json deleted file mode 100644 index 0df4035..0000000 --- a/newsletter/test/data/user/unknown_user_expectations.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "http-request": { - "path": "/getPerson/Unknown", - "method": "GET" - }, - "http-response": { - "status": 200, - "body": { - "description": null, - "name": "Unknown", - "title": null - } - } -} \ No newline at end of file diff --git a/newsletter/test/test_contract.py b/newsletter/test/test_contract.py index c4bfd42..57ae4ec 100644 --- a/newsletter/test/test_contract.py +++ b/newsletter/test/test_contract.py @@ -21,7 +21,7 @@ class TestContract: Specmatic() \ .with_project_root(ROOT_DIR) \ - .with_stub(stub_host, stub_port, args=["--data="+folder_with_stub_expectation_jsons]) \ + .with_stub(stub_host, stub_port) \ .with_wsgi_app(app, app_host, app_port) \ .test(TestContract) \ .run() diff --git a/requirements.txt b/requirements.txt index 829c45e..10b471a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ flask==2.2.5 -Werkzeug==2.3.7 +Werkzeug==2.3.8 apiflask Flask-Cors sqlalchemy @@ -10,4 +10,4 @@ capturemock pyodbc PyYAML pytest==7.3.1 -specmatic==0.23.0 \ No newline at end of file +specmatic==0.25.0 \ No newline at end of file From 31d970445d38085bf688be7e9a641898f410c67f Mon Sep 17 00:00:00 2001 From: Naresh Jain Date: Thu, 11 Jan 2024 11:09:47 +0530 Subject: [PATCH 12/14] Get route is technically not correct as it is not idempotent. So changed it to post route Added SPECMATIC_GENERATIVE_TESTS Running contract tests with API coverage turned on --- README.md | 2 ++ contracts/greeting-openapi.yaml | 22 ++++++++++-- contracts/newsletter-openapi.yaml | 49 +++++++++++++++++---------- contracts/users-openapi.yaml | 56 +++++++++++++++++++++---------- greeting/src/greeting.py | 7 +++- newsletter/src/newsletter.py | 36 ++++++++++++++------ newsletter/test/test_contract.py | 3 +- requirements.txt | 2 +- users/src/users.py | 21 +++++++++--- 9 files changed, 141 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index 09e5d6d..742715a 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,8 @@ When they are all running, you should be able to open a browser on the [Newslett ## Contract Testing with Specmatic To run the contract tests for newsletter service: + virtualenv -p /usr/bin/python3 venv + source venv/bin/activate cd newsletter pytest test -v -s diff --git a/contracts/greeting-openapi.yaml b/contracts/greeting-openapi.yaml index 1067658..40a21e3 100644 --- a/contracts/greeting-openapi.yaml +++ b/contracts/greeting-openapi.yaml @@ -31,6 +31,8 @@ paths: examples: SUCCESS: value: Independent Technical Coach, YouTuber, creator of Samman Coaching, Author + UNKNOWN: + value: "" - in: query name: name required: true @@ -38,7 +40,9 @@ paths: type: string examples: SUCCESS: - value: EmilyBache + value: Emily Bache + UNKNOWN: + value: Unknown - in: query name: title required: false @@ -47,6 +51,18 @@ paths: examples: SUCCESS: value: Miss. + UNKNOWN: + value: "" + - in: query + name: first_time_user + required: false + schema: + type: boolean + examples: + SUCCESS: + value: false + UNKNOWN: + value: true responses: '200': content: @@ -55,7 +71,9 @@ paths: type: string examples: SUCCESS: - value: Hello, Miss. EmilyBache. Independent Technical Coach, YouTuber, creator of Samman Coaching, Author is my favourite person. + value: Hello, Miss. Emily Bache. Independent Technical Coach, YouTuber, creator of Samman Coaching, Author is my favourite person. Welcome back! + UNKNOWN: + value: Hello, Unknown. Nice to meet you! description: Successful response '400': content: diff --git a/contracts/newsletter-openapi.yaml b/contracts/newsletter-openapi.yaml index e56475f..17ddfde 100644 --- a/contracts/newsletter-openapi.yaml +++ b/contracts/newsletter-openapi.yaml @@ -12,21 +12,28 @@ info: version: 0.1.0 openapi: 3.0.3 paths: - /sayHello/{name}: - get: - parameters: - - in: path - name: name - required: true - schema: - type: string - examples: - SUCCESS: - value: "EmilyBache" - BLACKLISTED: - value: "DDT" - Unknown: - value: "Unknown" + /sayHello: + post: + requestBody: + content: + application/json: + schema: + type: object + required: + - "name" + properties: + name: + type: string + examples: + SUCCESS: + value: + name: "Emily Bache" + UNKNOWN: + value: + name: "Unknown" + BLACKLISTED: + value: + name: "DDT" responses: '200': content: @@ -35,9 +42,9 @@ paths: type: string examples: SUCCESS: - value: "Hello, Miss. EmilyBache. Independent Technical Coach, YouTuber, creator of Samman Coaching, Author is my favourite person." - Unknown: - value: "Hello, Unknown." + value: "Hello, Miss. Emily Bache. Independent Technical Coach, YouTuber, creator of Samman Coaching, Author is my favourite person. Welcome back!" + UNKNOWN: + value: "Hello, Unknown. Nice to meet you!" description: Successful response '400': content: @@ -50,5 +57,11 @@ paths: detail: { "error": "Blacklisted name" } message: "Bad request" description: Bad request + '422': + content: + application/json: + schema: + $ref: '#/components/schemas/HTTPError' + description: Unprocessable entity summary: Say Hello tags: [] diff --git a/contracts/users-openapi.yaml b/contracts/users-openapi.yaml index a1588a5..0519ef9 100644 --- a/contracts/users-openapi.yaml +++ b/contracts/users-openapi.yaml @@ -20,29 +20,42 @@ components: title: nullable: true type: string + first_time_user: + type: boolean type: object - example: - description: "Software Engineer" - name: "John Doe" - title: "Mr." info: title: Users Service version: 0.1.0 openapi: 3.0.3 paths: - /getPerson/{name}: - get: - parameters: - - in: path - name: name - required: true - schema: - type: string - examples: - SUCCESS: - value: "EmilyBache" - BLACKLISTED: - value: "DDT" + /persons: + post: + requestBody: + content: + application/json: + schema: + type: object + required: + - "name" + properties: + name: + type: string + title: + nullable: true + type: string + description: + nullable: true + type: string + examples: + SUCCESS: + value: + name: "Emily Bache" + UNKNOWN: + value: + name: "Unknown" + BLACKLISTED: + value: + name: "DDT" responses: '200': content: @@ -53,8 +66,15 @@ paths: SUCCESS: value: description: "Independent Technical Coach, YouTuber, creator of Samman Coaching, Author" - name: "EmilyBache" + name: "Emily Bache" title: "Miss." + first_time_user: false + UNKNOWN: + value: + name: "Unknown" + first_time_user: true + description: "" + title: "" description: Successful response '400': content: diff --git a/greeting/src/greeting.py b/greeting/src/greeting.py index 3267437..85e1591 100644 --- a/greeting/src/greeting.py +++ b/greeting/src/greeting.py @@ -11,14 +11,15 @@ class PersonData(Schema): name = String(required=True) title = String() description = String() + first_time_user = bool() - @app.get("/formatGreeting") @app.input(PersonData, "query") def format_greeting(query_data): name = query_data.get('name') title = query_data.get('title') description = query_data.get('description') + first_time_user = query_data.get('first_time_user') greeting = 'Hello' greeting += ', ' @@ -27,6 +28,10 @@ def format_greeting(query_data): greeting += name + '!' if description: greeting += ' ' + description + " is my favourite!" + if first_time_user: + greeting += ' Nice to meet you!' + else: + greeting += ' Welcome back!' return greeting if __name__ == "__main__": diff --git a/newsletter/src/newsletter.py b/newsletter/src/newsletter.py index 8e45d51..99fe5bc 100644 --- a/newsletter/src/newsletter.py +++ b/newsletter/src/newsletter.py @@ -3,7 +3,8 @@ import requests from flask_cors import CORS, cross_origin -from apiflask import APIFlask, abort +from apiflask import APIFlask, abort, Schema +from apiflask.fields import String import yaml app = APIFlask('newsletter', title='Newsletter Service') @@ -15,20 +16,24 @@ 'requestInterceptor': interceptor } -@app.get("/sayHello/") + +class PersonIn(Schema): + name = String() + + +@app.post("/sayHello") +@app.input(PersonIn) @cross_origin() -def say_hello(name): - person = get_person(name) - resp = format_greeting(person) - return resp +def say_hello(json_data): + name = json_data.get('name') + person = fetch_or_create_person(name) + return format_greeting(person) -def get_person(name): +def fetch_or_create_person(name): users_url = os.getenv("USERS_URL", 'http://localhost:5001') - url = f'{users_url}/getPerson/{name}' - res = _get(url) - person = json.loads(res) - return person + url = f'{users_url}/persons' + return _post(url, person={'name': name}) def format_greeting(person): @@ -36,6 +41,15 @@ def format_greeting(person): url = greeting_url + '/formatGreeting' return _get(url, params=person) + +def _post(url, person): + r = requests.post(url, json=person) + data = json.loads(r.text) + if r.status_code != 200: + abort(r.status_code, data['message']) + return data + + def _get(url, params=None): r = requests.get(url, params=params) if r.status_code != 200: diff --git a/newsletter/test/test_contract.py b/newsletter/test/test_contract.py index 57ae4ec..710219f 100644 --- a/newsletter/test/test_contract.py +++ b/newsletter/test/test_contract.py @@ -13,6 +13,7 @@ os.environ['USERS_URL'] = stub_url os.environ['GREETING_URL'] = stub_url +os.environ['SPECMATIC_GENERATIVE_TESTS'] = "true" folder_with_stub_expectation_jsons = ROOT_DIR + '/test/data' @@ -23,7 +24,7 @@ class TestContract: .with_project_root(ROOT_DIR) \ .with_stub(stub_host, stub_port) \ .with_wsgi_app(app, app_host, app_port) \ - .test(TestContract) \ + .test_with_api_coverage_for_flask_app(TestContract, app) \ .run() if __name__ == '__main__': diff --git a/requirements.txt b/requirements.txt index 10b471a..746576f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,4 @@ capturemock pyodbc PyYAML pytest==7.3.1 -specmatic==0.25.0 \ No newline at end of file +specmatic==0.26.1 \ No newline at end of file diff --git a/users/src/users.py b/users/src/users.py index 469e7db..8c773ae 100644 --- a/users/src/users.py +++ b/users/src/users.py @@ -11,23 +11,34 @@ class PersonOut(Schema): name = String() title = String(allow_none=True) description = String(allow_none=True) + first_time_user = bool() -@app.get("/getPerson/") +class PersonIn(Schema): + name = String() + title = String(allow_none=True) + description = String(allow_none=True) + +@app.post("/persons") @app.output(PersonOut) -def get_person_http(name): +@app.input(PersonIn) +def get_person_http(json_data): + name = json_data.get('name') + first_time_user = False person = Person.get(name) if person is None: if name in ["Neonicotinoid", "Insecticide", "DDT"]: abort(400, f"{name}s are not kind to bees.") person = Person() person.name = name - person.save() - else: - person.description + person.title = json_data.get('title') + person.description = json_data.get('description') + person.save() + first_time_user = True response = { 'name': person.name, 'title': person.title, 'description': person.description, + 'first_time_user': first_time_user } return response From 9a047b3b7c7321c7ed0772bd1140035f22093bd0 Mon Sep 17 00:00:00 2001 From: Naresh Jain Date: Thu, 11 Jan 2024 11:34:54 +0530 Subject: [PATCH 13/14] added an UNPROCESSABLE example when name has a wrong data type --- contracts/newsletter-openapi.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/contracts/newsletter-openapi.yaml b/contracts/newsletter-openapi.yaml index 17ddfde..0875d3e 100644 --- a/contracts/newsletter-openapi.yaml +++ b/contracts/newsletter-openapi.yaml @@ -34,6 +34,9 @@ paths: BLACKLISTED: value: name: "DDT" + UNPROCESSABLE: + value: + name: 123 responses: '200': content: @@ -62,6 +65,11 @@ paths: application/json: schema: $ref: '#/components/schemas/HTTPError' + examples: + UNPROCESSABLE: + value: + detail: { "json": { name: ["Not a valid string."] } } + message: "Validation error" description: Unprocessable entity summary: Say Hello tags: [] From 89722273d739028ff8282f86402223c6414a5a51 Mon Sep 17 00:00:00 2001 From: Naresh Jain Date: Thu, 11 Jan 2024 17:07:16 +0530 Subject: [PATCH 14/14] Added API coverage report Upgraded Specmatic version --- contracts/newsletter-openapi.yaml | 8 -------- newsletter/specmatic.json | 24 +++++++++++++++++++++++- requirements.txt | 2 +- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/contracts/newsletter-openapi.yaml b/contracts/newsletter-openapi.yaml index 0875d3e..17ddfde 100644 --- a/contracts/newsletter-openapi.yaml +++ b/contracts/newsletter-openapi.yaml @@ -34,9 +34,6 @@ paths: BLACKLISTED: value: name: "DDT" - UNPROCESSABLE: - value: - name: 123 responses: '200': content: @@ -65,11 +62,6 @@ paths: application/json: schema: $ref: '#/components/schemas/HTTPError' - examples: - UNPROCESSABLE: - value: - detail: { "json": { name: ["Not a valid string."] } } - message: "Validation error" description: Unprocessable entity summary: Say Hello tags: [] diff --git a/newsletter/specmatic.json b/newsletter/specmatic.json index fc5b15c..45be4ed 100644 --- a/newsletter/specmatic.json +++ b/newsletter/specmatic.json @@ -10,5 +10,27 @@ "../contracts/users-openapi.yaml" ] } - ] + ], + "report": { + "formatters": [ + { + "type": "text", + "layout": "table" + } + ], + "types": { + "APICoverage": { + "OpenAPI": { + "successCriteria": { + "minThresholdPercentage": 100, + "maxMissedEndpointsInSpec": 0, + "enforce": true + }, + "excludedEndpoints": [ + "/docs", "/docs/oauth2-redirect", "/openapi.json" + ] + } + } + } + } } \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 746576f..9b6afd5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,4 @@ capturemock pyodbc PyYAML pytest==7.3.1 -specmatic==0.26.1 \ No newline at end of file +specmatic==0.26.3 \ No newline at end of file