Files
learningcircuit--local-deep…/tests/chat/test_chat_archive_blocked_in_progress.py
T
wehub-resource-sync 7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

155 lines
5.0 KiB
Python

"""Integration regression test for the archive-while-running guard.
`ChatService.archive_session` must refuse to archive a chat session
that has a `ResearchHistory` row in `status='in_progress'` for it.
Without this guard, archiving leaves the running research alive but
tied to a session the user thinks is read-only — an orphaned research
that keeps burning LLM cycles and would race with the archived-session
read-only invariant when it tries to write its final result back.
The chosen behaviour mirrors the existing send-to-archived 409 rule
(see ``insert_message_in_db`` and the send-to-archived guard in
``chat/routes.py``): rather than
auto-terminating the research (which delete_session does — but delete
is destructive by design), archive returns 409 and asks the caller to
stop the research first. This preserves "archive = read-only" without
silent side effects on running computation.
"""
import uuid
from unittest.mock import patch
import pytest
from src.local_deep_research.chat.service import (
ArchiveBlockedError,
ChatService,
)
from src.local_deep_research.constants import ResearchStatus
from src.local_deep_research.database.models import (
ChatSession,
ChatSessionStatus,
ResearchHistory,
)
def _seed_session_and_research(db, status: str):
"""Insert an active chat session + a research row in *status*."""
session_id = str(uuid.uuid4())
research_id = str(uuid.uuid4())
db.add(
ChatSession(
id=session_id,
title="archive-guard test",
status=ChatSessionStatus.ACTIVE.value,
message_count=0,
)
)
db.add(
ResearchHistory(
id=research_id,
query="probe",
mode="quick",
status=status,
created_at="2026-05-14T00:00:00",
chat_session_id=session_id,
)
)
db.commit()
return session_id, research_id
def _run_archive(SessionLocal, service, session_id):
"""Drive ``archive_session`` against the test SessionLocal."""
with patch(
"src.local_deep_research.chat.service.get_user_db_session"
) as ctx:
ctx.return_value.__enter__.return_value = SessionLocal()
ctx.return_value.__exit__.return_value = False
return service.archive_session(session_id)
def test_archive_blocked_when_research_in_progress(
setup_database_for_all_tests,
):
"""An active session with an in_progress research must NOT archive."""
SessionLocal = setup_database_for_all_tests
username = f"alice_{uuid.uuid4().hex[:8]}"
service = ChatService(username=username)
with SessionLocal() as db:
session_id, _ = _seed_session_and_research(
db, status=ResearchStatus.IN_PROGRESS.value
)
with pytest.raises(ArchiveBlockedError, match="in_progress"):
_run_archive(SessionLocal, service, session_id)
# And the session must still be ACTIVE — the failed archive must
# not have partially-committed a status change.
with SessionLocal() as db:
row = db.query(ChatSession).filter_by(id=session_id).first()
assert row is not None
assert row.status == ChatSessionStatus.ACTIVE.value
def test_archive_succeeds_when_research_completed(
setup_database_for_all_tests,
):
"""Completed research must not block archive — the guard is
in_progress-only."""
SessionLocal = setup_database_for_all_tests
username = f"alice_{uuid.uuid4().hex[:8]}"
service = ChatService(username=username)
with SessionLocal() as db:
session_id, _ = _seed_session_and_research(
db, status=ResearchStatus.COMPLETED.value
)
assert _run_archive(SessionLocal, service, session_id) is True
with SessionLocal() as db:
row = db.query(ChatSession).filter_by(id=session_id).first()
assert row.status == ChatSessionStatus.ARCHIVED.value
def test_archive_succeeds_when_no_research_attached(
setup_database_for_all_tests,
):
"""A session with zero research rows archives cleanly (sanity)."""
SessionLocal = setup_database_for_all_tests
username = f"alice_{uuid.uuid4().hex[:8]}"
service = ChatService(username=username)
sid = str(uuid.uuid4())
with SessionLocal() as db:
db.add(
ChatSession(
id=sid,
title="clean archive",
status=ChatSessionStatus.ACTIVE.value,
message_count=0,
)
)
db.commit()
assert _run_archive(SessionLocal, service, sid) is True
with SessionLocal() as db:
assert (
db.query(ChatSession).filter_by(id=sid).first().status
== ChatSessionStatus.ARCHIVED.value
)
def test_archive_missing_session_returns_false(
setup_database_for_all_tests,
):
"""Unknown id → False return, no exception."""
SessionLocal = setup_database_for_all_tests
username = f"alice_{uuid.uuid4().hex[:8]}"
service = ChatService(username=username)
assert _run_archive(SessionLocal, service, str(uuid.uuid4())) is False