From 38c6d5761e994f055188f244a0579c2aa99a6cd4 Mon Sep 17 00:00:00 2001 From: Etienne Jodry Date: Mon, 13 May 2024 15:59:15 +0200 Subject: [PATCH] Populate unit tests. --- .gitignore | 2 +- pyproject.toml | 7 + src/biodm/api.py | 1 - src/biodm/basics/k8scontroller.py | 2 +- src/biodm/basics/rootcontroller.py | 2 +- .../components/controllers/controller.py | 7 +- .../controllers/resourcecontroller.py | 8 +- src/biodm/components/services/dbservice.py | 4 +- src/biodm/components/table.py | 131 ++++----- src/biodm/managers/dbmanager.py | 16 +- src/biodm/tests/conftest.py | 20 +- src/biodm/tests/test_basics.py | 32 ++- src/biodm/tests/test_resource.py | 249 +++++++++++++++++- src/example/app.py | 6 +- src/example/entities/schemas/dataset.py | 2 +- src/example/entities/schemas/file.py | 2 +- src/example/entities/schemas/tag.py | 2 +- src/example/tests/test_app.py | 19 -- 18 files changed, 371 insertions(+), 141 deletions(-) delete mode 100644 src/example/tests/test_app.py diff --git a/.gitignore b/.gitignore index 3307468..a070b82 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,7 @@ venv*/ # Caches __pycache__/ -.pytest_cache/ +.*_cache/ # Build artifacts build/ diff --git a/pyproject.toml b/pyproject.toml index 8ef8c00..4476180 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,13 @@ include = ["*"] # package names should match these glob patterns (["*"] by defa exclude = ["*example*"] # exclude packages matching these glob patterns (empty by default) namespaces = false # to disable scanning PEP 420 namespaces (true by default) +[tool.coverage.report] +exclude_also = [ + "if TYPE_CHECKING:", + "raise NotImplementedError", + "def __repr__", +] + [tool.mypy] exclude = ["bak"] diff --git a/src/biodm/api.py b/src/biodm/api.py index 73d0121..1aeb53e 100644 --- a/src/biodm/api.py +++ b/src/biodm/api.py @@ -88,7 +88,6 @@ def __init__( self.config: Config = instance.get('config') m = instance.get('manifests') if m: - # So that it is passed as a parameter for k8 manager. self.config.K8_MANIFESTS = m ## Logger. diff --git a/src/biodm/basics/k8scontroller.py b/src/biodm/basics/k8scontroller.py index e5a5b08..1b34cac 100644 --- a/src/biodm/basics/k8scontroller.py +++ b/src/biodm/basics/k8scontroller.py @@ -32,7 +32,7 @@ def routes(self, schema=False) -> Mount: Route("/schema", self.openapi_schema), ]) if schema: - # Mock up an individual route for each available manifest, copying it's doc. + # Mock up an individual route for each available manifest, copying doc. r_create = m.routes[0] mans = self.k8s.manifests keys = [k for k in mans.__dict__.keys() if not k.startswith('__')] diff --git a/src/biodm/basics/rootcontroller.py b/src/biodm/basics/rootcontroller.py index 03715c9..e288611 100644 --- a/src/biodm/basics/rootcontroller.py +++ b/src/biodm/basics/rootcontroller.py @@ -78,7 +78,7 @@ async def syn_ack(self, request): return PlainTextResponse(token['access_token'] + '\n') @login_required - async def authenticated(self, request, userid, groups, projects): + async def authenticated(self, _, userid, groups, projects): """ --- description: Route to check token validity. diff --git a/src/biodm/components/controllers/controller.py b/src/biodm/components/controllers/controller.py index 0f9d86c..10cee4a 100644 --- a/src/biodm/components/controllers/controller.py +++ b/src/biodm/components/controllers/controller.py @@ -86,9 +86,10 @@ def validate(cls, data: bytes) -> (Any | list | dict | None): return cls.schema.loads(json_data=data) except ValidationError as e: - raise PayloadValidationError(e) from e + raise PayloadValidationError() from e except json.JSONDecodeError as e: - raise PayloadJSONDecodingError(e) from e + raise PayloadJSONDecodingError() from e + @classmethod def serialize( @@ -102,7 +103,7 @@ def serialize( :param data: some request body :type data: dict, class:`biodm.components.Base`, List[class:`biodm.components.Base`] :param many: plurality flag, essential to marshmallow - :type data: bool + :type many: bool :param only: List of fields to restrict serialization on, optional, defaults to None :type only: List[str] """ diff --git a/src/biodm/components/controllers/resourcecontroller.py b/src/biodm/components/controllers/resourcecontroller.py index 8b8c5b1..bfdc967 100644 --- a/src/biodm/components/controllers/resourcecontroller.py +++ b/src/biodm/components/controllers/resourcecontroller.py @@ -2,7 +2,7 @@ from functools import partial from typing import TYPE_CHECKING, List, Any -from marshmallow.schema import EXCLUDE +from marshmallow.schema import RAISE #EXCLUDE from starlette.routing import Mount, Route from starlette.requests import Request from starlette.responses import Response @@ -55,7 +55,6 @@ class ResourceController(EntityController): Implements and exposes routes under a prefix named as the resource pluralized that act as a standard REST-to-CRUD interface. - :param app: running server :type app: Api :param entity: entity name, defaults to None, inferred if None @@ -79,8 +78,7 @@ def __init__( self.pk = tuple(self.table.pk()) self.svc = self._infer_svc()(app=self.app, table=self.table) - # schema = schema if schema else self._infer_schema() - self.__class__.schema = (schema if schema else self._infer_schema())(unknown=EXCLUDE) + self.__class__.schema = (schema if schema else self._infer_schema())(unknown=RAISE) def _infer_entity_name(self) -> str: """Infer entity name from controller name.""" @@ -167,7 +165,7 @@ async def _extract_body(self, request: Request) -> bytes: :rtype: bytes """ body = await request.body() - if not body: + if body == b'{}': raise PayloadEmptyError return body diff --git a/src/biodm/components/services/dbservice.py b/src/biodm/components/services/dbservice.py index 071f648..31b5ea2 100644 --- a/src/biodm/components/services/dbservice.py +++ b/src/biodm/components/services/dbservice.py @@ -241,7 +241,7 @@ async def filter(self, query_params: dict, serializer: Callable = None, **kwargs # exclude = True # In case no value is associated we should be in the case of a numerical operator. - operator = None if csval else self._parse_int_operators(attr.pop()) + operator = None if csval else self._parse_int_operators(attr) # elif any(op in dskey for op in SUPPORTED_INT_OPERATORS): # raise ValueError("'field.op()=value' type of query is not yet supported.") stmt, (col, ctype) = self._filter_process_attr(stmt, attr) @@ -256,6 +256,7 @@ async def filter(self, query_params: dict, serializer: Callable = None, **kwargs op, val = operator op = getattr(col, f"__{op}__") stmt = stmt.where(op(ctype(val))) + continue ## Filters # Wildcards. @@ -378,6 +379,7 @@ async def _insert_composite( # Populate main object with nested object id if matching field is found. # TODO: hypehen the importance of that convention in the documentation. + # TODO: Find way to not have it depend on the name. for key, sub in composite.nested.items(): attr = f"id_{key}" if hasattr(item, attr): diff --git a/src/biodm/components/table.py b/src/biodm/components/table.py index 13eacec..2787040 100644 --- a/src/biodm/components/table.py +++ b/src/biodm/components/table.py @@ -1,5 +1,5 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, List import datetime from sqlalchemy import ( @@ -19,19 +19,27 @@ class Base(DeclarativeBase, AsyncAttrs): """Base class for ORM declarative Tables.""" # Enable entity - service linkage. svc: DatabaseService + # perm: Permission + + @declared_attr + def __tablename__(cls) -> str: + """Generate tablename.""" + return cls.__name__.upper() @classmethod def relationships(cls): + """Return table relationships.""" return inspect(cls).mapper.relationships @classmethod def target_table(cls, name): - """Returns target table of a property.""" + """Return target table of a property.""" c = cls.col(name).property return c.target if isinstance(c, Relationship) else None @classmethod def pk(cls): + """Return primary key names.""" return ( str(pk).rsplit('.', maxsplit=1)[-1] for pk in cls.__table__.primary_key.columns @@ -48,11 +56,6 @@ def colinfo(cls, name): c = cls.col(name) return c, c.type.python_type - @declared_attr - def __tablename__(cls) -> str: - """Generate tablename.""" - return cls.__name__.upper() - class S3File: """Class to use in order to have a file managed on S3 bucket associated to this table @@ -77,60 +80,62 @@ def user(cls) -> Mapped["User"]: validated_at = Column(TIMESTAMP(timezone=True)) -# TODO: VERSIONNED, does the auto increment, and sets adjacency list. Use on dataset instead. - class Permission: - """Class that produces necessary fields to declare ressource permissions for an entity. - for each action in [CREATE, READ, UPDATE, DELETE, DOWNLOAD]. - """ - # def __init_subclass__(cls, **kwargs) -> None: - # """To restrict on some tables.""" - # if S3File in cls.__bases__: - # cls.id_ls_download = declared_attr(cls.id_ls_download) - # cls.ls_download = declared_attr(cls.ls_download) - # super().__init_subclass__(**kwargs) - - @declared_attr - def id_ls_download(_): - return Column(ForeignKey("LISTGROUP.id"), nullable=True) - - @declared_attr - @classmethod - def ls_download(cls) -> Mapped["ListGroup"]: - return relationship("ListGroup", foreign_keys=[cls.id_ls_download], lazy="select") - - @declared_attr - def id_ls_create(_): - return Column(ForeignKey("LISTGROUP.id"), nullable=True) - - @declared_attr - @classmethod - def ls_create(cls) -> Mapped["ListGroup"]: - return relationship("ListGroup", foreign_keys=[cls.id_ls_create], lazy="select") - - @declared_attr - def id_ls_read(_): - return Column(ForeignKey("LISTGROUP.id"), nullable=True) - - @declared_attr - @classmethod - def ls_read(cls) -> Mapped["ListGroup"]: - return relationship("ListGroup", foreign_keys=[cls.id_ls_read], lazy="select") - - @declared_attr - def id_ls_update(_): - return Column(ForeignKey("LISTGROUP.id"), nullable=True) - - @declared_attr - @classmethod - def ls_update(cls) -> Mapped["ListGroup"]: - return relationship("ListGroup", foreign_keys=[cls.id_ls_update], lazy="select") - - @declared_attr - def name_owner_group(_): - return Column(ForeignKey("GROUP.name"), nullable=True) - - @declared_attr - @classmethod - def owner_group(cls) -> Mapped["Group"]: - return relationship(foreign_keys=[cls.name_owner_group], lazy="select") + def __init__(self, propagates_to: List[Base]) -> None: + pass + +"""""" +"""Class that produces necessary fields to declare ressource permissions for an entity. + for each action in [CREATE, READ, UPDATE, DELETE, DOWNLOAD]. +""" +# def __init_subclass__(cls, **kwargs) -> None: +# """To restrict on some tables.""" +# if S3File in cls.__bases__: +# cls.id_ls_download = declared_attr(cls.id_ls_download) +# cls.ls_download = declared_attr(cls.ls_download) +# super().__init_subclass__(**kwargs) + +# @declared_attr +# def id_ls_download(_): +# return Column(ForeignKey("LISTGROUP.id"), nullable=True) + +# @declared_attr +# @classmethod +# def ls_download(cls) -> Mapped["ListGroup"]: +# return relationship("ListGroup", foreign_keys=[cls.id_ls_download], lazy="select") + +# @declared_attr +# def id_ls_create(_): +# return Column(ForeignKey("LISTGROUP.id"), nullable=True) + +# @declared_attr +# @classmethod +# def ls_create(cls) -> Mapped["ListGroup"]: +# return relationship("ListGroup", foreign_keys=[cls.id_ls_create], lazy="select") + +# @declared_attr +# def id_ls_read(_): +# return Column(ForeignKey("LISTGROUP.id"), nullable=True) + +# @declared_attr +# @classmethod +# def ls_read(cls) -> Mapped["ListGroup"]: +# return relationship("ListGroup", foreign_keys=[cls.id_ls_read], lazy="select") + +# @declared_attr +# def id_ls_update(_): +# return Column(ForeignKey("LISTGROUP.id"), nullable=True) + +# @declared_attr +# @classmethod +# def ls_update(cls) -> Mapped["ListGroup"]: +# return relationship("ListGroup", foreign_keys=[cls.id_ls_update], lazy="select") + +# @declared_attr +# def name_owner_group(_): +# return Column(ForeignKey("GROUP.name"), nullable=True) + +# @declared_attr +# @classmethod +# def owner_group(cls) -> Mapped["Group"]: +# return relationship(foreign_keys=[cls.name_owner_group], lazy="select") diff --git a/src/biodm/managers/dbmanager.py b/src/biodm/managers/dbmanager.py index bd2e0ce..5ab9b80 100644 --- a/src/biodm/managers/dbmanager.py +++ b/src/biodm/managers/dbmanager.py @@ -31,7 +31,7 @@ def __init__(self, app: Api): expire_on_commit=False, ) except SQLAlchemyError as e: - raise PostgresUnavailableError(f"Failed to connect to Postgres: {e}") from e + raise PostgresUnavailableError(f"Failed to connect to DB") from e @staticmethod def async_database_url(url) -> str: @@ -98,25 +98,31 @@ async def wrapper(*args, **kwargs): """ Applies a bit of arguments manipulation whose goal is to maximize convenience of use of the decorator by allowing explicing or implicit argument calling. - Relevant doc: https://docs.python.org/3/library/inspect.html#inspect.Signature.bind - Then produces and passes down a session if needed. Finally after the function returns, serialization is applied if needed. + + Doc: + - https://docs.python.org/3/library/inspect.html#inspect.Signature.bind """ serializer = kwargs.pop('serializer', None) + bound_args = signature(db_exec).bind_partial(*args, **kwargs) bound_args.apply_defaults() bound_args = bound_args.arguments + if bound_args.get('kwargs') == {}: + # Else it will get passed around. + bound_args.pop('kwargs') + svc: DatabaseService = bound_args['self'] session = bound_args.get('session', None) async with AsyncExitStack() as stack: - # Produce session + # Ensure session. bound_args['session'] = ( session if session else await stack.enter_async_context(svc.app.db.session()) ) - + # Call and serialize result if requested. db_res = await db_exec(**bound_args) return await bound_args['session'].run_sync( lambda _, data: serializer(data), db_res diff --git a/src/biodm/tests/conftest.py b/src/biodm/tests/conftest.py index f1c9c42..a3ddc25 100644 --- a/src/biodm/tests/conftest.py +++ b/src/biodm/tests/conftest.py @@ -7,6 +7,7 @@ from sqlalchemy.orm import Mapped from sqlalchemy.orm import relationship from starlette.config import Config +from starlette.testclient import TestClient from biodm.api import Api from biodm.components import Base @@ -28,7 +29,7 @@ class A(Base): id_c = sa.Column(sa.ForeignKey("C.id")) bs: Mapped[List["B"]] = relationship(secondary=asso_a_b, uselist=True, lazy="select") - ac: Mapped["C"] = relationship(foreign_keys=[id_c], backref="ca", lazy="select") + c: Mapped["C"] = relationship(foreign_keys=[id_c], backref="ca", lazy="select") class B(Base): @@ -48,7 +49,8 @@ class ASchema(ma.Schema): y = ma.fields.Integer() id_c = ma.fields.Integer() - bs = ma.fields.List(ma.fields.Nested("B")) + bs = ma.fields.List(ma.fields.Nested("BSchema")) + c = ma.fields.Nested("CSchema") class BSchema(ma.Schema): @@ -60,6 +62,8 @@ class CSchema(ma.Schema): id = ma.fields.Integer() data = ma.fields.String(required=True) + ca = ma.fields.Nested("ASchema") + ## Api componenents. class AController(ResourceController): @@ -132,8 +136,16 @@ def __init__(self, app): @pytest.fixture() -def client_args() -> dict: - return {"app": app, 'backend_options': {"use_uvloop": True}} +def client(): + with TestClient(** + { + "app": app, + 'backend_options': { + "use_uvloop": True + } + } + ) as c: + yield c def json_bytes(d): diff --git a/src/biodm/tests/test_basics.py b/src/biodm/tests/test_basics.py index 206f143..bb507db 100644 --- a/src/biodm/tests/test_basics.py +++ b/src/biodm/tests/test_basics.py @@ -1,23 +1,21 @@ -from starlette.testclient import TestClient +from biodm.components.controllers import overload_docstring - -def test_live_endpoint(client_args): +def test_live_endpoint(client): """""" - with TestClient(**client_args) as client: - response = client.get('/live') - assert response.status_code == 200 - assert response.text == 'live\n' + response = client.get('/live') + assert response.status_code == 200 + assert response.text == 'live\n' -def test_api_schema(client_args): +def test_api_schema(client): """""" - with TestClient(**client_args) as client: - response = client.get('/schema') - assert response.status_code == 200 - assert "biodm_test" in response.text - assert "0.1.0" in response.text + response = client.get('/schema') + assert response.status_code == 200 + assert "biodm_test" in response.text + assert "0.1.0" in response.text + -# TODO: -# def test_login_endpoint(): -# def test_users -# def test_groups +# def test_login_endpoint(client_args): +# with TestClient(**client_args) as client: +# response = client.get('/login') +# assert response.status_code == 200 diff --git a/src/biodm/tests/test_resource.py b/src/biodm/tests/test_resource.py index 55e1666..036b50d 100644 --- a/src/biodm/tests/test_resource.py +++ b/src/biodm/tests/test_resource.py @@ -1,24 +1,245 @@ +import pytest import json +from copy import deepcopy -from starlette.testclient import TestClient +from biodm import exceptions as exc from .conftest import json_bytes -def test_resource_schema(client_args): +def test_resource_schema(client): """""" - with TestClient(**client_args) as client: - response = client.get('/as/schema/') - assert response.status_code == 200 - json_response = json.loads(response.text) - assert "/" in json_response['paths'] - assert "/search/" in json_response['paths'] + response = client.get('/as/schema/') + json_response = json.loads(response.text) + assert response.status_code == 200 + assert "/" in json_response['paths'] + assert "/search/" in json_response['paths'] -def test_create_unary_resource(client_args): + +def test_create_unary_resource(client): """""" - with TestClient(**client_args) as client: - response = client.post('/bs/', content=json_bytes({'name': 'test'})) - assert response.status_code == 201 - assert "id" in response.text - assert "test" in response.text + response = client.post('/bs/', content=json_bytes({'name': 'test'})) + + assert response.status_code == 201 + assert "id" in response.text + assert "test" in response.text + + +def test_create_composite_resource(client): + item = { + 'x': 1, + 'y': 2, + 'c': {'data': '1234'}, + 'bs': [{'name': 'bip'},{'name': 'bap'},{'name': 'bop'}] + } + oracle = deepcopy(item) + oracle['id'] = 1 + oracle['id_c'] = 1 + oracle['c']['id'] = 1 + oracle['c']['ca'] = {} + for i, x in enumerate(oracle['bs']): + x['id'] = i+1 + + response = client.post('/as/', content=json_bytes(item)) + json_response = json.loads(response.text) + + assert response.status_code == 201 + assert json_response == oracle + + +@pytest.mark.xfail(raises=exc.PayloadEmptyError) +def test_create_empty_data(client): + client.post('/as/', content=json_bytes({})) + + +@pytest.mark.xfail(raises=exc.PayloadValidationError) +def test_create_wrong_data(client): + client.post('/as/', content=json_bytes({'wrong': False})) + + +def test_read_resource(client): + item = { + 'x': 1, + 'y': 2, + 'c': {'data': '1234'}, + } + + _ = client.post('/as/', content=json_bytes(item)) + response = client.get('/cs/1') + json_response = json.loads(response.text) + + assert response.status_code == 200 + assert json_response['data'] == '1234' + + +@pytest.mark.xfail(raises=exc.FailedRead) +def test_missing_resource(client): + client.get('/cs/1') + + +@pytest.mark.xfail(raises=exc.FailedDelete) +def test_missing_resource(client): + client.delete('/cs/1') + + +def test_readall_resource(client): + item1 = { + 'x': 1, + 'y': 2, + 'bs': [{'name': 'bip'},{'name': 'bap'},] + } + item2 = { + 'x': 3, + 'y': 4, + 'bs': [{'name': 'tit'},{'name': 'tat'},] + } + + _ = client.post('/as/', content=json_bytes([item1, item2])) + response = client.get('/bs/') + json_response = json.loads(response.text) + + assert response.status_code == 200 + assert len(json_response) == 4 + for b in item1['bs'] + item2['bs']: + assert any([b['name'] == jr['name'] for jr in json_response]) + + +def test_filter_resource_wildcard(client): + item1 = { + 'x': 1, + 'y': 2, + 'bs': [{'name': 'bip'},{'name': 'bap'},] + } + item2 = { + 'x': 3, + 'y': 4, + 'bs': [{'name': 'tit'},{'name': 'tat'},] + } + + _ = client.post('/as/', content=json_bytes([item1, item2])) + response = client.get('/bs?name=b*') + json_response = json.loads(response.text) + + assert response.status_code == 200 + assert len(json_response) == 2 + for b in item1['bs']: + assert any([b['name'] == jr['name'] for jr in json_response]) + for b in item2['bs']: + assert not any([b['name'] == jr['name'] for jr in json_response]) + + +def test_filter_resource_values(client): + item1 = { + 'x': 1, + 'y': 2, + 'bs': [{'name': 'bip'},{'name': 'bap'},] + } + item2 = { + 'x': 3, + 'y': 4, + 'bs': [{'name': 'tit'},{'name': 'tat'},] + } + + _ = client.post('/as/', content=json_bytes([item1, item2])) + response = client.get('/bs?name=bip,tat') + json_response = json.loads(response.text) + + assert response.status_code == 200 + assert len(json_response) == 2 + for jr in json_response: + assert jr['name'] in ('bip', 'tat') + + +def test_filter_resource_op(client): + item1 = { + 'x': 1, + 'y': 2, + 'bs': [{'name': 'bip'},{'name': 'bap'},] + } + item2 = { + 'x': 3, + 'y': 4, + 'bs': [{'name': 'tit'},{'name': 'tat'},] + } + + _ = client.post('/as/', content=json_bytes([item1, item2])) + response = client.get('/as?x.lt(2)') + json_response = next(iter(json.loads(response.text))) + + assert response.status_code == 200 + assert json_response['x'] == 1 + assert json_response['y'] == 2 + + +def test_filter_resource_nested(client): + item1 = {'x': 1, 'y': 2, 'c': {'data': '1234'},} + item2 = {'x': 3, 'y': 4, 'c': {'data': '4321'},} + + _ = client.post('/as/', content=json_bytes([item1, item2])) + response = client.get('/as?c.data=4321') + json_response = next(iter(json.loads(response.text))) + + assert response.status_code == 200 + assert json_response['x'] == 3 + assert json_response['y'] == 4 + + +def test_filter_resource_with_fields(client): + item1 = {'x': 1, 'y': 2, 'c': {'data': '1234'},} + item2 = {'x': 3, 'y': 4, 'c': {'data': '4321'},} + + _ = client.post('/as/', content=json_bytes([item1, item2])) + response = client.get('/as?x=1&fields=x,c') + json_response = next(iter(json.loads(response.text))) + + assert response.status_code == 200 + assert 'x' in json_response and json_response['x'] == 1 + assert 'y' not in json_response + assert 'c' in json_response and json_response['c']['data'] == '1234' + + +@pytest.mark.xfail(raises=ValueError) +def test_filter_wrong_op(client): + item1 = { + 'x': 1, + 'y': 2, + 'bs': [{'name': 'bip'},{'name': 'bap'},] + } + client.post('/as/', content=json_bytes(item1)) + client.get('/as?x.lt=2') + + +@pytest.mark.xfail(raises=ValueError) +def test_filter_wrong_wildcard(client): + item1 = { + 'x': 1, + 'y': 2, + 'bs': [{'name': 'bip'},{'name': 'bap'},] + } + client.post('/as/', content=json_bytes(item1)) + client.get('/as?y=2*') + + +@pytest.mark.xfail(raises=ValueError) +def test_filter_op_on_string(client): + item1 = { + 'x': 1, + 'y': 2, + 'bs': [{'name': 'bip'},{'name': 'bap'},] + } + client.post('/as/', content=json_bytes(item1)) + client.get('/bs?name.gt(2)') + + +def test_delete_resource(client): + item = { + 'x': 1, + 'y': 2, + } + + _ = client.post('/as/', content=json_bytes(item)) + response = client.delete('/as/1') + + assert response.status_code == 200 + assert "Deleted." in response.text diff --git a/src/example/app.py b/src/example/app.py index a03b3c7..79c3803 100755 --- a/src/example/app.py +++ b/src/example/app.py @@ -3,10 +3,10 @@ from biodm.api import Api -from example.entities.controllers import CONTROLLERS -from example.entities import tables, schemas +from entities.controllers import CONTROLLERS +from entities import tables, schemas # from example import manifests -from example import config +import config def main(): diff --git a/src/example/entities/schemas/dataset.py b/src/example/entities/schemas/dataset.py index bb014de..078c831 100644 --- a/src/example/entities/schemas/dataset.py +++ b/src/example/entities/schemas/dataset.py @@ -3,7 +3,7 @@ from biodm.tables import User, Group, ListGroup from biodm.schemas import UserSchema, GroupSchema, ListGroupSchema -from example.entities.tables import Dataset +from entities.tables import Dataset # from controllers import schemas # from .group import GroupSchema # from .user import UserSchema diff --git a/src/example/entities/schemas/file.py b/src/example/entities/schemas/file.py index f4c8107..0ca223e 100644 --- a/src/example/entities/schemas/file.py +++ b/src/example/entities/schemas/file.py @@ -1,7 +1,7 @@ from marshmallow import Schema, validate from marshmallow.fields import String, List, Nested, Integer, Bool -from example.entities.tables import File +from entities.tables import File from .dataset import DatasetSchema diff --git a/src/example/entities/schemas/tag.py b/src/example/entities/schemas/tag.py index 772ca94..334e751 100644 --- a/src/example/entities/schemas/tag.py +++ b/src/example/entities/schemas/tag.py @@ -1,7 +1,7 @@ from marshmallow import Schema from marshmallow.fields import String, List, Nested, Integer -from example.entities.tables import Tag +from entities.tables import Tag # from controllers import schemas from .dataset import DatasetSchema diff --git a/src/example/tests/test_app.py b/src/example/tests/test_app.py deleted file mode 100644 index b66d487..0000000 --- a/src/example/tests/test_app.py +++ /dev/null @@ -1,19 +0,0 @@ -import sys -from pathlib import Path - -import pytest -from starlette.responses import HTMLResponse -from starlette.testclient import TestClient - -from example import app - - -# @pytest.fixture -# def test_client_factory() -> TestClient: -# return TestClient - -def test_liveness(): - with TestClient(app.main(), backend_options={"use_uvloop": True}) as client: - response = client.get('/live') - assert response.status_code == 200 - assert response.text == 'live\n'