From 95f84cf1fb6be8ac373d29ce677aadd570d8cbda Mon Sep 17 00:00:00 2001 From: Paule <44635962+V3lop5@users.noreply.github.com> Date: Thu, 3 Mar 2022 00:03:08 +0100 Subject: [PATCH 1/3] Added tests for jsonable_encoder See: https://github.com/tiangolo/fastapi/blob/master/tests/test_jsonable_encoder.py Should increase code coverage --- tests/utils/test_jsonable_encoder.py | 189 +++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 tests/utils/test_jsonable_encoder.py diff --git a/tests/utils/test_jsonable_encoder.py b/tests/utils/test_jsonable_encoder.py new file mode 100644 index 0000000..768096f --- /dev/null +++ b/tests/utils/test_jsonable_encoder.py @@ -0,0 +1,189 @@ +from datetime import datetime, timezone +from enum import Enum +from pathlib import PurePath, PurePosixPath, PureWindowsPath +from typing import Optional + +import pytest +from ensysmod.utils.encoders import jsonable_encoder +from pydantic import BaseModel, Field, ValidationError, create_model + + +class Person: + def __init__(self, name: str): + self.name = name + + +class Pet: + def __init__(self, owner: Person, name: str): + self.owner = owner + self.name = name + + +class DictablePerson(Person): + def __iter__(self): + return ((k, v) for k, v in self.__dict__.items()) + + +class DictablePet(Pet): + def __iter__(self): + return ((k, v) for k, v in self.__dict__.items()) + + +class Unserializable: + def __iter__(self): + raise NotImplementedError() + + @property + def __dict__(self): + raise NotImplementedError() + + +class ModelWithCustomEncoder(BaseModel): + dt_field: datetime + + class Config: + json_encoders = { + datetime: lambda dt: dt.replace( + microsecond=0, tzinfo=timezone.utc + ).isoformat() + } + + +class ModelWithCustomEncoderSubclass(ModelWithCustomEncoder): + class Config: + pass + + +class RoleEnum(Enum): + admin = "admin" + normal = "normal" + + +class ModelWithConfig(BaseModel): + role: Optional[RoleEnum] = None + + class Config: + use_enum_values = True + + +class ModelWithAlias(BaseModel): + foo: str = Field(..., alias="Foo") + + +class ModelWithDefault(BaseModel): + foo: str = ... # type: ignore + bar: str = "bar" + bla: str = "bla" + + +class ModelWithRoot(BaseModel): + __root__: str + + +@pytest.fixture( + name="model_with_path", params=[PurePath, PurePosixPath, PureWindowsPath] +) +def fixture_model_with_path(request): + class Config: + arbitrary_types_allowed = True + + ModelWithPath = create_model( + "ModelWithPath", path=(request.param, ...), __config__=Config # type: ignore + ) + return ModelWithPath(path=request.param("/foo", "bar")) + + +def test_encode_class(): + person = Person(name="Foo") + pet = Pet(owner=person, name="Firulais") + assert jsonable_encoder(pet) == {"name": "Firulais", "owner": {"name": "Foo"}} + + +def test_encode_dictable(): + person = DictablePerson(name="Foo") + pet = DictablePet(owner=person, name="Firulais") + assert jsonable_encoder(pet) == {"name": "Firulais", "owner": {"name": "Foo"}} + + +def test_encode_unsupported(): + unserializable = Unserializable() + with pytest.raises(ValueError): + jsonable_encoder(unserializable) + + +def test_encode_custom_json_encoders_model(): + model = ModelWithCustomEncoder(dt_field=datetime(2019, 1, 1, 8)) + assert jsonable_encoder(model) == {"dt_field": "2019-01-01T08:00:00+00:00"} + + +def test_encode_custom_json_encoders_model_subclass(): + model = ModelWithCustomEncoderSubclass(dt_field=datetime(2019, 1, 1, 8)) + assert jsonable_encoder(model) == {"dt_field": "2019-01-01T08:00:00+00:00"} + + +def test_encode_model_with_config(): + model = ModelWithConfig(role=RoleEnum.admin) + assert jsonable_encoder(model) == {"role": "admin"} + + +def test_encode_model_with_alias_raises(): + with pytest.raises(ValidationError): + ModelWithAlias(foo="Bar") + + +def test_encode_model_with_alias(): + model = ModelWithAlias(Foo="Bar") + assert jsonable_encoder(model) == {"Foo": "Bar"} + + +def test_encode_model_with_default(): + model = ModelWithDefault(foo="foo", bar="bar") + assert jsonable_encoder(model) == {"foo": "foo", "bar": "bar", "bla": "bla"} + assert jsonable_encoder(model, exclude_unset=True) == {"foo": "foo", "bar": "bar"} + assert jsonable_encoder(model, exclude_defaults=True) == {"foo": "foo"} + assert jsonable_encoder(model, exclude_unset=True, exclude_defaults=True) == { + "foo": "foo" + } + + +def test_custom_encoders(): + class safe_datetime(datetime): + pass + + class MyModel(BaseModel): + dt_field: safe_datetime + + instance = MyModel(dt_field=safe_datetime.now()) + + encoded_instance = jsonable_encoder( + instance, custom_encoder={safe_datetime: lambda o: o.isoformat()} + ) + assert encoded_instance["dt_field"] == instance.dt_field.isoformat() + + +def test_custom_enum_encoders(): + def custom_enum_encoder(v: Enum): + return v.value.lower() + + class MyEnum(Enum): + ENUM_VAL_1 = "ENUM_VAL_1" + + instance = MyEnum.ENUM_VAL_1 + + encoded_instance = jsonable_encoder( + instance, custom_encoder={MyEnum: custom_enum_encoder} + ) + assert encoded_instance == custom_enum_encoder(instance) + + +def test_encode_model_with_path(model_with_path): + if isinstance(model_with_path.path, PureWindowsPath): + expected = "\\foo\\bar" + else: + expected = "/foo/bar" + assert jsonable_encoder(model_with_path) == {"path": expected} + + +def test_encode_root(): + model = ModelWithRoot(__root__="Foo") + assert jsonable_encoder(model) == "Foo" \ No newline at end of file From 5cc5f4d6323e05c9f338916944ef49d9161685bb Mon Sep 17 00:00:00 2001 From: Paule <44635962+V3lop5@users.noreply.github.com> Date: Thu, 3 Mar 2022 00:07:22 +0100 Subject: [PATCH 2/3] Added tests for zip download --- tests/api/test_zip_download.py | 35 ++++++++++++++++++++++++++++++++++ tests/api/test_zip_upload.py | 2 +- 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 tests/api/test_zip_download.py diff --git a/tests/api/test_zip_download.py b/tests/api/test_zip_download.py new file mode 100644 index 0000000..c1a71e0 --- /dev/null +++ b/tests/api/test_zip_download.py @@ -0,0 +1,35 @@ +import os +import tempfile +import zipfile +import pytest +from typing import Dict +from zipfile import ZipFile + +from fastapi.testclient import TestClient +from sqlalchemy.orm import Session + +from tests.api.test_zip_upload import get_dataset_zip +from tests.utils import data_generator + + +@pytest.mark.parametrize("data_folder", ["dataset-1", "dataset-2"]) +def test_download_dataset(client: TestClient, db: Session, normal_user_headers: Dict[str, str], data_folder: str): + """ + Test creating a dataset. + """ + # Create a dataset + dataset = data_generator.random_existing_dataset(db) + + # Upload a zip file + zip_file_path = get_dataset_zip(data_folder) + + response = client.post( + f"/datasets/{dataset.id}/upload", + headers=normal_user_headers, + files={"file": ("dataset.zip", open(zip_file_path, "rb"), "application/zip")}, + ) + assert response.status_code == 200 + + response = client.get(f"/datasets/{dataset.id}/download", headers=normal_user_headers) + assert response.status_code == 200 + assert response.headers["Content-Type"] == "application/zip" diff --git a/tests/api/test_zip_upload.py b/tests/api/test_zip_upload.py index 07143ae..89ff7e1 100644 --- a/tests/api/test_zip_upload.py +++ b/tests/api/test_zip_upload.py @@ -39,7 +39,7 @@ def test_upload_dataset(client: TestClient, db: Session, normal_user_headers: Di dataset = data_generator.random_existing_dataset(db) # Upload a zip file - zip_file_path = get_dataset_zip("dataset-1") + zip_file_path = get_dataset_zip(data_folder) # print all the contents of the zip file print(f"Zip file contents of {zip_file_path}:") From cfb7b76fcd3f9e056825b2a174657007fc340c96 Mon Sep 17 00:00:00 2001 From: Paule <44635962+V3lop5@users.noreply.github.com> Date: Thu, 3 Mar 2022 00:10:50 +0100 Subject: [PATCH 3/3] Reformatted code --- tests/api/test_zip_download.py | 6 +----- tests/utils/test_jsonable_encoder.py | 5 +++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/tests/api/test_zip_download.py b/tests/api/test_zip_download.py index c1a71e0..3e0cc31 100644 --- a/tests/api/test_zip_download.py +++ b/tests/api/test_zip_download.py @@ -1,10 +1,6 @@ -import os -import tempfile -import zipfile -import pytest from typing import Dict -from zipfile import ZipFile +import pytest from fastapi.testclient import TestClient from sqlalchemy.orm import Session diff --git a/tests/utils/test_jsonable_encoder.py b/tests/utils/test_jsonable_encoder.py index 768096f..ce1ace3 100644 --- a/tests/utils/test_jsonable_encoder.py +++ b/tests/utils/test_jsonable_encoder.py @@ -4,9 +4,10 @@ from typing import Optional import pytest -from ensysmod.utils.encoders import jsonable_encoder from pydantic import BaseModel, Field, ValidationError, create_model +from ensysmod.utils.encoders import jsonable_encoder + class Person: def __init__(self, name: str): @@ -186,4 +187,4 @@ def test_encode_model_with_path(model_with_path): def test_encode_root(): model = ModelWithRoot(__root__="Foo") - assert jsonable_encoder(model) == "Foo" \ No newline at end of file + assert jsonable_encoder(model) == "Foo"