Files
simbarag/tests/unit/test_integration_status.py
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

112 lines
3.6 KiB
Python

from datetime import datetime, timedelta, timezone
from types import SimpleNamespace
from unittest.mock import AsyncMock
import pytest
from quart import Quart
from quart_jwt_extended import JWTManager, create_access_token
from tortoise import Tortoise
from blueprints import integrations
from blueprints.users.models import User
from blueprints.ynab import tools
from blueprints.ynab.sync import tool_data_freshness
async def test_stale_ynab_status_hides_provider_error(monkeypatch):
monkeypatch.setattr(integrations, "ynab_configured", lambda: True)
monkeypatch.setattr(
integrations,
"_current_state",
AsyncMock(
return_value=SimpleNamespace(
last_synced_at=datetime.now(timezone.utc) - timedelta(hours=1),
last_error="Authorization: secret-provider-key",
)
),
)
result = await integrations._ynab_status()
assert result["status"] == "stale"
assert result["last_success_at"]
assert "secret-provider-key" not in str(result)
async def test_fresh_ynab_status_is_ready(monkeypatch):
monkeypatch.setattr(integrations, "ynab_configured", lambda: True)
monkeypatch.setattr(
integrations,
"_current_state",
AsyncMock(
return_value=SimpleNamespace(
last_synced_at=datetime.now(timezone.utc), last_error=None
)
),
)
assert (await integrations._ynab_status())["status"] == "ready"
async def test_stale_tool_data_warns_model_and_carries_artifact_provenance():
@tools.ynab_tool_errors
async def result():
tool_data_freshness.set(
{"system": "ynab", "as_of": "2026-10-01T00:00:00+00:00", "stale": True}
)
return "Total: $100", {"kind": "ynab.summary", "data": {"total": 100}}
text, artifact = await result()
assert "cached budget data is stale" in text
assert "2026-10-01" in text
assert artifact["source_ref"]["stale"] is True
assert artifact["data"] == {"total": 100}
async def test_tool_freshness_does_not_leak_from_previous_call():
tool_data_freshness.set({"system": "ynab", "as_of": "old", "stale": True})
@tools.ynab_tool_errors
async def result():
return "Dates must be YYYY-MM-DD."
assert await result() == "Dates must be YYYY-MM-DD."
@pytest.fixture
async def status_app(monkeypatch):
await Tortoise.init(
db_url="sqlite://:memory:", modules={"models": ["blueprints.users.models"]}
)
await Tortoise.generate_schemas()
user = await User.create(username="owner", email="owner@example.com")
app = Quart(__name__)
app.config["JWT_SECRET_KEY"] = "test-secret"
JWTManager(app)
app.register_blueprint(integrations.integrations_blueprint)
monkeypatch.setattr(
integrations,
"integration_statuses",
AsyncMock(
return_value=[
{
"name": "YNAB",
"status": "stale",
"message": "Cached data",
"last_success_at": None,
}
]
),
)
async with app.app_context():
token = create_access_token(identity=str(user.id))
yield app, token
await Tortoise.close_connections()
async def test_status_requires_access_token(status_app):
app, token = status_app
assert (await app.test_client().get("/api/integrations/")).status_code == 401
response = await app.test_client().get(
"/api/integrations/", headers={"Authorization": f"Bearer {token}"}
)
assert response.status_code == 200
assert (await response.get_json())["integrations"][0]["status"] == "stale"