Skip to content

[P0][Test Isolation] Fixture setup hangs after content favorite test before content pipeline test #27

Description

@fxbin

Parent: #14

Reproduced evidence

After #22's DuckDB isolation fix, diagnostic PR #23 progressed through roughly 19% of 817 backend tests before reaching the next independent hang.

Observed sequence:

  1. tests/test_content_favorite_api.py::test_content_favorite_toggle_returns_favorite_id_and_state PASSED.
  2. The next visible test was tests/test_content_pipeline.py::test_update_source_error_uses_readable_fallback_for_blank_message.
  3. After 30 seconds without progress, faulthandler showed pytest waiting in async-generator fixture setup (pytest_asyncio / event-loop select), not inside the next test body.
  4. The 180-second diagnostic timeout ended the run with exit 124.
  5. PostgreSQL logged multiple client connections terminating with open transactions when the test process was killed.

Root-cause chain confirmed by code tracing

The favorite test fixture itself uses an in-memory SQLite engine, but POST /contents/{content_id}/favorite calls trigger_vector_rebuild(current_user.id) after the favorite write succeeds.

trigger_vector_rebuild():

  • calls asyncio.get_running_loop().create_task(...) without returning/tracking the task;
  • opens its own session from the module-level app.core.database.async_session (the CI PostgreSQL database), not the test fixture's SQLite session;
  • runs rebuild_user_vector() and commits asynchronously.

Therefore the unit-style favorite test can pass while a real PostgreSQL background transaction continues after the response/fixture body. The next global autouse clean_tables fixture then executes TRUNCATE ... CASCADE and can wait behind that task/transaction before the next test body starts.

Objective

Make the content-favorite test hermetic: authorization/API behavior should be tested without launching the production global interest-vector background task.

Minimal test fix

  • inject monkeypatch into contents_client;
  • monkeypatch app.services.interest_vector_service.trigger_vector_rebuild to a no-op for this fixture;
  • preserve all favorite toggle/state assertions;
  • do not alter production vector behavior in this slice.

Separate runtime concern

The production fire-and-forget task has no lifecycle handle/cancellation/join contract. That is a broader runtime reliability concern and must be tracked separately under #6 rather than hidden inside this test-isolation fix.

Acceptance gates

  • targeted order test_content_favorite_toggle_returns_favorite_id_and_statetest_update_source_error_uses_readable_fallback_for_blank_message completes without fixture hang;
  • favorite API assertions remain unchanged;
  • favorite unit test does not open a global PostgreSQL interest-vector task/session;
  • bounded diagnostic suite progresses beyond this point;
  • final PR contains durable test code only, no process artifacts.

Verifier focus

Verify this is proper unit-test isolation rather than disabling production functionality. Production background-task lifecycle remains a separate issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions