import contextlib
import os
import pathlib

import hypothesis.strategies as st
import pytest
import strawberry
from hypothesis.strategies import SearchStrategy
from sqlalchemy.pool import StaticPool
from sqlmodel import Session, SQLModel, create_engine
from starlette.testclient import TestClient

from fastapi_server.database.database import get_session
from fastapi_server.main import app
from fastapi_server.routes.graphql import schema

TEST_DB_FILE_PATH = 'test.db'
TEST_DB_URL = f'sqlite:///{TEST_DB_FILE_PATH}'
TEST_DB_MEMORY_PATH = ':memory:'
TEST_DB_MEMORY_URL = f'sqlite:///{TEST_DB_MEMORY_PATH}'


class BaseTest:
    method_client: TestClient = None  # type: ignore
    method_session: Session = None  # type: ignore
    example_client: TestClient = None  # type: ignore
    example_session: Session = None  # type: ignore

    def setup_method(self, _method):
        BaseTest.method_session = BaseTest.create_memory_sesssion()
        # BaseTest.method_session = BaseTest.create_file_sesssion()
        BaseTest.method_client = TestClient(app)
        BaseTest.method_client.app.dependency_overrides[get_session] = BaseTest.method_get_session

    def teardown_method(self, _method):
        if BaseTest.method_session is not None:
            db_path = pathlib.Path(TEST_DB_FILE_PATH)
            # Remove file if it wasnt a memory database
            if BaseTest.method_session.bind.url.database != TEST_DB_MEMORY_PATH and db_path.is_file():
                os.remove(db_path)
            BaseTest.method_session.close()
            BaseTest.method_session = None
        app.dependency_overrides.clear()
        BaseTest.method_client = None

    @classmethod
    def create_file_sesssion(cls):
        engine = create_engine(TEST_DB_URL, connect_args={'check_same_thread': False}, poolclass=StaticPool)
        SQLModel.metadata.create_all(engine)
        with Session(engine, autoflush=False, autocommit=False) as session:
            return session

    @classmethod
    def create_memory_sesssion(cls):
        engine = create_engine(TEST_DB_MEMORY_URL, connect_args={'check_same_thread': False}, poolclass=StaticPool)
        SQLModel.metadata.create_all(engine)
        with Session(engine, autoflush=False, autocommit=False) as session:
            # Can this be "yield" instead?
            return session

    @classmethod
    @contextlib.contextmanager
    def example_session_context(cls):
        """
        Used together with hypothesis: create a class-variable to be used in hypothesis. Unset once the test is over.
        Session strategy doesn't seem to work as expected, nor does setup example and teardown example with sql.
        """
        assert not isinstance(cls.example_session, Session)
        try:
            # cls.example_session = cls.create_file_sesssion()
            cls.example_session = cls.create_memory_sesssion()
            yield cls.example_session
        finally:
            if cls.example_session is not None:
                db_path = pathlib.Path(TEST_DB_FILE_PATH)
                # Remove file if it wasnt a memory database
                if cls.example_session.bind.url.database != TEST_DB_MEMORY_PATH and db_path.is_file():
                    os.remove(db_path)
                cls.example_session.close()
                cls.example_session = None

    @classmethod
    @contextlib.contextmanager
    def method_client_context(cls):
        """ Same reasoning as above. """
        # See https://sqlmodel.tiangolo.com/tutorial/fastapi/tests/#pytest-fixtures
        # https://strawberry.rocks/docs/integrations/fastapi#context_getter
        # app.dependency_overrides.clear()
        app.dependency_overrides[get_session] = cls.method_get_session
        cls.method_client = TestClient(app)
        try:
            yield cls.method_client
        finally:
            cls.method_client = None
            app.dependency_overrides.clear()

    @classmethod
    @contextlib.contextmanager
    def example_client_context(cls):
        """ Same reasoning as above. """
        # See https://sqlmodel.tiangolo.com/tutorial/fastapi/tests/#pytest-fixtures
        # https://strawberry.rocks/docs/integrations/fastapi#context_getter
        with cls.example_session_context() as _session:
            app.dependency_overrides[get_session] = cls.example_get_session
            cls.example_client = TestClient(app)
            try:
                yield cls.example_client
            finally:
                cls.example_client = None
                app.dependency_overrides.clear()

    @classmethod
    def method_get_session(cls) -> Session:  # type: ignore
        assert isinstance(cls.method_session, Session)
        assert cls.method_session.bind.url.database in {TEST_DB_FILE_PATH, TEST_DB_MEMORY_PATH}  # type: ignore
        yield cls.method_session

    @classmethod
    def example_get_session(cls) -> Session:  # type: ignore
        assert isinstance(cls.example_session, Session)
        assert cls.example_session.bind.url.database in {TEST_DB_FILE_PATH, TEST_DB_MEMORY_PATH}  # type: ignore
        yield cls.example_session

    @classmethod
    def example_get_client(cls) -> TestClient:  # type: ignore
        yield cls.example_client

    @pytest.fixture(name='method_client_fixture')
    def method_client_fixture(self) -> TestClient:  # type: ignore
        with BaseTest.method_client_context() as client:
            assert isinstance(client, TestClient)
            yield client

    @pytest.fixture(name='example_client_fixture')
    def example_client_fixture(self) -> TestClient:  # type: ignore
        assert isinstance(BaseTest.example_client, TestClient)
        yield self.example_client

    @pytest.fixture(name='method_session_fixture')
    def method_session_fixture(self) -> Session:  # type: ignore
        assert isinstance(BaseTest.method_session, Session)
        yield BaseTest.method_session

    @pytest.fixture(name='example_session_fixture')
    def example_session_fixture(self) -> Session:  # type: ignore
        assert isinstance(BaseTest.example_session, Session)
        yield BaseTest.example_session

    @classmethod
    def get_schema(cls) -> strawberry.Schema:
        return schema

    @pytest.fixture(name='schema_fixture')
    def schema_fixture(self):
        return BaseTest.get_schema()

    @classmethod
    def schema_strategy(cls) -> SearchStrategy:
        """ Deprecated? """
        return st.builds(cls.get_schema)

hypothesis.strategies.builds sqlmodel.create_engine pathlib.Path starlette.testclient.TestClient fastapi_server.main.app.dependency_overrides.clear pytest.fixture sqlmodel.Session sqlmodel.SQLModel.metadata.create_all os.remove
[((5233, 5277), 'pytest.fixture', 'pytest.fixture', ([], {'name': '"""method_client_fixture"""'}), "(name='method_client_fixture')\n", (5247, 5277), False, 'import pytest\n'), ((5483, 5528), 'pytest.fixture', 'pytest.fixture', ([], {'name': '"""example_client_fixture"""'}), "(name='example_client_fixture')\n", (5497, 5528), False, 'import pytest\n'), ((5700, 5745), 'pytest.fixture', 'pytest.fixture', ([], {'name': '"""method_session_fixture"""'}), "(name='method_session_fixture')\n", (5714, 5745), False, 'import pytest\n'), ((5915, 5961), 'pytest.fixture', 'pytest.fixture', ([], {'name': '"""example_session_fixture"""'}), "(name='example_session_fixture')\n", (5929, 5961), False, 'import pytest\n'), ((6220, 6257), 'pytest.fixture', 'pytest.fixture', ([], {'name': '"""schema_fixture"""'}), "(name='schema_fixture')\n", (6234, 6257), False, 'import pytest\n'), ((1042, 1057), 'starlette.testclient.TestClient', 'TestClient', (['app'], {}), '(app)\n', (1052, 1057), False, 'from starlette.testclient import TestClient\n'), ((1589, 1621), 'fastapi_server.main.app.dependency_overrides.clear', 'app.dependency_overrides.clear', ([], {}), '()\n', (1619, 1621), False, 'from fastapi_server.main import app\n'), ((1730, 1825), 'sqlmodel.create_engine', 'create_engine', (['TEST_DB_URL'], {'connect_args': "{'check_same_thread': False}", 'poolclass': 'StaticPool'}), "(TEST_DB_URL, connect_args={'check_same_thread': False},\n    poolclass=StaticPool)\n", (1743, 1825), False, 'from sqlmodel import Session, SQLModel, create_engine\n'), ((1830, 1866), 'sqlmodel.SQLModel.metadata.create_all', 'SQLModel.metadata.create_all', (['engine'], {}), '(engine)\n', (1858, 1866), False, 'from sqlmodel import Session, SQLModel, create_engine\n'), ((2042, 2144), 'sqlmodel.create_engine', 'create_engine', (['TEST_DB_MEMORY_URL'], {'connect_args': "{'check_same_thread': False}", 'poolclass': 'StaticPool'}), "(TEST_DB_MEMORY_URL, connect_args={'check_same_thread': False},\n    poolclass=StaticPool)\n", (2055, 2144), False, 'from sqlmodel import Session, SQLModel, create_engine\n'), ((2149, 2185), 'sqlmodel.SQLModel.metadata.create_all', 'SQLModel.metadata.create_all', (['engine'], {}), '(engine)\n', (2177, 2185), False, 'from sqlmodel import Session, SQLModel, create_engine\n'), ((3751, 3766), 'starlette.testclient.TestClient', 'TestClient', (['app'], {}), '(app)\n', (3761, 3766), False, 'from starlette.testclient import TestClient\n'), ((6434, 6459), 'hypothesis.strategies.builds', 'st.builds', (['cls.get_schema'], {}), '(cls.get_schema)\n', (6443, 6459), True, 'import hypothesis.strategies as st\n'), ((1268, 1299), 'pathlib.Path', 'pathlib.Path', (['TEST_DB_FILE_PATH'], {}), '(TEST_DB_FILE_PATH)\n', (1280, 1299), False, 'import pathlib\n'), ((1880, 1930), 'sqlmodel.Session', 'Session', (['engine'], {'autoflush': '(False)', 'autocommit': '(False)'}), '(engine, autoflush=False, autocommit=False)\n', (1887, 1930), False, 'from sqlmodel import Session, SQLModel, create_engine\n'), ((2199, 2249), 'sqlmodel.Session', 'Session', (['engine'], {'autoflush': '(False)', 'autocommit': '(False)'}), '(engine, autoflush=False, autocommit=False)\n', (2206, 2249), False, 'from sqlmodel import Session, SQLModel, create_engine\n'), ((3882, 3914), 'fastapi_server.main.app.dependency_overrides.clear', 'app.dependency_overrides.clear', ([], {}), '()\n', (3912, 3914), False, 'from fastapi_server.main import app\n'), ((4367, 4382), 'starlette.testclient.TestClient', 'TestClient', (['app'], {}), '(app)\n', (4377, 4382), False, 'from starlette.testclient import TestClient\n'), ((1475, 1493), 'os.remove', 'os.remove', (['db_path'], {}), '(db_path)\n', (1484, 1493), False, 'import os\n'), ((3002, 3033), 'pathlib.Path', 'pathlib.Path', (['TEST_DB_FILE_PATH'], {}), '(TEST_DB_FILE_PATH)\n', (3014, 3033), False, 'import pathlib\n'), ((4520, 4552), 'fastapi_server.main.app.dependency_overrides.clear', 'app.dependency_overrides.clear', ([], {}), '()\n', (4550, 4552), False, 'from fastapi_server.main import app\n'), ((3217, 3235), 'os.remove', 'os.remove', (['db_path'], {}), '(db_path)\n', (3226, 3235), False, 'import os\n')]