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) <noreply@anthropic.com>
This commit is contained in:
adlee-was-taken
2026-04-11 00:01:53 -04:00
parent 3817566ed5
commit 8e23adee14
2 changed files with 22 additions and 11 deletions

View File

@@ -45,6 +45,7 @@ class User:
is_banned: Whether user is banned. is_banned: Whether user is banned.
ban_reason: Reason for ban (if banned). ban_reason: Reason for ban (if banned).
force_password_reset: Whether user must reset password on next login. 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 id: str
username: str username: str
@@ -66,6 +67,7 @@ class User:
is_banned: bool = False is_banned: bool = False
ban_reason: Optional[str] = None ban_reason: Optional[str] = None
force_password_reset: bool = False force_password_reset: bool = False
is_test_account: bool = False
def is_admin(self) -> bool: def is_admin(self) -> bool:
"""Check if user has admin role.""" """Check if user has admin role."""
@@ -100,6 +102,7 @@ class User:
"is_banned": self.is_banned, "is_banned": self.is_banned,
"ban_reason": self.ban_reason, "ban_reason": self.ban_reason,
"force_password_reset": self.force_password_reset, "force_password_reset": self.force_password_reset,
"is_test_account": self.is_test_account,
} }
if include_sensitive: if include_sensitive:
d["password_hash"] = self.password_hash d["password_hash"] = self.password_hash
@@ -146,6 +149,7 @@ class User:
is_banned=d.get("is_banned", False), is_banned=d.get("is_banned", False),
ban_reason=d.get("ban_reason"), ban_reason=d.get("ban_reason"),
force_password_reset=d.get("force_password_reset", False), force_password_reset=d.get("force_password_reset", False),
is_test_account=d.get("is_test_account", False),
) )

View File

@@ -476,6 +476,7 @@ class UserStore:
guest_id: Optional[str] = None, guest_id: Optional[str] = None,
verification_token: Optional[str] = None, verification_token: Optional[str] = None,
verification_expires: Optional[datetime] = None, verification_expires: Optional[datetime] = None,
is_test_account: bool = False,
) -> Optional[User]: ) -> Optional[User]:
""" """
Create a new user account. Create a new user account.
@@ -488,6 +489,7 @@ class UserStore:
guest_id: Guest session ID if converting. guest_id: Guest session ID if converting.
verification_token: Email verification token. verification_token: Email verification token.
verification_expires: Token expiration time. verification_expires: Token expiration time.
is_test_account: True for accounts created by the soak test harness.
Returns: Returns:
Created User, or None if username/email already exists. Created User, or None if username/email already exists.
@@ -497,12 +499,13 @@ class UserStore:
row = await conn.fetchrow( row = await conn.fetchrow(
""" """
INSERT INTO users_v2 (username, password_hash, email, role, guest_id, INSERT INTO users_v2 (username, password_hash, email, role, guest_id,
verification_token, verification_expires) verification_token, verification_expires,
VALUES ($1, $2, $3, $4, $5, $6, $7) is_test_account)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
RETURNING id, username, email, password_hash, role, email_verified, RETURNING id, username, email, password_hash, role, email_verified,
verification_token, verification_expires, reset_token, reset_expires, verification_token, verification_expires, reset_token, reset_expires,
guest_id, deleted_at, preferences, created_at, last_login, last_seen_at, 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, username,
password_hash, password_hash,
@@ -511,6 +514,7 @@ class UserStore:
guest_id, guest_id,
verification_token, verification_token,
verification_expires, verification_expires,
is_test_account,
) )
return self._row_to_user(row) return self._row_to_user(row)
except asyncpg.UniqueViolationError: except asyncpg.UniqueViolationError:
@@ -524,7 +528,7 @@ class UserStore:
SELECT id, username, email, password_hash, role, email_verified, SELECT id, username, email, password_hash, role, email_verified,
verification_token, verification_expires, reset_token, reset_expires, verification_token, verification_expires, reset_token, reset_expires,
guest_id, deleted_at, preferences, created_at, last_login, last_seen_at, 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 FROM users_v2
WHERE id = $1 WHERE id = $1
""", """,
@@ -540,7 +544,7 @@ class UserStore:
SELECT id, username, email, password_hash, role, email_verified, SELECT id, username, email, password_hash, role, email_verified,
verification_token, verification_expires, reset_token, reset_expires, verification_token, verification_expires, reset_token, reset_expires,
guest_id, deleted_at, preferences, created_at, last_login, last_seen_at, 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 FROM users_v2
WHERE LOWER(username) = LOWER($1) WHERE LOWER(username) = LOWER($1)
""", """,
@@ -556,7 +560,7 @@ class UserStore:
SELECT id, username, email, password_hash, role, email_verified, SELECT id, username, email, password_hash, role, email_verified,
verification_token, verification_expires, reset_token, reset_expires, verification_token, verification_expires, reset_token, reset_expires,
guest_id, deleted_at, preferences, created_at, last_login, last_seen_at, 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 FROM users_v2
WHERE LOWER(email) = LOWER($1) WHERE LOWER(email) = LOWER($1)
""", """,
@@ -572,7 +576,7 @@ class UserStore:
SELECT id, username, email, password_hash, role, email_verified, SELECT id, username, email, password_hash, role, email_verified,
verification_token, verification_expires, reset_token, reset_expires, verification_token, verification_expires, reset_token, reset_expires,
guest_id, deleted_at, preferences, created_at, last_login, last_seen_at, 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 FROM users_v2
WHERE verification_token = $1 WHERE verification_token = $1
""", """,
@@ -588,7 +592,7 @@ class UserStore:
SELECT id, username, email, password_hash, role, email_verified, SELECT id, username, email, password_hash, role, email_verified,
verification_token, verification_expires, reset_token, reset_expires, verification_token, verification_expires, reset_token, reset_expires,
guest_id, deleted_at, preferences, created_at, last_login, last_seen_at, 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 FROM users_v2
WHERE reset_token = $1 WHERE reset_token = $1
""", """,
@@ -686,7 +690,7 @@ class UserStore:
RETURNING id, username, email, password_hash, role, email_verified, RETURNING id, username, email, password_hash, role, email_verified,
verification_token, verification_expires, reset_token, reset_expires, verification_token, verification_expires, reset_token, reset_expires,
guest_id, deleted_at, preferences, created_at, last_login, last_seen_at, 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: async with self.pool.acquire() as conn:
@@ -730,7 +734,8 @@ class UserStore:
""" """
SELECT id, username, email, password_hash, role, email_verified, SELECT id, username, email, password_hash, role, email_verified,
verification_token, verification_expires, reset_token, reset_expires, 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 FROM users_v2
ORDER BY created_at DESC ORDER BY created_at DESC
""" """
@@ -740,7 +745,8 @@ class UserStore:
""" """
SELECT id, username, email, password_hash, role, email_verified, SELECT id, username, email, password_hash, role, email_verified,
verification_token, verification_expires, reset_token, reset_expires, 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 FROM users_v2
WHERE is_active = TRUE AND deleted_at IS NULL WHERE is_active = TRUE AND deleted_at IS NULL
ORDER BY created_at DESC ORDER BY created_at DESC
@@ -1033,6 +1039,7 @@ class UserStore:
is_banned=row.get("is_banned", False) or False, is_banned=row.get("is_banned", False) or False,
ban_reason=row.get("ban_reason"), ban_reason=row.get("ban_reason"),
force_password_reset=row.get("force_password_reset", False) or False, 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: def _row_to_session(self, row: asyncpg.Record) -> UserSession: