fix: address final review findings for foundation plan
- C1: drop module-level app singleton in app/main.py so importing the package no longer validates Settings or creates DATA_DIR; run uvicorn with --factory in the Dockerfile. pytest now collects and passes with no ambient env vars. - I2: add missing app/auth, app/security, app/web __init__.py so setuptools discovers all five packages. - I3: resolve the Jinja2 template directory relative to __file__ instead of the process CWD. - I4: add .gitignore covering .env, data/, .venv/, caches and build artifacts so example deployment secrets cannot be committed. - I5: assert UserRepository.list_enabled() excludes disabled users. - M6: encode both operands before hmac.compare_digest in validate_csrf so a non-ASCII token yields 403 instead of an unhandled 500. - M9: remove unused relationship / HealthState imports. - M11: make session cookie https_only configurable via SESSION_HTTPS_ONLY (default unchanged: false). - M13: dispose SQLAlchemy engines in the db_session and client fixtures. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -22,8 +22,11 @@ def db_session() -> Session:
|
||||
)
|
||||
Base.metadata.create_all(engine)
|
||||
factory = sessionmaker(bind=engine, expire_on_commit=False)
|
||||
with factory() as session:
|
||||
yield session
|
||||
try:
|
||||
with factory() as session:
|
||||
yield session
|
||||
finally:
|
||||
engine.dispose()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -46,4 +49,8 @@ def client(tmp_path: Path) -> TestClient:
|
||||
DATABASE_URL=f"sqlite:///{tmp_path / 'app.db'}",
|
||||
SYNC_INTERVAL_MINUTES=5,
|
||||
)
|
||||
return TestClient(create_app(settings))
|
||||
app = create_app(settings)
|
||||
try:
|
||||
yield TestClient(app)
|
||||
finally:
|
||||
app.state.db_engine.dispose()
|
||||
|
||||
@@ -21,8 +21,20 @@ def test_create_two_independent_users(db_session, user_repository) -> None:
|
||||
garmin_password_enc="g-pw-2",
|
||||
)
|
||||
|
||||
disabled = user_repository.create(
|
||||
name="Paused",
|
||||
enabled=False,
|
||||
health_state=HealthState.DISABLED,
|
||||
mywhoosh_email_enc="mw-3",
|
||||
mywhoosh_password_enc="mw-pw-3",
|
||||
garmin_email_enc="g-3",
|
||||
garmin_password_enc="g-pw-3",
|
||||
)
|
||||
|
||||
assert first.id != second.id
|
||||
assert {u.name for u in user_repository.list_enabled()} == {"Max", "Anna"}
|
||||
assert disabled.id not in {u.id for u in user_repository.list_enabled()}
|
||||
assert {u.name for u in user_repository.list_all()} == {"Max", "Anna", "Paused"}
|
||||
|
||||
|
||||
def test_activity_external_id_is_unique_per_user(user_repository, activity_repository) -> None:
|
||||
|
||||
@@ -225,6 +225,23 @@ def test_create_user_rejects_invalid_csrf(client: TestClient) -> None:
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_create_user_rejects_non_ascii_csrf_token(client: TestClient) -> None:
|
||||
login(client)
|
||||
response = client.post(
|
||||
"/users",
|
||||
data={
|
||||
"csrf_token": "invalid-tokeü",
|
||||
"name": "Max",
|
||||
"mywhoosh_email": "max@example.com",
|
||||
"mywhoosh_password": "mw-secret",
|
||||
"garmin_email": "max-garmin@example.com",
|
||||
"garmin_password": "garmin-secret",
|
||||
"enabled": "on",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_dashboard_lists_created_user_without_secrets(client: TestClient) -> None:
|
||||
login(client)
|
||||
create_user_via_http(client)
|
||||
|
||||
Reference in New Issue
Block a user