diff --git a/mock/__init__.py b/mock/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mock/mock_db.py b/mock/mock_db.py new file mode 100644 index 0000000..1f65a82 --- /dev/null +++ b/mock/mock_db.py @@ -0,0 +1,61 @@ +from __future__ import annotations +from typing import TYPE_CHECKING + +from sqlalchemy import event +from sqlalchemy import Engine +from sqlmodel import StaticPool +from sqlmodel import create_engine +from sqlmodel import Session + +from common import tables + + +if TYPE_CHECKING: + from typing import TypeVar + T = TypeVar("T", bound=tables.SQLModel) + + +def collation(string1, string2): + if string1 == string2: + return 0 + elif string1 > string2: + return 1 + else: + return -1 + +@event.listens_for(Engine, "connect") +def set_sqlite_pragma(dbapi_connection, dummy_connection_record): + dbapi_connection.create_collation("Hebrew_100_CI_AI_SC_UTF8", collation) + dbapi_connection.create_collation("Hebrew_CI_AI", collation) + cursor = dbapi_connection.cursor() + cursor.execute("PRAGMA foreign_keys=ON") + cursor.close() + +class MockDb: + def __init__(self): + self.db_uri = "sqlite:///:memory:?cache=shared" + self.engine = create_engine( + self.db_uri, + connect_args={"check_same_thread": False}, + poolclass=StaticPool, + ) + self.session = Session( + bind=self.engine, + expire_on_commit=False, + autoflush=True, + ) + tables.SQLModel.metadata.create_all(self.engine) + + def add(self, entity: T) -> T: + self.session.begin() + self.session.add(entity) + self.session.commit() + return entity + + def add_many(self, entities: list[T]) -> None: + self.session.begin() + for entity in entities: + self.session.add(entity) + self.session.commit() + for entity in entities: + self.session.refresh(entity) diff --git a/poetry.lock b/poetry.lock index 2560156..b3a3ad2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -862,6 +862,17 @@ files = [ {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, ] +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + [[package]] name = "jinja2" version = "3.1.3" @@ -1236,6 +1247,17 @@ files = [ antlr4-python3-runtime = "==4.9.*" PyYAML = ">=5.1.0" +[[package]] +name = "packaging" +version = "24.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, +] + [[package]] name = "platformdirs" version = "4.2.0" @@ -1251,6 +1273,21 @@ files = [ docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +[[package]] +name = "pluggy" +version = "1.4.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, + {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + [[package]] name = "ply" version = "3.11" @@ -1569,6 +1606,26 @@ files = [ {file = "pyodbc-5.1.0.tar.gz", hash = "sha256:397feee44561a6580be08cedbe986436859563f4bb378f48224655c8e987ea60"}, ] +[[package]] +name = "pytest" +version = "8.1.1" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.1.1-py3-none-any.whl", hash = "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7"}, + {file = "pytest-8.1.1.tar.gz", hash = "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.4,<2.0" + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -2528,4 +2585,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "251d55afb8611d65ee98d9ac64e54821b530b74da7224509c0e5863aa6c29e48" +content-hash = "ab76e08263ed9cbe83f7231e777ee072c1d19958a79ea395bf58ee12b165646a" diff --git a/pyproject.toml b/pyproject.toml index 158c795..510df73 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,7 @@ types-requests = "^2.31.0.10" types-redis = "^4.6.0.11" ruff = "0.3.4" alembic = "^1.13.1" +pytest = "^8.1.1" [tool.ruff] fix = true diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_secret_logic.py b/tests/test_secret_logic.py new file mode 100644 index 0000000..e598ce4 --- /dev/null +++ b/tests/test_secret_logic.py @@ -0,0 +1,62 @@ +import unittest +import datetime +import pytest +from sqlmodel import Session + +from common import tables +from common.error import HSError +from logic.game_logic import SecretLogic +from mock.mock_db import MockDb + + +class TestGameLogic(unittest.IsolatedAsyncioTestCase): + async def asyncSetUp(self): + self.db = MockDb() + self.date = datetime.date(2021, 1, 1) + self.testee = SecretLogic(session=self.db.session, dt=self.date) + + async def test_no_secret(self) -> None: + # act & assert + with pytest.raises(HSError): + await self.testee.get_secret() + + async def test_get_secret(self) -> None: + # arrange + db_secret = tables.SecretWord(word="test", game_date=self.date) + self.db.add(db_secret) + + # act + secret = await self.testee.get_secret() + + # assert + self.assertEqual(db_secret.word, secret) + + async def test_get_secret__cache(self): + # arrange + cached = self.db.add(tables.SecretWord(word="cached", game_date=self.date)) + await self.testee.get_secret() + with Session(self.db.engine) as session: + db_secret = session.get(tables.SecretWord, cached.id) + db_secret.word = "not_cached" + session.add(db_secret) + session.commit() + + # act + secret = await self.testee.get_secret() + + # assert + self.assertEqual("cached", secret) + + async def test_get_secret__dont_cache_if_no_secret(self): + # arrange + try: + await self.testee.get_secret() + except HSError: + pass + self.db.add(tables.SecretWord(word="not_cached", game_date=self.date)) + + # act + secret = await self.testee.get_secret() + + # assert + self.assertEqual("not_cached", secret)