def test_given_bad_condition(mocker, condition_verifications, expected_status): mocker.patch(f"{PKG}.now", return_value=sentinel.starts) def _analyze_context(context: Context) -> Analyzer: assert context == Context(foo="bar", starts=sentinel.starts, base_url=sentinel.base_url) return sentinel.context_analyzer analyze_context = mocker.patch(f"{PKG}.MappingAnalyzer", side_effect=_analyze_context) def _verify( verification: Verification, analyzer: Analyzer, context: Optional[Context] = None, ) -> Verification: assert analyzer is sentinel.context_analyzer assert context == Context(foo="bar", starts=sentinel.starts, base_url=sentinel.base_url) return verification conditions = [ NonCallableMock(Description, verify=Mock(side_effect=partial(_verify, v))) for v in condition_verifications ] case = Case( label=sentinel.label, conditions=conditions, request=sentinel.request, response=sentinel.response, ) unit_runner = NonCallableMock(UnitRunner, base_url=sentinel.base_url) listener = NonCallableMock(CaseListener) runner = CaseRunner(unit_runner=unit_runner, listener=listener) result = runner.run(case, context=Context(foo="bar")) assert result.label is sentinel.label assert result.status is expected_status # Contextual values will disappear. for condition in conditions: condition.verify.assert_called_once_with(sentinel.context_analyzer, Context(foo="bar")) analyze_context.assert_called_once_with(Context(foo="bar")) unit_runner.run.assert_not_called() listener.on_execution.assert_not_called()
def test_when_given_an_response(mocker): mocker.patch(f"{PKG}.now", return_value=sentinel.starts) execution = ExecutionReport(status=Status.SUCCESS, starts=sentinel.starts) verification = ResponseVerification( response_id=sentinel.response_id, status_code=Verification.succeed(), headers=Verification.succeed(), body=Verification(status=Status.UNSTABLE), ) case = Case(label=sentinel.label, request=sentinel.request, response=sentinel.response) def _run_unit( request: Request, requirements: ResponseDescription, session: Optional[requests.Session] = None, context: Optional[Context] = None, ) -> Result: assert request is sentinel.request assert requirements is sentinel.response assert session is sentinel.session assert context == Context(foo="bar", starts=sentinel.starts, base_url=sentinel.base_url) return execution, sentinel.response, verification unit_runner = NonCallableMock(UnitRunner, base_url=sentinel.base_url) unit_runner.run.side_effect = Mock(side_effect=_run_unit) listener = NonCallableMock(spec=CaseListener) runner = CaseRunner(unit_runner=unit_runner, listener=listener) result = runner.run(case, session=sentinel.session, context=Context(foo="bar")) assert result.label is sentinel.label assert result.status is Status.UNSTABLE assert result.execution is execution assert result.response is verification # Contextual values will disappear. unit_runner.run.assert_called_once_with( request=sentinel.request, requirements=sentinel.response, session=sentinel.session, context=Context(foo="bar"), ) listener.on_execution.assert_called_once_with(execution, sentinel.response)
def test_ordered(mocker): mocker.patch(f"{PKG}.now", side_effect=[sentinel.starts1, sentinel.starts2]) cases_task_ctor = mocker.patch(f"{PKG}.OrderedCasesTask", return_value=sentinel.cases_task) task_ctor = mocker.patch(f"{PKG}.RunningScenarioTask", return_value=sentinel.task) sentinel.context.starts = sentinel.starts subscenario = Scenario(label=sentinel.subscenario_label) scenario = Scenario(label=sentinel.label, cases=sentinel.cases, subscenarios=[subscenario]) case_runner = NonCallableMock(CaseRunner, base_url=sentinel.base_url) runner = ScenarioRunner(executor=sentinel.executor, case_runner=case_runner) task = runner.submit(scenario) assert task is sentinel.task cases_task_ctor.assert_has_calls( [ call( sentinel.executor, case_runner, sentinel.cases, context=Context(starts=sentinel.starts1, base_url=sentinel.base_url), ), call( sentinel.executor, case_runner, [], context=Context(starts=sentinel.starts2, base_url=sentinel.base_url), ), ] ) task_ctor.assert_has_calls( [ call( label=sentinel.subscenario_label, conditions=Verification.collect([]), cases=sentinel.cases_task, subscenarios=[], ), call( label=sentinel.label, conditions=Verification(status=Status.SKIPPED, children=[]), cases=sentinel.cases_task, subscenarios=[sentinel.task], ), ] )
def test_given_no_response(mocker): retry = mocker.patch(f"{PKG}.retry_while_false", side_effect=_retry) requester = NonCallableMock(Requester) requester.base_url = sentinel.requester_base_url requester.execute.return_value = (sentinel.execution, None) requirements = NonCallableMock(ResponseDescription) runner = UnitRunner(requester) assert runner.base_url is sentinel.requester_base_url execution, response, verification = runner.run(sentinel.request, requirements) assert execution is sentinel.execution assert response is None assert verification is None requester.execute.assert_called_once_with(sentinel.request, session=None, context=Context()) requirements.verify.assert_not_called() retry.assert_called_once_with(ANY, attempts=1, delay=0.1, predicate=predicate)
def test_closed_context(): context = Context( control=sentinel.control, normal=sentinel.normal_out_of_context, deleted=sentinel.deleted_out_of_context, overwritten=sentinel.overwritten_out_of_context, ) with closed_context( context, normal=sentinel.normal_in_context, deleted=sentinel.deleted_in_context, overwritten=sentinel.overwritten_in_context, contextual=sentinel.contextual, ) as context: assert context["control"] is sentinel.control assert context["normal"] is sentinel.normal_in_context assert context["deleted"] is sentinel.deleted_in_context assert context["overwritten"] is sentinel.overwritten_in_context assert context["contextual"] is sentinel.contextual del context["deleted"] context["overwritten"] = sentinel.overwritten_new context["not_contextual"] = sentinel.not_contextual assert context["control"] is sentinel.control assert context["normal"] is sentinel.normal_out_of_context assert context["deleted"] is sentinel.deleted_out_of_context assert context["overwritten"] is sentinel.overwritten_out_of_context assert context["not_contextual"] is sentinel.not_contextual assert "contextual" not in context
def test_given_not_satisfied_conditions(mocker, statuses, expected_status): mocker.patch(f"{PKG}.now", return_value=sentinel.starts) analyze_context = mocker.patch(f"{PKG}.MappingAnalyzer") analyze_context.return_value = sentinel.context_analyzer ordered_cases_task_ctor = mocker.patch(f"{PKG}.OrderedCasesTask") unordered_cases_task_ctor = mocker.patch(f"{PKG}.UnorderedCasesTask") task_ctor = mocker.patch(f"{PKG}.StaticScenarioTask", return_value=sentinel.task) verifications = [Verification(status) for status in statuses] conditions = [NonCallableMock(Description, verify=Mock(return_value=v)) for v in verifications] scenario = Scenario( label=sentinel.label, conditions=conditions, cases=sentinel.cases, subscenarios=[sentinel.subscenario], ) case_runner = NonCallableMock(CaseRunner, base_url=sentinel.base_url) runner = ScenarioRunner(executor=sentinel.executor, case_runner=case_runner) task = runner.submit(scenario) assert task is sentinel.task result = task_ctor.call_args[0][0] assert result.label is sentinel.label assert result.status is expected_status assert result.conditions.children == verifications assert result.cases.status is Status.SKIPPED assert not result.cases.items assert result.subscenarios.status is Status.SKIPPED assert not result.subscenarios.items for condition in conditions: condition.verify.assert_called_once_with( sentinel.context_analyzer, Context(starts=sentinel.starts, base_url=sentinel.base_url), ) analyze_context.assert_called_once_with( Context(starts=sentinel.starts, base_url=sentinel.base_url) ) ordered_cases_task_ctor.assert_not_called() unordered_cases_task_ctor.assert_not_called()
def _verify( verification: Verification, analyzer: Analyzer, context: Optional[Context] = None, ) -> Verification: assert analyzer is sentinel.context_analyzer assert context == Context(foo="bar", starts=sentinel.starts, base_url=sentinel.base_url) return verification
def test_given_a_response(mocker): retry = mocker.patch(f"{PKG}.retry_while_false", side_effect=_retry) execution = ExecutionReport(starts=sentinel.starts) requester = NonCallableMock(Requester) requester.base_url = sentinel.requester_base_url requester.execute.return_value = (execution, sentinel.response) def _verify(analyzer: Analyzer, context: Optional[Context] = None) -> Verification: assert analyzer is sentinel.response assert context == Context(foo="bar", starts=sentinel.starts) return sentinel.verification requirements = NonCallableMock(ResponseDescription, verify=Mock(side_effect=_verify)) runner = UnitRunner(requester=requester, retry=3, delay=sentinel.delay) assert runner.base_url is sentinel.requester_base_url execution, response, verification = runner.run( sentinel.request, requirements, sentinel.session, context=Context(foo="bar"), ) assert execution is execution assert response is sentinel.response assert verification is sentinel.verification requester.execute.assert_called_with( sentinel.request, session=sentinel.session, context=Context(foo="bar"), ) # Contextual values will disappear. requirements.verify.assert_called_with(sentinel.response, Context(foo="bar")) retry.assert_called_once_with(ANY, attempts=4, delay=sentinel.delay, predicate=ANY)
def _run_unit( request: Request, requirements: ResponseDescription, session: Optional[requests.Session] = None, context: Optional[Context] = None, ) -> Result: assert request is sentinel.request assert requirements is sentinel.response assert session is None assert context == Context(starts=sentinel.starts, base_url=sentinel.base_url) return execution, None, None
def execute( self, request: Request, session: Optional[requests.Session] = None, context: Optional[Context] = None, ) -> Tuple[ExecutionReport, Optional[Response]]: """ Executes a request. Args: request: A request. session: A session object to execute. context: Execution context. Returns: A tuple of execution report and response. When there is no response, the response will be ``None``. """ if session is None: with requests.Session() as new_session: return self.execute(request, session=new_session) context = context if context is not None else Context() starts = now() report = ExecutionReport(starts=starts) try: with closed_context(context, starts=starts) as context: prepped = self._prepare_request(request, context) proxies = session.rebuild_proxies(prepped, proxies=None) except Exception as error: message = to_message(error) report = replace(report, status=Status.FAILURE, message=message) return report, None report = replace( report, request=PreparedRequest( method=prepped.method or "", url=prepped.url or "", headers=prepped.headers, body=prepped.body, ), ) try: res = session.send(prepped, proxies=proxies, timeout=self._timeout) except Exception as error: message = to_message(error) report = replace(report, status=Status.UNSTABLE, message=message) return report, None report = replace(report, status=Status.SUCCESS) response = ResponseWrapper(id=_generate_id(), res=res) return report, response
def run( self, request: Request, requirements: ResponseDescription, session: Optional[requests.Session] = None, context: Optional[Context] = None, ) -> Result: context = context if context is not None else Context() return retry_while_false( partial(self._execute, request, requirements, session, context), attempts=self._retry + 1, delay=self._delay, predicate=predicate, )
def submit(self, scenario: Scenario) -> ScenarioTask: starts = now() context = Context( **{ CONTEXT_KEY_STARTS: starts, CONTEXT_KEY_BASE_URL: self._case_runner.base_url, }) context_analyzer = MappingAnalyzer(context) conditions = Verification.collect( condition.verify(context_analyzer, context) for condition in scenario.conditions) if not conditions.status.is_succeeded: status = Status.SKIPPED if conditions.status is Status.FAILURE: status = Status.FAILURE result = ScenarioResult(label=scenario.label, status=status, conditions=conditions) return StaticScenarioTask(result) if scenario.ordered: cases: CasesTask = OrderedCasesTask( self._executor, self._case_runner, scenario.cases, context=context, ) else: cases = UnorderedCasesTask(self._executor, self._case_runner, scenario.cases) subscenarios = [ self.submit(subscenario) for subscenario in scenario.subscenarios ] return RunningScenarioTask( label=scenario.label, conditions=conditions, cases=cases, subscenarios=subscenarios, )
def test_when_given_no_response(mocker): mocker.patch(f"{PKG}.now", return_value=sentinel.starts) execution = ExecutionReport(status=Status.FAILURE) case = Case(label=sentinel.label, request=sentinel.request, response=sentinel.response) def _run_unit( request: Request, requirements: ResponseDescription, session: Optional[requests.Session] = None, context: Optional[Context] = None, ) -> Result: assert request is sentinel.request assert requirements is sentinel.response assert session is None assert context == Context(starts=sentinel.starts, base_url=sentinel.base_url) return execution, None, None unit_runner = NonCallableMock(UnitRunner, base_url=sentinel.base_url) unit_runner.run.side_effect = Mock(side_effect=_run_unit) listener = NonCallableMock(spec=CaseListener) runner = CaseRunner(unit_runner=unit_runner, listener=listener) result = runner.run(case) assert result.label is sentinel.label assert result.status is Status.FAILURE assert result.execution is execution assert result.response is None # Contextual values will disappear. unit_runner.run.assert_called_once_with( request=sentinel.request, requirements=sentinel.response, session=None, context=Context(), ) listener.on_execution.assert_called_once_with(execution, None)
class CaseRunner: def __init__(self, unit_runner: UnitRunner, listener: Optional[CaseListener] = None): self._unit_runner = unit_runner self._listener = listener or CaseListener() @property def base_url(self) -> str: return self._unit_runner.base_url def run( self, case: Case, session: Optional[requests.Session] = None, context: Optional[Context] = None, ) -> CaseResult: if not case.enabled: return CaseResult(label=case.label) context = context if context is not None else Context() with closed_context(context, starts=now(), base_url=self.base_url) as context: context_analyzer = MappingAnalyzer(context) conditions = Verification.collect( condition.verify(context_analyzer, context) for condition in case.conditions) if not conditions.status.is_succeeded: return CaseResult(case.label, conditions) execution, response, verification = self._unit_runner.run( request=case.request, requirements=case.response, session=session, context=context, ) self._listener.on_execution(execution, response) return CaseResult(case.label, conditions, execution, verification)
def resolve(self, context: Optional[Context] = None) -> object: context = context or Context() return context.get(self._key)
def test_context(): context = Context(foo="bar") assert list(context) == ["foo"] assert context != {"foo": "bar"} assert context == Context(foo="bar")
def _analyze_context(context: Context) -> Analyzer: assert context == Context(foo="bar", starts=sentinel.starts, base_url=sentinel.base_url) return sentinel.context_analyzer
def _verify(analyzer: Analyzer, context: Optional[Context] = None) -> Verification: assert analyzer is sentinel.response assert context == Context(foo="bar", starts=sentinel.starts) return sentinel.verification