Files
simbarag/tests/unit/test_retrieval.py
T
ryan 5809ad3adb
CI / backend (pull_request) Successful in 42s
CI / frontend (pull_request) Failing after 7s
CI / backend (push) Successful in 41s
CI / frontend (push) Failing after 7s
Add durable chat runs, shared agent runtime, reliable jobs, history tools and citations
Shared runtime
- One agent entry point (runtime.invoke_agent_for_user / prepare_agent_turn) for
  web, email, iMessage, WhatsApp, scheduled and cron, so identity, permissions,
  memories, summaries and metrics match across channels.
- Serialize turns per checkpoint thread; /query validates input and returns 409
  while a durable run owns the conversation.

Reconnectable chat (durable runs)
- POST /runs creates an idempotent, owner-scoped run; GET .../events replays an
  ordered SSE log by cursor, so generation survives refresh and device switches.
  Stop cancels; disconnect only detaches. /stream-query is a compatibility adapter.
- Coalesce streamed deltas (150 ms) and wake same-process subscribers instead of
  polling; keep buffered text when a run is stopped.
- Drain runs on shutdown; serve via Hypercorn with a shutdown timeout covering
  the drain (and no longer in debug mode). CHAT_RUN_* settings are forwarded.

Scheduled messages and cron
- Atomic delivery claims, attempt history and explicit retries; ambiguous sends
  become "uncertain" and are never resent automatically.
- Cron triggers need a run_id/Idempotency-Key; legacy jobs need an explicit owner.
- Cancelling never rewrites a started delivery; series stop via tombstones.
  Legacy sent rows are not forked into new series. Malformed bodies return 400.

History, integrations and retrieval
- Message-content search, pinned conversations, JSON/Markdown export and a
  memory editor.
- Integration status panel and YNAB data-freshness provenance.
- Date-filtered document/note search with stable citations and a source viewer.

Migration 20 is generated by aerich.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 23:11:04 -04:00

165 lines
5.5 KiB
Python

from datetime import datetime, timezone
from types import SimpleNamespace
from unittest.mock import AsyncMock
from uuid import uuid4
import pytest
from langchain_core.documents import Document
from langchain_postgres import PGVector
from quart import Quart
from quart_jwt_extended import JWTManager, create_access_token
from sqlalchemy import Column
from sqlalchemy.dialects.postgresql import JSONB
from blueprints.rag.retrieval import build_filter, citation_url, document_date
from blueprints.rag import logic, source_api
from blueprints.conversation.artifacts_util import search_documents_artifact
@pytest.mark.parametrize("source", [None, "paperless", "obsidian"])
@pytest.mark.parametrize(
"start,end",
[("2026-09-01", "2026-09-30"), ("2026-09-01", None), (None, "2026-09-30")],
)
def test_filters_compile_with_actual_pgvector_operator_rules(source, start, end):
store = PGVector.__new__(PGVector)
store.EmbeddingStore = SimpleNamespace(cmetadata=Column("cmetadata", JSONB))
expression = store._create_filter_clause(build_filter(source, start, end))
assert expression is not None
@pytest.mark.parametrize(
"start,end",
[
("20260901", None),
("bad", None),
("2026-10-01", "2026-09-01"),
(None, "9999-12-31"),
],
)
def test_date_filters_validate_before_vector_lookup(start, end):
with pytest.raises(ValueError):
build_filter("obsidian", start, end)
async def test_filtered_query_preserves_citations_and_excludes_unknown_dates(
monkeypatch,
):
identifier = str(uuid4())
docs = [
Document(
id=identifier,
page_content="September groceries plan",
metadata={
"source": "obsidian",
"date": "2026-09-15",
"filepath": "notes/budget.md",
},
),
Document(
id=str(uuid4()),
page_content="Undated plan",
metadata={"source": "obsidian"},
),
]
store = SimpleNamespace(amax_marginal_relevance_search=AsyncMock(return_value=docs))
monkeypatch.setattr(logic, "_get_vector_store", lambda: store)
text, results = await logic.query_vector_store(
"budget", source="obsidian", start_date="2026-09-01", end_date="2026-09-30"
)
assert results == docs[:1]
assert f"Citation: /sources/{identifier}" in text
assert store.amax_marginal_relevance_search.call_args.kwargs[
"filter"
] == build_filter("obsidian", "2026-09-01", "2026-09-30")
def test_artifact_citations_identify_exact_retrieved_chunk(monkeypatch):
monkeypatch.setenv("OBSIDIAN_VAULT_PATH", "/vault")
identifier = str(uuid4())
doc = Document(
id=identifier,
page_content="Savings target",
metadata={
"filepath": "/vault/notes/budget.md",
"h1": "Budget",
"h2": "Savings",
},
)
artifact = search_documents_artifact("target", "obsidian", [doc])
result = artifact["data"]["results"][0]
assert result["url"] == f"/sources/{identifier}"
assert result["source_id"] == identifier
assert result["path"] == "notes/budget.md"
assert result["headings"] == ["Budget", "Savings"]
def test_document_date_and_unknown_citations():
assert (
document_date(
{"created_date": datetime(2026, 9, 1, tzinfo=timezone.utc).timestamp()}
)
== "2026-09-01"
)
assert document_date({"date": "bad"}) is None
assert citation_url(Document(page_content="no stable ID")) is None
async def test_source_read_requires_auth_and_handles_missing_chunk(monkeypatch):
app = Quart(__name__)
app.config["JWT_SECRET_KEY"] = "test-secret"
JWTManager(app)
app.register_blueprint(source_api.sources_blueprint)
calls = []
def load(identifier):
calls.append(identifier)
return {
"id": identifier,
"source": "paperless",
"title": "Vet report",
"content": "Retrieved section",
"headings": [],
}
monkeypatch.setattr(source_api, "_load_chunk", load)
identifier = str(uuid4())
assert (
await app.test_client().get(f"/api/sources/{identifier}")
).status_code == 401
async with app.app_context():
token = create_access_token(identity=str(uuid4()))
headers = {"Authorization": f"Bearer {token}"}
response = await app.test_client().get(
f"/api/sources/{identifier}", headers=headers
)
assert (await response.get_json())["content"] == "Retrieved section"
assert response.headers["Cache-Control"] == "no-store"
assert (
await app.test_client().get("/api/sources/not-an-id", headers=headers)
).status_code == 404
assert calls == [identifier]
async def test_post_filter_keeps_note_matched_by_any_date_field(monkeypatch):
# The database filter matches if any date field is in range; the post-filter
# must agree or matching notes silently vanish from results.
doc = Document(
id=str(uuid4()),
page_content="Lease renewal notes",
metadata={
"source": "obsidian",
"date": "2023-05-01",
"created_at": "2024-06-12T09:00:00",
"filepath": "notes/lease.md",
},
)
store = SimpleNamespace(
amax_marginal_relevance_search=AsyncMock(return_value=[doc])
)
monkeypatch.setattr(logic, "_get_vector_store", lambda: store)
_, results = await logic.query_vector_store(
"lease", source="obsidian", start_date="2024-06-01", end_date="2024-06-30"
)
assert results == [doc]