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
136 lines
4.4 KiB
Python
136 lines
4.4 KiB
Python
"""Integration regression test: archive-status guard on message insert.
|
|
|
|
Previously, ``ChatService.insert_message_in_db`` ran an
|
|
``UPDATE chat_sessions SET message_count = message_count + 1
|
|
WHERE id = :session_id``
|
|
without checking the session's status. The route-layer
|
|
``status='active'`` guard was informational only — between that check
|
|
and the UPDATE, a concurrent PATCH could flip the session to
|
|
``archived``, and the send would still land. Result: archived sessions
|
|
could silently receive new messages.
|
|
|
|
The fix narrows the UPDATE's WHERE clause to
|
|
``id = :session_id AND status = 'active'``. If the session is archived
|
|
(or deleted), the UPDATE matches zero rows, ``scalar_one_or_none``
|
|
returns None, and the service raises ValueError("not found or not
|
|
active") — caught by routes.py and converted to a 4xx.
|
|
|
|
This test asserts on the atomic UPDATE directly so the regression
|
|
surface is covered even if a future refactor moves the route-layer
|
|
guard.
|
|
"""
|
|
|
|
import uuid
|
|
|
|
import pytest
|
|
|
|
from src.local_deep_research.chat.service import ChatService
|
|
from src.local_deep_research.database.models import (
|
|
ChatMessage,
|
|
ChatSession,
|
|
ChatSessionStatus,
|
|
)
|
|
|
|
|
|
def _seed_session(db, status: str):
|
|
"""Insert a chat_sessions row in *status* and return its id."""
|
|
sid = str(uuid.uuid4())
|
|
db.add(
|
|
ChatSession(
|
|
id=sid,
|
|
title="archive-guard test",
|
|
status=status,
|
|
message_count=0,
|
|
)
|
|
)
|
|
db.commit()
|
|
return sid
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"status",
|
|
[
|
|
ChatSessionStatus.ARCHIVED.value,
|
|
ChatSessionStatus.DELETED.value,
|
|
],
|
|
)
|
|
def test_insert_message_rejects_non_active_session(
|
|
setup_database_for_all_tests, status
|
|
):
|
|
"""A direct call to insert_message_in_db against a non-active
|
|
session must raise rather than silently increment message_count."""
|
|
SessionLocal = setup_database_for_all_tests
|
|
service = ChatService(username=f"alice_{uuid.uuid4().hex[:8]}")
|
|
|
|
with SessionLocal() as db:
|
|
sid = _seed_session(db, status=status)
|
|
|
|
with SessionLocal() as db:
|
|
with pytest.raises(ValueError, match="not found or not active"):
|
|
service.insert_message_in_db(
|
|
db=db,
|
|
session_id=sid,
|
|
role="user",
|
|
content="should not land",
|
|
message_type="query",
|
|
)
|
|
|
|
# And the counter MUST not have moved.
|
|
row = db.query(ChatSession).filter_by(id=sid).first()
|
|
assert row is not None
|
|
assert row.message_count == 0, (
|
|
f"message_count incremented despite {status} session — "
|
|
f"the atomic guard did not hold"
|
|
)
|
|
# And no message row was persisted.
|
|
msgs = db.query(ChatMessage).filter_by(session_id=sid).all()
|
|
assert msgs == [], f"Unexpected message rows: {msgs}"
|
|
|
|
|
|
def test_insert_message_succeeds_on_active_session(
|
|
setup_database_for_all_tests,
|
|
):
|
|
"""Sanity counter-test: an active session accepts the message and
|
|
message_count bumps by 1."""
|
|
SessionLocal = setup_database_for_all_tests
|
|
service = ChatService(username=f"alice_{uuid.uuid4().hex[:8]}")
|
|
|
|
with SessionLocal() as db:
|
|
sid = _seed_session(db, status=ChatSessionStatus.ACTIVE.value)
|
|
|
|
with SessionLocal() as db:
|
|
mid = service.insert_message_in_db(
|
|
db=db,
|
|
session_id=sid,
|
|
role="user",
|
|
content="hello",
|
|
message_type="query",
|
|
)
|
|
db.commit()
|
|
|
|
row = db.query(ChatSession).filter_by(id=sid).first()
|
|
assert row.message_count == 1
|
|
msg = db.query(ChatMessage).filter_by(id=mid).first()
|
|
assert msg is not None
|
|
assert msg.content == "hello"
|
|
assert msg.session_id == sid
|
|
|
|
|
|
def test_insert_message_rejects_unknown_session(
|
|
setup_database_for_all_tests,
|
|
):
|
|
"""An unknown id is rejected the same way an archived one is —
|
|
the WHERE clause matches zero rows either way."""
|
|
SessionLocal = setup_database_for_all_tests
|
|
service = ChatService(username=f"alice_{uuid.uuid4().hex[:8]}")
|
|
|
|
with SessionLocal() as db:
|
|
with pytest.raises(ValueError, match="not found or not active"):
|
|
service.insert_message_in_db(
|
|
db=db,
|
|
session_id=str(uuid.uuid4()),
|
|
role="user",
|
|
content="should not land",
|
|
message_type="query",
|
|
)
|