From 3021e6d2bfcb9f7b75db5ba048c034f33651798b Mon Sep 17 00:00:00 2001 From: michaelboulton Date: Sat, 13 Apr 2024 19:21:05 +0100 Subject: [PATCH] Allow using referenced 'finally' stages (#929) --- tavern/_core/run.py | 45 ++++++++++++++----- tavern/_core/schema/tests.jsonschema.yaml | 4 +- tests/integration/global_cfg.yaml | 9 ++++ .../integration/test_control_flow.tavern.yaml | 19 ++++++++ tests/unit/test_core.py | 19 ++++++++ 5 files changed, 83 insertions(+), 13 deletions(-) diff --git a/tavern/_core/run.py b/tavern/_core/run.py index 91474d38c..1478331e3 100644 --- a/tavern/_core/run.py +++ b/tavern/_core/run.py @@ -30,10 +30,25 @@ logger: logging.Logger = logging.getLogger(__name__) -def _resolve_test_stages(test_spec: Mapping, available_stages: Mapping): +def _resolve_test_stages( + stages: List[Mapping], available_stages: Mapping +) -> List[Mapping]: + """Looks for 'ref' stages in the given stages and returns any resolved stages + + Args: + stages: list of stages to possibly replace + available_stages: included stages to possibly use in replacement + + Returns: + list of stages that were included, if any + """ # Need to get a final list of stages in the tests (resolving refs) test_stages = [] - for raw_stage in test_spec["stages"]: + + if not isinstance(stages, list): + raise exceptions.BadSchemaError("stages should have been a list") + + for raw_stage in stages: stage = raw_stage if stage.get("type") == "ref": if "id" in stage: @@ -67,7 +82,7 @@ def _get_included_stages( for use in this test Args: - tavern_box: Available parameters for fomatting at this point + tavern_box: Available parameters for formatting at this point test_block_config: Current test config dictionary test_spec: Specification for current test available_stages: List of stages which already exist @@ -151,7 +166,8 @@ def run_test( tavern_box, test_block_config, test_spec, available_stages ) all_stages = {s["id"]: s for s in available_stages + included_stages} - test_spec["stages"] = _resolve_test_stages(test_spec, all_stages) + test_spec["stages"] = _resolve_test_stages(test_spec["stages"], all_stages) + finally_stages = _resolve_test_stages(test_spec.get("finally", []), all_stages) test_block_config.variables["tavern"] = tavern_box["tavern"] @@ -194,17 +210,22 @@ def getonly(stage): if getonly(stage): break finally: - finally_stages = test_spec.get("finally", []) - if not isinstance(finally_stages, list): - raise exceptions.BadSchemaError( - f"finally block should be a list of dicts but was {type(finally_stages)}" + if finally_stages: + logger.info( + "Running finally stages: %s", [s["name"] for s in finally_stages] ) - for idx, stage in enumerate(finally_stages): - if not isinstance(stage, dict): + if not isinstance(finally_stages, list): raise exceptions.BadSchemaError( - f"finally block should be a dict but was {type(stage)}" + f"finally block should be a list of dicts but was {type(finally_stages)}" ) - runner.run_stage(idx, stage, is_final=True) + for idx, stage in enumerate(finally_stages): + if not isinstance(stage, dict): + raise exceptions.BadSchemaError( + f"finally block should be a dict but was {type(stage)}" + ) + runner.run_stage(idx, stage, is_final=True) + else: + logger.debug("no 'finally' stages to run") def _calculate_stage_strictness( diff --git a/tavern/_core/schema/tests.jsonschema.yaml b/tavern/_core/schema/tests.jsonschema.yaml index 6e426ec8e..371f42f39 100644 --- a/tavern/_core/schema/tests.jsonschema.yaml +++ b/tavern/_core/schema/tests.jsonschema.yaml @@ -512,4 +512,6 @@ properties: description: Stages to run after test finishes items: - $ref: "#/definitions/stage" + oneOf: + - $ref: "#/definitions/stage" + - $ref: "#/definitions/stage_ref" diff --git a/tests/integration/global_cfg.yaml b/tests/integration/global_cfg.yaml index 386ef35e4..401d52117 100644 --- a/tests/integration/global_cfg.yaml +++ b/tests/integration/global_cfg.yaml @@ -9,3 +9,12 @@ variables: retry_max: 4 negative_int: -2 + +stages: + - id: finally-nothing-check + name: finally nothing check + request: + url: "{global_host}/echo" + method: POST + json: + value: "123" diff --git a/tests/integration/test_control_flow.tavern.yaml b/tests/integration/test_control_flow.tavern.yaml index dbcdfcf14..1a3d54c47 100644 --- a/tests/integration/test_control_flow.tavern.yaml +++ b/tests/integration/test_control_flow.tavern.yaml @@ -21,6 +21,25 @@ finally: json: value: "123" +--- +test_name: Test finally block being replaced + +stages: + - name: Simple echo + request: + url: "{global_host}/echo" + method: POST + json: + value: "123" + response: + status_code: 200 + json: + value: "123" + +finally: + - type: ref + id: finally-nothing-check + --- test_name: Test finally block fail diff --git a/tests/unit/test_core.py b/tests/unit/test_core.py index e930d813e..ad67a39fa 100644 --- a/tests/unit/test_core.py +++ b/tests/unit/test_core.py @@ -162,6 +162,25 @@ def test_included_stage(self, fulltest, mockargs, includes, fake_stages): self.check_mocks_called(pmock) + def test_included_finally_stage(self, fulltest, mockargs, includes, fake_stages): + """Load stage from includes""" + mock_response = Mock(**mockargs) + + stage_includes = [{"stages": fake_stages}] + + newtest = deepcopy(fulltest) + newtest["includes"] = stage_includes + newtest["finally"] = [{"type": "ref", "id": "my_external_stage"}] + + with patch( + "tavern._plugins.rest.request.requests.Session.request", + return_value=mock_response, + ) as pmock: + run_test("bloo", newtest, includes) + + pmock.call_args_list = list(reversed(pmock.call_args_list)) + self.check_mocks_called(pmock) + def test_global_stage(self, fulltest, mockargs, includes, fake_stages): """Load stage from global config""" mock_response = Mock(**mockargs)