feat(02-01): add BackupCode ORM model, password_must_change field, Alembic migration, extend Settings
- Add BackupCode model to db/models.py with user_id FK, code_hash (Argon2), used_at (nullable) - Add ix_backup_codes_user_id index on backup_codes.user_id - Add password_must_change BOOLEAN NOT NULL DEFAULT false to User model (ADMIN-01) - Extend config.py Settings with JWT, SMTP, admin bootstrap, and CORS fields (D-01, D-04, D-09) - Add env_list_separator=',' for cors_origins env var parsing - Append PyJWT, pwdlib[argon2], pyotp, aioredis, slowapi to requirements.txt - Add .env.example entries for SECRET_KEY, ADMIN_EMAIL, SMTP_*, CORS_ORIGINS - Create migration 0002 adding backup_codes table and password_must_change column - Add TDD tests for all Task 1 acceptance criteria (7 tests pass)
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
"""
|
||||
TDD tests for Task 1: BackupCode ORM model, password_must_change field, Settings extension.
|
||||
|
||||
These tests should FAIL before implementation (RED phase).
|
||||
"""
|
||||
import pytest
|
||||
|
||||
|
||||
def test_backup_code_model_exists():
|
||||
"""BackupCode ORM model must exist and have correct tablename + columns."""
|
||||
from db.models import BackupCode
|
||||
assert BackupCode.__tablename__ == "backup_codes"
|
||||
assert hasattr(BackupCode, "id")
|
||||
assert hasattr(BackupCode, "user_id")
|
||||
assert hasattr(BackupCode, "code_hash")
|
||||
assert hasattr(BackupCode, "used_at")
|
||||
|
||||
|
||||
def test_user_has_password_must_change():
|
||||
"""User model must have password_must_change column."""
|
||||
from db.models import User
|
||||
assert hasattr(User, "password_must_change")
|
||||
|
||||
|
||||
def test_settings_has_jwt_config():
|
||||
"""Settings must have access_token_expire_minutes and refresh_token_expire_days."""
|
||||
from config import settings
|
||||
assert hasattr(settings, "access_token_expire_minutes")
|
||||
assert settings.access_token_expire_minutes == 15
|
||||
assert hasattr(settings, "refresh_token_expire_days")
|
||||
assert settings.refresh_token_expire_days == 30
|
||||
|
||||
|
||||
def test_settings_has_smtp_config():
|
||||
"""Settings must have SMTP fields."""
|
||||
from config import settings
|
||||
assert hasattr(settings, "smtp_host")
|
||||
assert hasattr(settings, "smtp_port")
|
||||
assert hasattr(settings, "smtp_user")
|
||||
assert hasattr(settings, "smtp_password")
|
||||
assert hasattr(settings, "smtp_from")
|
||||
|
||||
|
||||
def test_settings_has_admin_config():
|
||||
"""Settings must have admin bootstrap fields."""
|
||||
from config import settings
|
||||
assert hasattr(settings, "admin_email")
|
||||
assert hasattr(settings, "admin_password")
|
||||
|
||||
|
||||
def test_settings_has_cors_origins():
|
||||
"""Settings must have cors_origins as a list with default localhost."""
|
||||
from config import settings
|
||||
assert hasattr(settings, "cors_origins")
|
||||
assert isinstance(settings.cors_origins, list)
|
||||
assert "http://localhost:5173" in settings.cors_origins
|
||||
|
||||
|
||||
def test_backup_code_table_in_schema(db_session):
|
||||
"""backup_codes table must be created in the SQLite test schema."""
|
||||
from db.models import BackupCode
|
||||
import uuid
|
||||
# Should not raise — table must exist
|
||||
bc = BackupCode(
|
||||
id=uuid.uuid4(),
|
||||
user_id=uuid.uuid4(),
|
||||
code_hash="testhash",
|
||||
used_at=None,
|
||||
)
|
||||
db_session.add(bc)
|
||||
# We don't commit here — just verify the model is valid
|
||||
Reference in New Issue
Block a user