From 8e23adee143b8c5f03694284ac4ae3fedfbea63b Mon Sep 17 00:00:00 2001 From: adlee-was-taken Date: Sat, 11 Apr 2026 00:01:53 -0400 Subject: [PATCH] feat(server): propagate is_test_account through User model & store User dataclass, create_user, and all SELECT lists now round-trip the new column. Value is always FALSE until Task 4 wires the register flow to the invite code's marks_as_test flag. Co-Authored-By: Claude Opus 4.6 (1M context) --- server/models/user.py | 4 ++++ server/stores/user_store.py | 29 ++++++++++++++++++----------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/server/models/user.py b/server/models/user.py index c2860ac..83403a7 100644 --- a/server/models/user.py +++ b/server/models/user.py @@ -45,6 +45,7 @@ class User: is_banned: Whether user is banned. ban_reason: Reason for ban (if banned). force_password_reset: Whether user must reset password on next login. + is_test_account: True for accounts created by the soak test harness. """ id: str username: str @@ -66,6 +67,7 @@ class User: is_banned: bool = False ban_reason: Optional[str] = None force_password_reset: bool = False + is_test_account: bool = False def is_admin(self) -> bool: """Check if user has admin role.""" @@ -100,6 +102,7 @@ class User: "is_banned": self.is_banned, "ban_reason": self.ban_reason, "force_password_reset": self.force_password_reset, + "is_test_account": self.is_test_account, } if include_sensitive: d["password_hash"] = self.password_hash @@ -146,6 +149,7 @@ class User: is_banned=d.get("is_banned", False), ban_reason=d.get("ban_reason"), force_password_reset=d.get("force_password_reset", False), + is_test_account=d.get("is_test_account", False), ) diff --git a/server/stores/user_store.py b/server/stores/user_store.py index febf639..c103762 100644 --- a/server/stores/user_store.py +++ b/server/stores/user_store.py @@ -476,6 +476,7 @@ class UserStore: guest_id: Optional[str] = None, verification_token: Optional[str] = None, verification_expires: Optional[datetime] = None, + is_test_account: bool = False, ) -> Optional[User]: """ Create a new user account. @@ -488,6 +489,7 @@ class UserStore: guest_id: Guest session ID if converting. verification_token: Email verification token. verification_expires: Token expiration time. + is_test_account: True for accounts created by the soak test harness. Returns: Created User, or None if username/email already exists. @@ -497,12 +499,13 @@ class UserStore: row = await conn.fetchrow( """ INSERT INTO users_v2 (username, password_hash, email, role, guest_id, - verification_token, verification_expires) - VALUES ($1, $2, $3, $4, $5, $6, $7) + verification_token, verification_expires, + is_test_account) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id, username, email, password_hash, role, email_verified, verification_token, verification_expires, reset_token, reset_expires, guest_id, deleted_at, preferences, created_at, last_login, last_seen_at, - is_active, is_banned, ban_reason, force_password_reset + is_active, is_banned, ban_reason, force_password_reset, is_test_account """, username, password_hash, @@ -511,6 +514,7 @@ class UserStore: guest_id, verification_token, verification_expires, + is_test_account, ) return self._row_to_user(row) except asyncpg.UniqueViolationError: @@ -524,7 +528,7 @@ class UserStore: SELECT id, username, email, password_hash, role, email_verified, verification_token, verification_expires, reset_token, reset_expires, guest_id, deleted_at, preferences, created_at, last_login, last_seen_at, - is_active, is_banned, ban_reason, force_password_reset + is_active, is_banned, ban_reason, force_password_reset, is_test_account FROM users_v2 WHERE id = $1 """, @@ -540,7 +544,7 @@ class UserStore: SELECT id, username, email, password_hash, role, email_verified, verification_token, verification_expires, reset_token, reset_expires, guest_id, deleted_at, preferences, created_at, last_login, last_seen_at, - is_active, is_banned, ban_reason, force_password_reset + is_active, is_banned, ban_reason, force_password_reset, is_test_account FROM users_v2 WHERE LOWER(username) = LOWER($1) """, @@ -556,7 +560,7 @@ class UserStore: SELECT id, username, email, password_hash, role, email_verified, verification_token, verification_expires, reset_token, reset_expires, guest_id, deleted_at, preferences, created_at, last_login, last_seen_at, - is_active, is_banned, ban_reason, force_password_reset + is_active, is_banned, ban_reason, force_password_reset, is_test_account FROM users_v2 WHERE LOWER(email) = LOWER($1) """, @@ -572,7 +576,7 @@ class UserStore: SELECT id, username, email, password_hash, role, email_verified, verification_token, verification_expires, reset_token, reset_expires, guest_id, deleted_at, preferences, created_at, last_login, last_seen_at, - is_active, is_banned, ban_reason, force_password_reset + is_active, is_banned, ban_reason, force_password_reset, is_test_account FROM users_v2 WHERE verification_token = $1 """, @@ -588,7 +592,7 @@ class UserStore: SELECT id, username, email, password_hash, role, email_verified, verification_token, verification_expires, reset_token, reset_expires, guest_id, deleted_at, preferences, created_at, last_login, last_seen_at, - is_active, is_banned, ban_reason, force_password_reset + is_active, is_banned, ban_reason, force_password_reset, is_test_account FROM users_v2 WHERE reset_token = $1 """, @@ -686,7 +690,7 @@ class UserStore: RETURNING id, username, email, password_hash, role, email_verified, verification_token, verification_expires, reset_token, reset_expires, guest_id, deleted_at, preferences, created_at, last_login, last_seen_at, - is_active, is_banned, ban_reason, force_password_reset + is_active, is_banned, ban_reason, force_password_reset, is_test_account """ async with self.pool.acquire() as conn: @@ -730,7 +734,8 @@ class UserStore: """ SELECT id, username, email, password_hash, role, email_verified, verification_token, verification_expires, reset_token, reset_expires, - guest_id, deleted_at, preferences, created_at, last_login, is_active + guest_id, deleted_at, preferences, created_at, last_login, is_active, + is_test_account FROM users_v2 ORDER BY created_at DESC """ @@ -740,7 +745,8 @@ class UserStore: """ SELECT id, username, email, password_hash, role, email_verified, verification_token, verification_expires, reset_token, reset_expires, - guest_id, deleted_at, preferences, created_at, last_login, is_active + guest_id, deleted_at, preferences, created_at, last_login, is_active, + is_test_account FROM users_v2 WHERE is_active = TRUE AND deleted_at IS NULL ORDER BY created_at DESC @@ -1033,6 +1039,7 @@ class UserStore: is_banned=row.get("is_banned", False) or False, ban_reason=row.get("ban_reason"), force_password_reset=row.get("force_password_reset", False) or False, + is_test_account=row.get("is_test_account", False) or False, ) def _row_to_session(self, row: asyncpg.Record) -> UserSession: