Compare commits

...

4 commits

Author SHA1 Message Date
Campbell Alden
6c46a1df3a Add a default False to the user model email_confirmed field to avoid violation errors if the value is not explicitly set 2026-09-03 23:00:07 +09:00
Campbell Alden
32e3fe04a2 Set up an extremely lightweight set of tests for the user infra implementation 2026-09-03 22:58:29 +09:00
Campbell Alden
0d270c9cfb Setup exports in the infra __init__.py
This helps avoid issues where model classes are not evaluated. If the models
are not properly imported then Sqlalchemy can explode during database initialization
since the mapped columns have not yet been registered.

Later, having the models all imported into __init__.py will help with setting
up a test db since it will ensure that the registrations for all models is happening
as the classes are evaluated during the imports in the package init file.
2026-09-03 22:57:55 +09:00
Campbell Alden
f44f054f56 Implement equality for SecretBox 2026-09-03 22:56:14 +09:00
9 changed files with 82 additions and 3 deletions

4
pytest.ini Normal file
View file

@ -0,0 +1,4 @@
[pytest]
testpaths = src
python_files = *_test.py
addopts = -ra -q

View file

@ -6,5 +6,8 @@ setup(
packages=find_packages(), packages=find_packages(),
include_package_data=True, include_package_data=True,
package_data={'src': ['templates/**/*.html', 'templates/**/*.txt']}, package_data={'src': ['templates/**/*.html', 'templates/**/*.txt']},
exclude_package_data={
'': ['*_test.py'],
},
scripts=['./src/main.py'], scripts=['./src/main.py'],
) )

View file

@ -20,6 +20,7 @@ mkShell {
python313Packages.ruff python313Packages.ruff
python313Packages.python-lsp-server python313Packages.python-lsp-server
python313Packages.jedi-language-server python313Packages.jedi-language-server
python313Packages.pytest
ty ty
]; ];
} }

View file

@ -0,0 +1,27 @@
from .db import Base, CRUD, CRUDRepo, UpdateMissingEntryError, get_database
from .users import UserModel, UserRepoImpl
from .publication import (
PublicationEntryModel,
PublicationSequenceModel,
PublicationOrderModel,
PublicationModel,
PublicationRepoImpl,
)
from .subscription import SubscriptionModel, SubscriptionRepoImpl
__all__ = [
'Base',
'CRUD',
'CRUDRepo',
'UpdateMissingEntryError',
'get_database',
'UserModel',
'UserRepoImpl',
'PublicationEntryModel',
'PublicationSequenceModel',
'PublicationOrderModel',
'PublicationModel',
'PublicationRepoImpl',
'SubscriptionModel',
'SubscriptionRepoImpl',
]

8
src/infra/db_test.py Normal file
View file

@ -0,0 +1,8 @@
from src.infra import Base
from sqlalchemy import Engine, create_engine
def mock_db() -> Engine:
engine = create_engine('sqlite:///:memory:', echo=True)
Base.metadata.create_all(engine)
return engine

View file

@ -18,7 +18,7 @@ class UserModel(Base):
__tablename__ = 'user' __tablename__ = 'user'
id: Mapped[int] = mapped_column(primary_key=True) id: Mapped[int] = mapped_column(primary_key=True)
email: Mapped[str] = mapped_column(String(254), unique=True) email: Mapped[str] = mapped_column(String(254), unique=True)
email_confirmed: Mapped[bool] = mapped_column() email_confirmed: Mapped[bool] = mapped_column(default=False)
password_hash: Mapped[str] = mapped_column(VARCHAR(255)) password_hash: Mapped[str] = mapped_column(VARCHAR(255))
subscriptions: Mapped[list['SubscriptionModel']] = relationship(back_populates='user') subscriptions: Mapped[list['SubscriptionModel']] = relationship(back_populates='user')

31
src/infra/users_test.py Normal file
View file

@ -0,0 +1,31 @@
from src.utils.secret import SecretBox
from src.services.users.data import UserDTO, User
from src.services.users.repo import UserRepo
from src.infra.users import UserRepoImpl
from src.infra.db_test import mock_db
import pytest
DB = mock_db()
@pytest.fixture
def db():
return DB
@pytest.fixture
def repo(db):
return UserRepoImpl(db)
@pytest.fixture
def users(repo: UserRepo):
return [
repo.create(UserDTO('example@example.com', SecretBox('hunter1')), None),
repo.create(UserDTO('example2@example.com', SecretBox('hunter2')), None),
]
def test_getting_a_user_by_email(users: list[User], repo: UserRepo):
assert repo.get_user_by_email('example@example.com') == users[0]
assert repo.get_user_by_email('fred@example.com') is None

View file

@ -6,8 +6,7 @@ from src.services.email import EmailService, get_email_service
from src.services.auth import AuthService from src.services.auth import AuthService
from src.services.users.service import UserService from src.services.users.service import UserService
from dataclasses import dataclass from dataclasses import dataclass
from src.infra.db import get_database from src.infra import get_database, UserRepoImpl
from src.infra.users import UserRepoImpl
from waitress import serve from waitress import serve
from flask import Flask from flask import Flask
import argparse import argparse

View file

@ -7,6 +7,12 @@ class SecretBox[T]:
def expose_secret(self) -> T: def expose_secret(self) -> T:
return self._item return self._item
def __eq__(self, other):
if not isinstance(other, SecretBox):
return False
return self._item == other._item
def __str__(self): def __str__(self):
return '<SECRET>' return '<SECRET>'