From 1cd52b576c4b152059d114ae448e63db2c5b0ad4 Mon Sep 17 00:00:00 2001 From: siye566 <114060217+siye566@users.noreply.github.com> Date: Thu, 8 Oct 2026 10:19:40 +0800 Subject: [PATCH] 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 --- backend/tests/AGENTS.md | 7 +++++++ backend/tests/test_auth.py | 23 +++++++++++++++++------ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/backend/tests/AGENTS.md b/backend/tests/AGENTS.md index 048fc459d9..0ff5f021d1 100644 --- a/backend/tests/AGENTS.md +++ b/backend/tests/AGENTS.md @@ -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 diff --git a/backend/tests/test_auth.py b/backend/tests/test_auth.py index 4c348b7611..152542df03 100644 --- a/backend/tests/test_auth.py +++ b/backend/tests/test_auth.py @@ -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()