From 9291b677b256f9bd7e7623e9b1cb176f8165e0ed Mon Sep 17 00:00:00 2001 From: Joel Klinger Date: Wed, 27 Dec 2023 16:21:27 +0000 Subject: [PATCH] [feature/PI-129-postman_collection] Locally generated postman collection --- feature_tests/end_to_end/environment.py | 93 ++++++++++--------- .../features/createDevice.failure.feature | 1 + .../features/createDevice.success.feature | 1 + .../createProductTeam.failure.feature | 1 + .../createProductTeam.success.feature | 1 + .../features/headers.failure.feature | 1 + .../features/headers.success.feature | 1 + .../features/readDevice.failure.feature | 1 + .../features/readDevice.success.feature | 1 + .../features/readProductTeam.failure.feature | 1 + .../features/readProductTeam.success.feature | 1 + .../features/status.success.feature | 1 + feature_tests/end_to_end/steps/context.py | 18 ++-- feature_tests/end_to_end/steps/postman.py | 36 ++++--- feature_tests/end_to_end/steps/requests.py | 3 +- .../steps/tests/test_mock_requests.py | 4 +- scripts/builder/build.mk | 2 + 17 files changed, 98 insertions(+), 69 deletions(-) diff --git a/feature_tests/end_to_end/environment.py b/feature_tests/end_to_end/environment.py index 4b5932872..5cae86f11 100644 --- a/feature_tests/end_to_end/environment.py +++ b/feature_tests/end_to_end/environment.py @@ -1,4 +1,5 @@ -import json +from contextlib import nullcontext +from pathlib import Path from behave import use_fixture from behave.model import Feature, Scenario, Step @@ -11,74 +12,78 @@ mock_requests, ) from feature_tests.end_to_end.steps.postman import ( - FeatureItem, + BASE_URL, PostmanCollection, - ScenarioItem, - StepItem, + PostmanItem, ) from feature_tests.feature_test_helpers import TestMode from test_helpers.aws_session import aws_session -from test_helpers.constants import PROJECT_ROOT from test_helpers.dynamodb import clear_dynamodb_table from test_helpers.terraform import read_terraform_output +PATH_TO_HERE = Path(__file__).parent +LOCAL_TABLE_NAME = "my-table" +GENERIC_SCENARIO_DESCRIPTION = "The following steps demonstrate this scenario:" + def before_all(context: Context): context.postman_collection = PostmanCollection() + context.test_mode = TestMode.parse(config=context.config) + context.table_name = LOCAL_TABLE_NAME + context.base_url = BASE_URL + context.session = nullcontext + context.headers = {} - test_mode = TestMode.parse(config=context.config) - if test_mode is not TestMode.INTEGRATION: - use_fixture(mock_requests, context=context) - use_fixture(mock_dynamodb, context=context, table_name="my-table") - use_fixture(mock_environment, context=context, table_name="my-table") - - -def after_all(context: Context): - test_mode = TestMode.parse(config=context.config) + if context.test_mode is TestMode.INTEGRATION: + context.table_name = read_terraform_output("dynamodb_table_name.value") + context.base_url = read_terraform_output("invoke_url.value") + "/" + context.session = aws_session - if context.postman_collection.item and test_mode is TestMode.INTEGRATION: - postman_collection = context.postman_collection.dict( - exclude_none=True, by_alias=True - ) - with open(PROJECT_ROOT / "postman-collection.json", "w") as f: - json.dump(fp=f, obj=postman_collection, indent=4) + if context.test_mode is TestMode.LOCAL: + use_fixture(mock_requests, context=context) + use_fixture(mock_dynamodb, context=context, table_name=context.table_name) + use_fixture(mock_environment, context=context, table_name=context.table_name) def before_feature(context: Context, feature: Feature): - context.postman_feature = FeatureItem(name=feature.name) + context.postman_feature = PostmanItem( + name=feature.name, + description=" ".join(feature.description), + ) -def after_feature(context: Context, scenario: Scenario): - context.postman_collection.item.append(context.postman_feature) +def before_scenario(context: Context, scenario: Scenario): + context.postman_scenario = PostmanItem( + name=scenario.name, + description=GENERIC_SCENARIO_DESCRIPTION, + ) + with context.session(): + client = dynamodb_client() + clear_dynamodb_table(client=client, table_name=context.table_name) -def before_scenario(context: Context, scenario: Scenario): - context.postman_scenario = ScenarioItem(name=scenario.name) +def before_step(context: Context, step: Step): + context.postman_step = PostmanItem( + name=f"{step.keyword.lower().title()} {step.name}", item=None + ) - context.headers = {} - test_mode = TestMode.parse(config=context.config) - if test_mode is TestMode.INTEGRATION: - table_name = read_terraform_output("dynamodb_table_name.value") - context.base_url = read_terraform_output("invoke_url.value") + "/" - with aws_session(): - client = dynamodb_client() - clear_dynamodb_table(client=client, table_name=table_name) - else: - context.base_url = "" - client = dynamodb_client() - clear_dynamodb_table(client=client, table_name="my-table") +def after_step(context: Context, step: Step): + context.table = None + if context.postman_step: + context.postman_scenario.item.append(context.postman_step) def after_scenario(context: Context, scenario: Scenario): - context.postman_feature.item.append(context.postman_scenario) + context.headers = {} + if context.postman_scenario: + context.postman_feature.item.append(context.postman_scenario) -def before_step(context: Context, step: Step): - context.postman_step = StepItem(name=f"{step.keyword.lower().title()} {step.name}") +def after_feature(context: Context, feature: Feature): + if context.postman_feature: + context.postman_collection.item.append(context.postman_feature) -def after_step(context: Context, step: Step): - if context.postman_step.request is not None: - context.postman_scenario.item.append(context.postman_step) - context.table = None +def after_all(context: Context): + context.postman_collection.save(path=PATH_TO_HERE) diff --git a/feature_tests/end_to_end/features/createDevice.failure.feature b/feature_tests/end_to_end/features/createDevice.failure.feature index 5a3ffad73..01c0f01ce 100644 --- a/feature_tests/end_to_end/features/createDevice.failure.feature +++ b/feature_tests/end_to_end/features/createDevice.failure.feature @@ -1,4 +1,5 @@ Feature: Create Device - failure scenarios + These scenarios demonstrate failures to create a new Device Background: Given "default" request headers: diff --git a/feature_tests/end_to_end/features/createDevice.success.feature b/feature_tests/end_to_end/features/createDevice.success.feature index c2ca02f0a..f227a73c6 100644 --- a/feature_tests/end_to_end/features/createDevice.success.feature +++ b/feature_tests/end_to_end/features/createDevice.success.feature @@ -1,4 +1,5 @@ Feature: Create Device - success scenarios + These scenarios demonstrate successful Device creation Background: Given "default" request headers: diff --git a/feature_tests/end_to_end/features/createProductTeam.failure.feature b/feature_tests/end_to_end/features/createProductTeam.failure.feature index 2f99ebacc..d96e3b1f1 100644 --- a/feature_tests/end_to_end/features/createProductTeam.failure.feature +++ b/feature_tests/end_to_end/features/createProductTeam.failure.feature @@ -1,4 +1,5 @@ Feature: Create Product Team - failure scenarios + These scenarios demonstrate failures to create a new Product Team (FHIR "Organization") Background: Given "default" request headers: diff --git a/feature_tests/end_to_end/features/createProductTeam.success.feature b/feature_tests/end_to_end/features/createProductTeam.success.feature index 2edd0c30a..914d3461a 100644 --- a/feature_tests/end_to_end/features/createProductTeam.success.feature +++ b/feature_tests/end_to_end/features/createProductTeam.success.feature @@ -1,4 +1,5 @@ Feature: Create Product Team - success scenarios + These scenarios demonstrate successful Product Team (FHIR "Organization") creation Background: Given "default" request headers: diff --git a/feature_tests/end_to_end/features/headers.failure.feature b/feature_tests/end_to_end/features/headers.failure.feature index b6aed2b79..c7667e73a 100644 --- a/feature_tests/end_to_end/features/headers.failure.feature +++ b/feature_tests/end_to_end/features/headers.failure.feature @@ -1,4 +1,5 @@ Feature: Headers - failure scenarios + These scenarios demonstrate invalid header values Scenario: Version is missing Given "bad" request headers: diff --git a/feature_tests/end_to_end/features/headers.success.feature b/feature_tests/end_to_end/features/headers.success.feature index 793fb81fa..109261bf6 100644 --- a/feature_tests/end_to_end/features/headers.success.feature +++ b/feature_tests/end_to_end/features/headers.success.feature @@ -1,4 +1,5 @@ Feature: Headers - success scenarios + These scenarios demonstrate valid header values, and associated behaviour Scenario Outline: Headers are case insensitive Given "default" request headers: diff --git a/feature_tests/end_to_end/features/readDevice.failure.feature b/feature_tests/end_to_end/features/readDevice.failure.feature index efd477e45..11ec4e9e7 100644 --- a/feature_tests/end_to_end/features/readDevice.failure.feature +++ b/feature_tests/end_to_end/features/readDevice.failure.feature @@ -1,4 +1,5 @@ Feature: Read Device - failure scenarios + These scenarios demonstrate failures from the GET Device endpoint Background: Given "default" request headers: diff --git a/feature_tests/end_to_end/features/readDevice.success.feature b/feature_tests/end_to_end/features/readDevice.success.feature index 4aed205f6..a2d2bc9ed 100644 --- a/feature_tests/end_to_end/features/readDevice.success.feature +++ b/feature_tests/end_to_end/features/readDevice.success.feature @@ -1,4 +1,5 @@ Feature: Read Device - success scenarios + These scenarios demonstrate successful reads from the GET Device endpoint Background: Given "default" request headers: diff --git a/feature_tests/end_to_end/features/readProductTeam.failure.feature b/feature_tests/end_to_end/features/readProductTeam.failure.feature index cd16d31a4..34e454664 100644 --- a/feature_tests/end_to_end/features/readProductTeam.failure.feature +++ b/feature_tests/end_to_end/features/readProductTeam.failure.feature @@ -1,4 +1,5 @@ Feature: Read Product Team - failure scenarios + These scenarios demonstrate failures from the GET Organization (i.e. Product Team) endpoint Background: Given "default" request headers: diff --git a/feature_tests/end_to_end/features/readProductTeam.success.feature b/feature_tests/end_to_end/features/readProductTeam.success.feature index 74926270b..758cfa8e5 100644 --- a/feature_tests/end_to_end/features/readProductTeam.success.feature +++ b/feature_tests/end_to_end/features/readProductTeam.success.feature @@ -1,4 +1,5 @@ Feature: Read Product Team - success scenarios + These scenarios demonstrate successful reads from the GET Organization (i.e. Product Team) endpoint Background: Given "default" request headers: diff --git a/feature_tests/end_to_end/features/status.success.feature b/feature_tests/end_to_end/features/status.success.feature index bf4d82cdf..37e763c28 100644 --- a/feature_tests/end_to_end/features/status.success.feature +++ b/feature_tests/end_to_end/features/status.success.feature @@ -1,4 +1,5 @@ Feature: Status + These scenarios demonstrate the expected behaviour of the status endpoint Background: Given "default" request headers: diff --git a/feature_tests/end_to_end/steps/context.py b/feature_tests/end_to_end/steps/context.py index 88687d719..28563b15e 100644 --- a/feature_tests/end_to_end/steps/context.py +++ b/feature_tests/end_to_end/steps/context.py @@ -1,15 +1,12 @@ from dataclasses import dataclass +from typing import ContextManager from behave.model import Table from behave.runner import Context as BehaveContext from requests import Response -from feature_tests.end_to_end.steps.postman import ( - FeatureItem, - PostmanCollection, - ScenarioItem, - StepItem, -) +from feature_tests.end_to_end.steps.postman import PostmanCollection, PostmanItem +from feature_tests.feature_test_helpers import TestMode @dataclass @@ -18,8 +15,11 @@ class Context(BehaveContext): headers: dict[str, dict[str, str]] = None response: Response = None table: Table = None + test_mode: TestMode = None + table_name: str = None + session: ContextManager = None postman_collection: PostmanCollection = None - postman_feature: FeatureItem = None - postman_scenario: ScenarioItem = None - postman_step: StepItem = None + postman_feature: PostmanItem = None + postman_scenario: PostmanItem = None + postman_step: PostmanItem = None diff --git a/feature_tests/end_to_end/steps/postman.py b/feature_tests/end_to_end/steps/postman.py index efe1a19e2..cb6558c21 100644 --- a/feature_tests/end_to_end/steps/postman.py +++ b/feature_tests/end_to_end/steps/postman.py @@ -1,7 +1,11 @@ +import json +from pathlib import Path from typing import Literal, Optional from pydantic import BaseModel, Field +POSTMAN_COLLECTION_FILENAME = "postman-collection.json" +BASE_URL = r"{{baseUrl}}/" OPTIONS = lambda: {"raw": {"language": "json"}} @@ -30,31 +34,37 @@ class PostmanRequest(BaseModel): url: Url -class StepItem(BaseModel): +class PostmanItem(BaseModel): name: str - request: PostmanRequest = None + description: str = Field(default_factory=str) + request: Optional[PostmanRequest] = None + item: None | list["PostmanItem"] = Field(default_factory=list) - -class ScenarioItem(BaseModel): - name: str - item: list[StepItem] = Field(default_factory=list) - - -class FeatureItem(BaseModel): - name: str - item: list[ScenarioItem] = Field(default_factory=list) + def __bool__(self): + return bool(self.item) or bool(self.request) class Info(BaseModel): - name: Literal["connecting-party-manager"] = "connecting-party-manager" + name: Literal["Feature Tests"] = "Feature Tests" the_schema: Literal[ "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" ] = Field( default="https://schema.getpostman.com/json/collection/v2.1.0/collection.json", alias="schema", ) + description: Literal[ + "The following 'Gherkin' Features demonstrate the behaviour of this API" + ] = "The following 'Gherkin' Features demonstrate the behaviour of this API" class PostmanCollection(BaseModel): info: Info = Field(default_factory=Info) - item: list[FeatureItem] = Field(default_factory=list) + item: list[PostmanItem] = Field(default_factory=list) + + def save(self, path: Path): + collection = self.dict(exclude_none=True, by_alias=True) + with open(path / POSTMAN_COLLECTION_FILENAME, "w") as f: + json.dump(fp=f, obj=collection, indent=4) + + def __bool__(self): + return bool(self.item) diff --git a/feature_tests/end_to_end/steps/requests.py b/feature_tests/end_to_end/steps/requests.py index e7c96323f..26d6dede0 100644 --- a/feature_tests/end_to_end/steps/requests.py +++ b/feature_tests/end_to_end/steps/requests.py @@ -67,9 +67,10 @@ def _mocked_request( ): """Implement the desired mocked behaviour of the 'request' function""" endpoint_lambda_mapping = get_endpoint_lambda_mapping() + _, path = url.split(sep="/", maxsplit=1) path_params, query_params, handler = parse_api_path( method=method, - path=url, + path=path, endpoint_lambda_mapping=endpoint_lambda_mapping, ) diff --git a/feature_tests/end_to_end/steps/tests/test_mock_requests.py b/feature_tests/end_to_end/steps/tests/test_mock_requests.py index d44b60f46..c1cf32c74 100644 --- a/feature_tests/end_to_end/steps/tests/test_mock_requests.py +++ b/feature_tests/end_to_end/steps/tests/test_mock_requests.py @@ -21,7 +21,7 @@ def test__mock_requests(): }, ): response = make_request( - base_url="my_url/my_id/my_something", + base_url="BASE_URL/my_url/my_id/my_something", http_method="GET", endpoint="/the_endpoint", body={"key": "value"}, @@ -36,5 +36,5 @@ def test__mock_requests(): }, "status_code": 200, "reason": "OK", - "url": "my_url/my_id/my_something/the_endpoint", + "url": "BASE_URL/my_url/my_id/my_something/the_endpoint", } diff --git a/scripts/builder/build.mk b/scripts/builder/build.mk index 5f3df6183..80724351f 100644 --- a/scripts/builder/build.mk +++ b/scripts/builder/build.mk @@ -1,10 +1,12 @@ .PHONY: build BUILD_TIMESTAMP = $(TIMESTAMP_DIR)/.build.stamp +POSTMAN_COLLECTION = $(CURDIR)/feature_tests/end_to_end/postman-collection.json clean: poetry--clean swagger--clean fhir--models--clean ## Complete clear-out of the project installation and artifacts [[ -d $(TIMESTAMP_DIR) ]] && rm -r $(TIMESTAMP_DIR) || : [[ -d $(DOWNLOADS_DIR) ]] && rm -r $(DOWNLOADS_DIR) || : + [[ -f $(POSTMAN_COLLECTION) ]] && rm $(POSTMAN_COLLECTION) || : clean--build: [[ -f $(BUILD_TIMESTAMP) ]] && rm $(BUILD_TIMESTAMP) || :