Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions tests/api/test_zip_download.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from typing import Dict

import pytest
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"
2 changes: 1 addition & 1 deletion tests/api/test_zip_upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}:")
Expand Down
190 changes: 190 additions & 0 deletions tests/utils/test_jsonable_encoder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
from datetime import datetime, timezone
from enum import Enum
from pathlib import PurePath, PurePosixPath, PureWindowsPath
from typing import Optional

import pytest
from pydantic import BaseModel, Field, ValidationError, create_model

from ensysmod.utils.encoders import jsonable_encoder


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"