def test_validate_headers_wrong(self): r = Response() r.status_code = 200 r.headers = {"fake": "header"} with pytest.raises(test_utils.HeadersMismatchError): test_utils.validate_response(r, expected_headers={"fake": "wrong"})
def test_validate_paging_no_urls_raises(self): r = Response() r.status_code = 200 r.headers = { "paging-next": "www.url.com/next", "paging-previous": "www.url.com/previous", "paging-offset": 5, "paging-limit": 5, "paging-current-page": 2, "paging-total-pages": 10, "paging-total-items": 50, } expected_paging = PagingResp( next=None, previous=None, offset=5, limit=5, current_page=2, total_pages=10, total_items=50, ) with pytest.raises(test_utils.PagingMismatchError): test_utils.validate_response(r, expected_paging=expected_paging)
def test_validate_text_not_text(self): r = Response() r.status_code = 200 r._content = bytes(10) with pytest.raises(test_utils.TextValidationError): test_utils.validate_response(r, text_value="test text")
def test_validate_data_validation_error(self): r = Response() r.status_code = 200 r._content = json.dumps({"first": "Harry"}).encode() with pytest.raises(test_utils.DataValidationError): test_utils.validate_response(r, data_schema=NameSchema())
def test_validate_data_load_error(self): r = Response() r.status_code = 200 r._content = bytes(10) with pytest.raises(test_utils.ContentDecodeError): test_utils.validate_response(r, data_schema=NameSchema())
def test_use_schema_resp( self, api: SpanAPI, resp_dump: DumpOptions, data_send: Union[Name, dict], data_returned: dict, error: Optional[Type[errors_api.APIError]], bson: bool, ): @api.route("/test") class TestRoute(SpanRoute): @api.use_schema(resp=NameSchema(), resp_dump=resp_dump) async def on_get(self, req: Request, resp: Response): resp.media = data_send with api.requests as client: if bson: headers = {"accept": "application/bson"} else: headers = {} r = client.get("/test", headers=headers) if error is not None: validate_error(r, error) else: validate_response(r) if error is None: if bson: assert dict(RawBSONDocument(r.content)) == dict( RawBSONDocument(BSON.encode(data_returned)) ) else: assert r.json() == data_returned
def test_get_paged_custom_offset(self, api: SpanAPI): @api.route("/test") class PagedTest(SpanRoute): @api.paged(limit=2) async def on_get(self, req: Request, resp: Response): resp.paging.total_items = 10 resp.media = [ {"offset": req.paging.offset}, {"offset": req.paging.offset + 1}, ] with api.requests as client: i = 5 page = 3 r = client.get("/test", params={"paging-offset": 5}) page_previous = None while True: validate_response(r) data = r.json() assert isinstance(data, list) assert len(data) == 2 for obj in data: assert obj["offset"] == i i += 1 assert r.headers.get("paging-total-items") == "10" assert r.headers.get("paging-total-pages") == "5" assert r.headers.get("paging-limit") == "2" assert r.headers.get("paging-current-page") == str(page) if page == 5: assert "paging-next" not in r.headers else: assert r.headers.get("paging-next") == ( f"http://;/test?paging-offset={i}&paging-limit=2" ) if page == 3: assert r.headers.get("paging-previous") == ( f"http://;/test?paging-offset=3&paging-limit=2" ) elif page == 4: assert r.headers.get("paging-previous") == ( f"http://;/test?paging-offset=5&paging-limit=2" ) else: assert r.headers.get("paging-previous") == page_previous next_page_url = r.headers.get("paging-next", None) if next_page_url is None: break page += 1 page_previous = r.url r = client.get(next_page_url) assert i == 11
def test_load_bool(self, api: SpanAPI, value_in: str, value_parsed: bool): @api.route("/test/{value}") class PagedTest(SpanRoute): async def on_get(self, req: Request, resp: Response, *, value: bool): assert value == value_parsed with api.requests as client: r = client.get(f"/test/{value_in}") validate_response(r)
def test_basic(self, capsys): r = Response() r.status_code = 200 test_utils.validate_response(r) captured = capsys.readouterr() assert captured.out.startswith("RESPONSE: <Response [200]>\n")
def test_no_media(self, api: SpanAPI): @api.route("/test") class TestRoute(SpanRoute): async def on_post(self, req: Request, resp: Response): media = await req.media() assert media is None with api.requests as client: r = client.post("/test") validate_response(r)
def test_req_load_text(self, api: SpanAPI): @api.route("/test") class TestRoute(SpanRoute): @api.use_schema(req=MimeType.TEXT) async def on_post(self, req: Request, resp: Response): assert await req.media_loaded() == "test_text" with api.requests as client: r = client.post("/test", data="test_text") validate_response(r)
def test_req_load_schema_class(self, api: SpanAPI): @api.route("/test") class TestRoute(SpanRoute): @api.use_schema(req=NameSchema) async def on_post(self, req: Request, resp: Response): assert await req.media_loaded() == HARRY with api.requests as client: r = client.post("/test", json=HARRY_DUMPED) validate_response(r)
def test_use_schema_resp_text(self, api: SpanAPI): @api.route("/test") class TestRoute(SpanRoute): @api.use_schema(resp=MimeType.TEXT) async def on_get(self, req: Request, resp: Response): resp.text = "response_text" with api.requests as client: r = client.get("/test") validate_response(r) assert r.text == "response_text"
def test_req_load_bson_list(self, api: SpanAPI, data): @api.route("/test") class TestRoute(SpanRoute): @api.use_schema(req=NameSchema(many=True)) async def on_post(self, req: Request, resp: Response): assert await req.media_loaded() == [HARRY, HARRY] with api.requests as client: headers = {"Content-Type": "application/bson"} r = client.post("/test", data=encode_bson(data), headers=headers) validate_response(r)
def test_body(self, capsys): r = Response() r.status_code = 200 r._content = json.dumps({"key": "value"}).encode() test_utils.validate_response(r) captured = capsys.readouterr() assert '"key": "value"' in captured.out assert "JSON:" in captured.out
def test_text(self, capsys): r = Response() r.status_code = 200 r._content = "test text".encode() test_utils.validate_response(r, text_value="test text") captured = capsys.readouterr() assert "CONTENT:" in captured.out assert "test text" in captured.out
def test_body_not_json(self, capsys): r = Response() r.status_code = 200 r._content = bytes(10) test_utils.validate_response(r) captured = capsys.readouterr() assert "CONTENT:" in captured.out assert str(bytes(10)) in captured.out
def test_mimetype_unknown(self, api: SpanAPI): @api.route("/test") class TestRoute(SpanRoute): async def on_post(self, req: Request, resp: Response): assert req.mimetype == "application/unknown" with api.requests as client: headers = dict() MimeType.add_to_headers(headers, "application/unknown") r = client.post("/test", headers=headers) validate_response(r)
def test_paging_attrs(self, api: SpanAPI): @api.route("/test") class PagedTest(SpanRoute): @api.paged(limit=2) async def on_get(self, req: Request, resp: Response): assert req.paging is not resp.paging assert req.paging.offset == resp.paging.offset == 0 assert req.paging.limit == resp.paging.limit == 2 with api.requests as client: r = client.get("/test") validate_response(r)
def test_decode_media_loaded(self, api: SpanAPI): @api.route("/test") class TestRoute(SpanRoute): async def on_post(self, req: Request, resp: Response): loaded = await req.media_loaded() media = await req.media() assert media is loaded with api.requests as client: headers = dict() MimeType.add_to_headers(headers, MimeType.JSON) r = client.post("/test", headers=headers, json={"key": "value"}) validate_response(r)
def test_validate_data_schema(self): r = Response() r.status_code = 200 r._content = json.dumps({"first": "Harry", "last": "Potter"}).encode() name: Name = test_utils.validate_response(r, data_schema=NameSchema()) assert name == Name("Harry", "Potter")
def test_data_schema_bson(self): r = Response() r.status_code = 200 r._content = encode_bson({"first": "Harry", "last": "Potter"}) r.headers = {"Content-Type": "application/bson"} name: Name = test_utils.validate_response(r, data_schema=NameSchema()) assert name == Name("Harry", "Potter")
def test_paging_req_error(self, api: SpanAPI, test_obj_name): @api.route("/test") class PagedTest(SpanRoute): async def on_get(self, req: Request, resp: Response): error = None test_obj = req if test_obj_name == "req" else resp try: _ = test_obj.paging except TypeError as exc: error = exc assert isinstance(error, TypeError) with api.requests as client: r = client.get("/test") validate_response(r)
def test_req_schema_class_marshmallow(self, api: SpanAPI): class NameSchemaM(marshmallow.Schema): id = marshmallow.fields.UUID() first = marshmallow.fields.Str() last = marshmallow.fields.Str() harry_loaded = NameSchemaM().load(HARRY_DUMPED) @api.route("/test") class TestRoute(SpanRoute): @api.use_schema(req=NameSchemaM()) async def on_post(self, req: Request, resp: Response): assert await req.media_loaded() == harry_loaded with api.requests as client: r = client.post("/test", json=HARRY_DUMPED) validate_response(r)
def test_lru_cache_hits(self, api: SpanAPI): run = 0 @api.route("/test") class TestRoute(SpanRoute): @api.use_schema(resp=NameSchema()) async def on_get(self, req: Request, resp: Response): hits = max(0, run - 2) assert ( resp._projection_builder._build_projection_schema.cache_info().hits ) == hits resp.media = HARRY @api.route("/test2") class TestRoute(SpanRoute): @api.use_schema(resp=NameSchema()) async def on_get(self, req: Request, resp: Response): # We need to check that the first request for this endpoint does not # trigger a hit. if run < 2: hits = 9 else: hits = 9 + run - 2 assert ( resp._projection_builder._build_projection_schema.cache_info().hits ) == hits resp.media = HARRY with api.requests as client: for _ in range(10): run += 1 r = client.get("/test", params={"project.id": 1}) _ = validate_response(r, data_schema=NameSchema(only=["id"])) run = 0 # Check that the cache is endpoint-specific for _ in range(10): run += 1 r = client.get("/test2", params={"project.id": 1}) _ = validate_response(r, data_schema=NameSchema(only=["id"]))
def test_req_load_schema( self, api: SpanAPI, req_load: LoadOptions, data_post: dict, data_passed: Union[Name, dict], error: Optional[Type[errors_api.APIError]], bson: bool, ): if bson and isinstance(data_passed, dict): data_passed = RawBSONDocument(BSON.encode(data_passed)) @api.route("/test") class TestRoute(SpanRoute): @api.use_schema(req=NameSchema(), req_load=req_load) async def on_post( self, req: Request[dict, Union[Name, dict]], resp: Response ): data = await req.media_loaded() if req_load is LoadOptions.IGNORE: assert data == data_passed assert await req.media() == data_passed else: assert isinstance(data, type(data_passed)) assert data == data_passed with api.requests as client: if bson: if isinstance(data_post, dict): data_post = BSON.encode(data_post) r = client.post( "/test", data=data_post, headers={"Content-Type": "application/bson"}, ) else: r = client.post("/test", json=data_post) if error is not None: validate_error(r, error) else: validate_response(r)
def test_data_schema_bson_many_single(self): r = Response() r.status_code = 200 r._content = encode_bson([{"first": "Harry", "last": "Potter"}]) r.headers = {"Content-Type": "application/bson"} names: List[Name] = test_utils.validate_response( r, data_schema=NameSchema(many=True, normalize_many=True) ) assert names[0] == Name("Harry", "Potter") assert len(names) == 1
def test_resp_schema_class(self, api: SpanAPI): @api.route("/test") class TestRoute(SpanRoute): @api.use_schema(resp=NameSchema) async def on_get(self, req: Request, resp: Response): resp.media = HARRY with api.requests as client: r = client.get("/test") name: Name = validate_response(r, data_schema=NameSchema()) assert name == HARRY
def test_return_bson_types(self, api: SpanAPI): uuid_value = uuid.uuid4() # bytes_value = b"Some data" dt_value = datetime.datetime.utcnow() dt_value = dt_value.replace(microsecond=0) info_value = {"key": "value"} array_value = ["one", "two", "three"] @dataclass class ResponseData: id: uuid.UUID dt: datetime.datetime # binary: str info: Dict[str, str] array: List[str] empty: Dict[str, str] @schema_for(ResponseData) class ResponseSchema(DataSchema[ResponseData]): pass @api.route("/test") class TestRoute(SpanRoute): @api.use_schema(resp=ResponseSchema(), resp_dump=DumpOptions.IGNORE) async def on_get(self, req: Request, resp: Response): resp.media = RawBSONDocument( BSON.encode( { "id": uuid_value, "dt": dt_value, # "binary": bytes_value, "info": info_value, "array": array_value, "empty": {}, } ) ) with api.requests as client: r = client.get("/test") loaded: ResponseData = validate_response( r, data_schema=ResponseSchema(), expected_headers={"Content-Type": "application/json"}, ) assert loaded.id == uuid_value assert loaded.dt == dt_value # assert bytes.fromhex(loaded.binary) == bytes_value assert loaded.info == info_value assert loaded.array == array_value assert loaded.empty == {}
def test_register_mimetype_encoders(self, api: SpanAPI): data = [{"key": "value1"}, {"key": "value2"}] def csv_encode(data: List[dict]) -> bytes: encoded = io.StringIO() headers = list(data[0].keys()) writer = csv.DictWriter(encoded, fieldnames=headers) writer.writeheader() writer.writerows(data) return encoded.getvalue().encode() def csv_decode(data: bytes) -> List[Dict[str, Any]]: csv_file = io.StringIO(data.decode()) reader = csv.DictReader(csv_file) return [row for row in reader] api.register_mimetype("text/csv", encoder=csv_encode, decoder=csv_decode) @api.route("/test") class TestRoute(SpanRoute): async def on_post(self, req: Request, resp: Response): loaded = await req.media_loaded() media = await req.media() assert media is loaded assert media == loaded == data resp.media = media resp.mimetype = "text/csv" req_data = csv_encode(data) r = api.requests.post( "/test", headers={"Content-Type": "text/csv"}, data=req_data ) validate_response(r, expected_headers={"Content-Type": "text/csv"}) resp_data = csv_decode(r.content) assert resp_data == data