示例#1
0
async def test_api_slow_supplies_content_location(
        api_client: APISessionClient):

    async with api_client.get("slow?complete_in=0.01&final_status=418") as r:

        assert 'application/json' in r.headers.get('Content-Type')
        poll_location = r.headers.get('Content-Location')
        assert r.status == 202, (r.status, r.reason, (await r.text())[:2000])

    async with api_client.get(poll_location) as r:
        poll_count = r.cookies.get('poll-count')
        assert poll_count.value == '1'
        assert r.status == 418, (r.status, r.reason, (await r.text())[:2000])
async def test_token_exchange_happy_path(test_app, api_client: APISessionClient):
    subject_token_claims = {
        'identity_proofing_level': test_app.request_params.get('identity_proofing_level')
    }
    token_response = await conftest.get_token_nhs_login_token_exchange(
        test_app,
        subject_token_claims=subject_token_claims
    )
    token = token_response["access_token"]

    correlation_id = str(uuid4())
    headers = {
        "Authorization": f"Bearer {token}",
        "X-Correlation-ID": correlation_id
    }

    async with api_client.get(
        _base_valid_uri("9999999990"),
        headers=headers,
        allow_retries=True
    ) as resp:
        assert resp.status == 200, 'failed getting backend data'
        body = await resp.json()
        assert "x-correlation-id" in resp.headers, resp.headers
        assert resp.headers["x-correlation-id"] == correlation_id
        assert_body(body)
示例#3
0
async def api_client(api_test_config: APITestSessionConfig):

    session_client = APISessionClient(api_test_config.base_uri)

    yield session_client

    await session_client.close()
示例#4
0
    async def set_custom_attributes(self, attributes: dict) -> dict:
        """ Replaces the current list of attributes with the attributes specified """
        custom_attributes = [{"name": "DisplayName", "value": self.name}]

        for key, value in attributes.items():
            custom_attributes.append({"name": key, "value": value})

        params = self.default_params.copy()
        params['name'] = self.name

        async with APISessionClient(self.app_base_uri) as session:
            async with session.post(f"apps/{self.name}/attributes",
                                    params=params,
                                    headers=self.headers,
                                    json={"attribute":
                                          custom_attributes}) as resp:
                body = await resp.json()
                if resp.status != 200:
                    headers = dict(resp.headers.items())
                    throw_friendly_error(
                        message=
                        f"unable to add custom attributes {attributes} to app: "
                        f"{self.name}",
                        url=resp.url,
                        status_code=resp.status,
                        response=body,
                        headers=headers)
                return body['attribute']
示例#5
0
async def test_wait_for_status(api_client: APISessionClient,
                               api_test_config: APITestSessionConfig):
    async def is_deployed(resp: ClientResponse):
        if resp.status != 200:
            return False
        body = await resp.json()

        if body.get("commitId") != api_test_config.commit_id:
            return False

        backend = dict_path(body,
                            ["checks", "healthcheck", "outcome", "version"])
        if not backend:
            return True

        return backend.get("commitId") == api_test_config.commit_id

    deploy_timeout = 120 if api_test_config.api_environment.endswith(
        "sandbox") else 30

    await poll_until(
        make_request=lambda: api_client.get(
            "_status", headers={"apikey": env.status_endpoint_api_key()}),
        until=is_deployed,
        timeout=deploy_timeout,
    )
async def test_token_exchange_both_header_and_exchange(api_client: APISessionClient,
                                                       test_product_and_app):
    test_product, test_app = test_product_and_app
    subject_token_claims = {
        'identity_proofing_level': test_app.request_params.get('identity_proofing_level')
    }
    authorised_headers = {}
    correlation_id = str(uuid4())
    authorised_headers["X-Correlation-ID"] = correlation_id
    authorised_headers["NHSD-User-Identity"] = conftest.nhs_login_id_token(test_app)

    # Use token exchange token in conjunction with JWT header
    token_response = await conftest.get_token_nhs_login_token_exchange(
        test_app,
        subject_token_claims=subject_token_claims
    )
    token = token_response["access_token"]

    authorised_headers["Authorization"] = f"Bearer {token}"

    async with api_client.get(
        _base_valid_uri("9999999990"),
        headers=authorised_headers,
        allow_retries=True
    ) as resp:
        assert resp.status == 200
        body = await resp.json()
        assert "x-correlation-id" in resp.headers, resp.headers
        assert resp.headers["x-correlation-id"] == correlation_id
        assert_body(body)
示例#7
0
async def test_user_restricted_access_not_permitted(
        api_client: APISessionClient, test_product_and_app):

    await asyncio.sleep(1
                        )  # Add delay to tests to avoid 429 on service callout

    test_product, test_app = test_product_and_app

    await test_product.update_scopes(
        ["urn:nhsd:apim:user-nhs-id:aal3:immunisation-history"])
    await test_app.add_api_product([test_product.name])

    token_response = await conftest.get_token(test_app)

    authorised_headers = {
        "Authorization": f"Bearer {token_response['access_token']}",
        "NHSD-User-Identity": conftest.nhs_login_id_token(test_app)
    }

    async with api_client.get(_valid_uri("9912003888", "90640007"),
                              headers=authorised_headers,
                              allow_retries=True) as resp:
        assert resp.status == 401
        body = await resp.json()
        assert body["resourceType"] == "OperationOutcome"
        assert body["issue"][0]["severity"] == "error"
        assert body["issue"][0][
            "diagnostics"] == "Provided access token is invalid"
        assert body["issue"][0]["code"] == "forbidden"
    async def _get_state(self, request_state: str) -> str:
        """Send an authorize request and retrieve the state"""
        params = {
            "client_id": self.client_id,
            "redirect_uri": self.redirect_uri,
            "response_type": "code",
            "state": request_state,
        }

        async with APISessionClient(self.base_uri) as session:
            async with session.get("authorize", params=params) as resp:
                body = await resp.read()
                if resp.status != 200:
                    headers = dict(resp.headers.items())
                    throw_friendly_error(
                        message=
                        "unexpected response, unable to authenticate with simulated oauth",
                        url=resp.url,
                        status_code=resp.status,
                        response=body,
                        headers=headers,
                    )

                state = dict(resp.url.query)["state"]

                # Confirm state is converted to a cryptographic value
                assert state != request_state
                return state
async def test_retry_request_varying_responses(status_codes, max_retries,
                                               expected_response):
    async with APISessionClient("https://httpbin.org") as session:
        mock_status_list = map(mock_response, status_codes)
        requester = MockRequest(mock_status_list)
        resp = await session._retry_requests(requester, max_retries)  # pylint: disable=W0212
        assert resp.status == expected_response
示例#10
0
async def test_token_exchange_both_header_and_exchange(
        api_client: APISessionClient, test_product_and_app,
        authorised_headers):
    test_product, test_app = test_product_and_app
    correlation_id = str(uuid4())
    authorised_headers["X-Correlation-ID"] = correlation_id
    authorised_headers["NHSD-User-Identity"] = conftest.nhs_login_id_token(
        test_app)

    # Use token exchange token in conjunction with JWT header
    token_response = await conftest.get_token_nhs_login_token_exchange(test_app
                                                                       )
    token = token_response["access_token"]

    authorised_headers["Authorization"] = f"Bearer {token}"

    async with api_client.get(_valid_uri("9912003888", "90640007"),
                              headers=authorised_headers,
                              allow_retries=True) as resp:
        assert resp.status == 200
        body = await resp.json()
        assert "x-correlation-id" in resp.headers, resp.headers
        assert resp.headers["x-correlation-id"] == correlation_id
        assert body["resourceType"] == "Bundle", body
        # no data for this nhs number ...
        assert len(body["entry"]) == 0, body
示例#11
0
    async def add_api_product(self, api_products: list) -> dict:
        """ Add a number of API Products to the app """
        params = self.default_params.copy()
        params['name'] = self.name

        data = {
            "apiProducts": api_products,
            "name": self.name,
            "status": "approved"
        }

        async with APISessionClient(self.app_base_uri) as session:
            async with session.put(f"apps/{self.name}/keys/{self.client_id}",
                                   params=params,
                                   headers=self.headers,
                                   json=data) as resp:
                body = await resp.json()
                if resp.status != 200:
                    headers = dict(resp.headers.items())
                    throw_friendly_error(
                        message=
                        f"unable to add api products {api_products} to app: "
                        f"{self.name}",
                        url=resp.url,
                        status_code=resp.status,
                        response=body,
                        headers=headers)
                return body['apiProducts']
示例#12
0
async def test_p5_token_exchange_with_allowed_proofing_level(
        api_client: APISessionClient, test_product_and_app):

    test_product, test_app = test_product_and_app

    await _set_app_allowed_proofing_level(test_app, 'P5')

    token_response = await conftest.get_token_nhs_login_token_exchange(
        test_app, subject_token_claims={"identity_proofing_level": "P5"})
    token = token_response["access_token"]

    correlation_id = str(uuid4())
    headers = {
        "Authorization": f"Bearer {token}",
        "X-Correlation-ID": correlation_id,
    }

    async with api_client.get(_valid_uri("9912003888", "90640007"),
                              headers=headers,
                              allow_retries=True) as resp:
        assert resp.status == 200
        body = await resp.json()
        assert body["resourceType"] == "Bundle", body
        # no data for this nhs number ...
        assert len(body["entry"]) == 0, body
示例#13
0
async def test_immunisation_id_token_error_scenarios(
        test_app, api_client: APISessionClient, authorised_headers,
        request_data: dict):
    await asyncio.sleep(1
                        )  # Add delay to tests to avoid 429 on service callout
    id_token = conftest.nhs_login_id_token(
        test_app=test_app,
        id_token_claims=request_data.get("claims"),
        id_token_headers=request_data.get("headers"))

    if request_data.get("id_token") is not None:
        authorised_headers["NHSD-User-Identity"] = request_data.get("id_token")
    else:
        authorised_headers["NHSD-User-Identity"] = id_token

    async with api_client.get(_valid_uri("9912003888", "90640007"),
                              headers=authorised_headers,
                              allow_retries=True) as resp:
        assert resp.status == request_data["expected_status_code"]
        body = await resp.json()
        assert body["resourceType"] == "OperationOutcome"
        assert body["issue"][0]["severity"] == request_data[
            "expected_response"]["severity"]
        assert body["issue"][0]["diagnostics"] == request_data[
            "expected_response"]["error_diagnostics"]
        assert body["issue"][0]["code"] == request_data["expected_response"][
            "error_code"]
示例#14
0
    async def update_custom_attribute(self, attribute_name: str,
                                      attribute_value: str) -> dict:
        """ Update an existing custom attribute """
        params = self.default_params.copy()
        params["name"] = self.name
        params["attribute_name"] = attribute_name

        data = {"value": attribute_value}

        async with APISessionClient(self.app_base_uri) as session:
            async with session.post(
                    f"apps/{self.name}/attributes/{attribute_name}",
                    params=params,
                    headers=self.headers,
                    json=data) as resp:
                body = await resp.json()
                if resp.status != 200:
                    headers = dict(resp.headers.items())
                    throw_friendly_error(
                        message=
                        f"unable to add custom attribute for app: {self.name}",
                        url=resp.url,
                        status_code=resp.status,
                        response=body,
                        headers=headers)
                return body
示例#15
0
    async def hit_oauth_endpoint(self, method: str, endpoint: str,
                                 **kwargs) -> dict:
        """Send a request to a OAuth endpoint"""
        async with APISessionClient(self.base_uri) as session:
            request_method = (session.post,
                              session.get)[method.lower().strip() == 'get']
            resp = await self._retry_requests(
                lambda: request_method(endpoint, **kwargs), 5)
            try:
                body = await resp.json()
                _ = body.pop(
                    'message_id', None
                )  # Remove the unique message id if the response is na error
            except ContentTypeError:
                # Might be html or text response
                body = await resp.read()

                if isinstance(body, bytes):
                    # Convert into a string
                    body = str(body, "UTF-8")
                    try:
                        # In case json response was of type bytes
                        body = literal_eval(body)
                    except SyntaxError:
                        # Continue
                        pass

            return {
                'method': resp.method,
                'url': resp.url,
                'status_code': resp.status,
                'body': body,
                'headers': dict(resp.headers.items()),
                'history': resp.history
            }
    async def get_trace_data(self) -> dict or None:
        if not self.revision:
            raise RuntimeError(
                "You must run start_trace() before you can run get_raw_trace()"
            )

        await self._set_transaction_id()
        if not self.transaction_id:
            return None

        async with APISessionClient(self.base_uri) as session:
            async with session.post(
                    f"environments/{self.env}/apis/{self.proxy}/revisions/{self.revision}/"
                    f"debugsessions/{self.name}/data/{self.transaction_id}",
                    headers=self.headers) as resp:
                body = await resp.read()
                if resp.status != 201:
                    headers = dict(resp.headers.items())
                    throw_friendly_error(
                        message=
                        f"unable to get trace data for session {self.name} "
                        f"on proxy {self.proxy}",
                        url=resp.url,
                        status_code=resp.status,
                        response=body,
                        headers=headers)
                return body
async def test_retry_request_varying_error():
    async with APISessionClient("https://httpbin.org") as session:
        mock_status_list = map(mock_response, [429, 429, 503])
        requester = MockRequest(mock_status_list)
        with pytest.raises(TimeoutError) as excinfo:
            await session._retry_requests(requester, max_retries=3)  # pylint: disable=W0212
            error = excinfo.value
            assert error == "Maximum retry limit hit."
示例#18
0
async def test_api_slow_supplies_content_location(
        api_client: APISessionClient, api_test_config: APITestSessionConfig):

    async with api_client.get("slow") as r:
        assert r.status == 202, (r.status, r.reason, (await r.text())[:2000])
        assert r.headers.get('Content-Type') == 'application/json'
        assert r.headers.get('Content-Location').startswith(
            api_test_config.base_uri + '/poll?')
        assert r.cookies.get('poll-count') == '0'
async def test_max_retries_limit(endpoint, should_retry, expected_error):
    async with APISessionClient("https://httpbin.org") as session:
        with pytest.raises(TimeoutError) as excinfo:
            await session.get(endpoint,
                              allow_retries=should_retry,
                              max_retries=3)

        error = excinfo.value
        assert expected_error in str(error)
async def test_postman_echo_send_multivalue_headers():
    async with APISessionClient("http://postman-echo.com") as session:
        async with session.get("headers",
                               headers=[("foo1", "bar1"),
                                        ("foo1", "bar2")]) as resp:
            assert resp.status == 200
            body = await resp.json()

            assert body["headers"]["foo1"] == "bar1, bar2"
async def test_fixture_postman_echo_send_multivalue_headers(
        api_client: APISessionClient):

    async with api_client.get("headers",
                              headers=[("foo1", "bar1"),
                                       ("foo1", "bar2")]) as resp:
        assert resp.status == 200
        body = await resp.json()

        assert body["headers"]["foo1"] == "bar1, bar2"
async def test_wait_for_poll_does_timeout(api_client: APISessionClient):

    with pytest.raises(PollTimeoutError) as exec_info:
        await poll_until(lambda: api_client.get('status/404'),
                         timeout=1,
                         sleep_for=0.3)

    error = exec_info.value  # type: PollTimeoutError
    assert len(error.responses) > 0
    assert error.responses[0][0] == 404
async def test_wait_for_200_json_deflate(api_client: APISessionClient):

    responses = await poll_until(lambda: api_client.get('deflate'), timeout=5)

    assert len(responses) == 1

    status, headers, body = responses[0]

    assert status == 200
    assert headers.get('Content-Type').split(';')[0] == 'application/json'
    assert body['deflated'] is True
示例#24
0
    async def authenticate(self, request_state: str = str(uuid4())) -> str:
        """Authenticate and retrieve the code value"""
        state = await self._get_state(request_state)
        params = {
            "response_type": "code",
            "client_id": self.client_id,
            "redirect_uri": self.redirect_uri,
            "scope": "openid",
            "state": state
        }
        headers = {"Content-Type": "application/x-www-form-urlencoded"}
        payload = {"state": state}

        async with APISessionClient(self.base_uri) as session:
            async with session.post("simulated_auth",
                                    params=params,
                                    data=payload,
                                    headers=headers,
                                    allow_redirects=False) as resp:
                if resp.status != 302:
                    body = await resp.json()
                    headers = dict(resp.headers.items())
                    throw_friendly_error(
                        message=
                        "unexpected response, unable to authenticate with simulated oauth",
                        url=resp.url,
                        status_code=resp.status,
                        response=body,
                        headers=headers)

                redirect_uri = resp.headers['Location'][
                    resp.headers['Location'].index('callback'):]

                async with session.get(redirect_uri,
                                       allow_redirects=False) as callback_resp:
                    headers = dict(callback_resp.headers.items())
                    # Confirm request was successful
                    if callback_resp.status != 302:
                        body = await resp.read()
                        throw_friendly_error(
                            message=
                            "unexpected response, unable to authenticate with simulated oauth",
                            url=resp.url,
                            status_code=resp.status,
                            response=body,
                            headers=headers)

                    # Get code value from location parameters
                    query = headers['Location'].split("?")[1]
                    params = {
                        x[0]: x[1]
                        for x in [x.split("=") for x in query.split("&")]
                    }
                    return params['code']
async def test_wait_for_200_json(api_client: APISessionClient):

    responses = await poll_until(lambda: api_client.get('json'), timeout=5)

    assert len(responses) == 1

    status, headers, body = responses[0]

    assert status == 200
    assert headers.get('Content-Type').split(';')[0] == 'application/json'
    assert body['slideshow']['title'] == 'Sample Slide Show'
async def test_wait_for_200_html(api_client: APISessionClient):

    responses = await poll_until(lambda: api_client.get('html'), timeout=5)

    assert len(responses) == 1

    status, headers, body = responses[0]

    assert status == 200
    assert headers.get('Content-Type').split(';')[0] == 'text/html'
    assert isinstance(body, str)
    assert body.startswith('<!DOCTYPE html>')
示例#27
0
async def test_wait_for_ping(api_client: APISessionClient,
                             api_test_config: APITestSessionConfig):
    async def _is_complete(resp: ClientResponse):

        if resp.status != 200:
            return False
        body = await resp.json()
        return body.get("commitId") == api_test_config.commit_id

    await poll_until(make_request=lambda: api_client.get('_ping'),
                     until=_is_complete,
                     timeout=120)
示例#28
0
async def test_wait_for_ping(api_client: APISessionClient,
                             api_test_config: APITestSessionConfig):
    async def apigee_deployed(resp: ClientResponse):
        if resp.status != 200:
            return False
        body = await resp.json()

        return body.get("commitId") == api_test_config.commit_id

    await poll_until(make_request=lambda: api_client.get("_ping"),
                     until=apigee_deployed,
                     timeout=30)
示例#29
0
async def test_fixture_override_http_bin_post(api_client: APISessionClient):

    data = {'test': 'data'}
    async with api_client.post("post", json=data) as resp:

        assert resp.status == 200

        body = await resp.json()

        assert body['headers'].get('Host') == 'httpbin.org'
        assert body['headers'].get('Content-Type') == 'application/json'
        assert body['data'] == json.dumps(data)
示例#30
0
async def test_wait_for_ping(api_client: APISessionClient,
                             api_test_config: APITestSessionConfig):
    """
        test for _ping ..  this uses poll_until to wait until the correct SOURCE_COMMIT_ID ( from env var )
        is available
    """

    is_deployed = partial(_is_ping_deployed, api_test_config=api_test_config)

    await poll_until(make_request=lambda: api_client.get('_ping'),
                     until=is_deployed,
                     timeout=120)