test(auth): make user-list ordering independent of clock resolution (#6362)

* test(auth): make user-list ordering independent of clock resolution

* docs(test): clarify deterministic user-list ordering

Explain the lexical tie-break over stored canonical UUID strings and give
user-repository ordering its own guidance section, separate from router fixtures.

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
This commit is contained in:
siye566 2026-10-08 10:19:40 +08:00 committed by GitHub
parent e0bd70279e
commit 1cd52b576c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 24 additions and 6 deletions

View File

@ -67,6 +67,13 @@ through offline HTTP transports. Keep its directly imported `anthropic` SDK in
the backend `dev` dependency group rather than relying on `langchain-anthropic`
to supply it transitively.
## User repository ordering
`test_auth.py` pins `list_user_ids()` ordering with fixed UTC timestamps and UUIDs.
Cover both creation-time precedence and the lexical stored-ID tie-break for
equal timestamps through the real SQLite repository; do not assume wall-clock
calls are distinct or weaken the result to an unordered comparison.
## Router auth fixtures
For owner-scoped route assertions, pass a stable `user_factory` and

View File

@ -1,8 +1,8 @@
"""Tests for authentication module: JWT, password hashing, AuthContext, and authz decorators."""
from datetime import timedelta
from datetime import UTC, datetime, timedelta
from unittest.mock import AsyncMock, MagicMock, patch
from uuid import uuid4
from uuid import UUID, uuid4
import bcrypt
import pytest
@ -425,7 +425,14 @@ def test_sqlite_round_trip_new_fields():
asyncio.run(_run())
def test_user_repository_lists_registered_user_ids(tmp_path):
@pytest.mark.parametrize(
("second_created_delta", "expected_ids"),
[
pytest.param(timedelta(seconds=1), (UUID(int=2), UUID(int=1)), id="creation-time-first"),
pytest.param(timedelta(0), (UUID(int=1), UUID(int=2)), id="id-breaks-time-tie"),
],
)
def test_user_repository_lists_registered_user_ids(tmp_path, second_created_delta, expected_ids):
import asyncio
from app.gateway.auth.repositories.sqlite import SQLiteUserRepository
@ -440,10 +447,14 @@ def test_user_repository_lists_registered_user_ids(tmp_path):
)
try:
repo = SQLiteUserRepository(get_session_factory())
first = await repo.create_user(User(email="first@test.com", password_hash="hash"))
second = await repo.create_user(User(email="second@test.com", password_hash="hash"))
assert await repo.list_user_ids() == []
created_at = datetime(2026, 1, 1, tzinfo=UTC)
# Reverse stored UUID string order relative to insertion order so both sort keys matter.
# The ID tie-break is lexical; canonical UUID strings preserve UUID integer order.
await repo.create_user(User(id=UUID(int=2), email="first@test.com", password_hash="hash", created_at=created_at))
await repo.create_user(User(id=UUID(int=1), email="second@test.com", password_hash="hash", created_at=created_at + second_created_delta))
assert await repo.list_user_ids() == [str(first.id), str(second.id)]
assert await repo.list_user_ids() == [str(user_id) for user_id in expected_ids]
finally:
await close_engine()