md-first memory extraction framework for AI agents. Markdown is the single source of truth; SQLite holds state and LanceDB provides the rebuildable vector + BM25 + scalar index. The codebase follows a single-direction DDD layering (entrypoints -> service -> memory -> infra, with component / core / config cross-cutting) enforced by import-linter. Engineering surface: - Coding conventions in .claude/rules/ (path-scoped) and workflows in .claude/skills/ (/commit, /new-branch, /pr). - GitHub Actions CI runs make lint + test + integration; pre-commit mirrors the gates locally (ruff, hygiene hooks, gitlint commit-msg). - Commit messages follow Conventional Commits, enforced by gitlint. - make lint also enforces datetime two-zone discipline and OpenAPI drift.
88 lines
3.2 KiB
Python
88 lines
3.2 KiB
Python
"""Belt-and-braces gate: dev-mode ``GET /openapi.json`` ≡ ``docs/openapi.json``.
|
|
|
|
The lint-time ``make check-openapi`` already diffs ``app.openapi()``
|
|
against the committed ``docs/openapi.json``. This e2e test closes the
|
|
remaining theoretical gap: if anyone ever adds a *lifespan-mutated*
|
|
OpenAPI schema (e.g. ``app.openapi_schema = ...`` inside a startup
|
|
handler), the in-memory ``app.openapi()`` and the runtime
|
|
``GET /openapi.json`` response would diverge — the lint gate would
|
|
miss it, but this test wouldn't.
|
|
|
|
How:
|
|
|
|
1. Force ``ENV=DEV`` so the ``openapi_url`` route is enabled.
|
|
2. Construct the app via ``create_app(lifespan_providers=[])`` to skip
|
|
SQLite / LanceDB / OME (the schema is route-driven, not state-
|
|
driven) — but *do* run the lifespan context, so any startup hook
|
|
that mutates ``app.openapi_schema`` is exercised.
|
|
3. ``GET /openapi.json`` through ``httpx.AsyncClient``.
|
|
4. Diff against ``docs/openapi.json`` byte-for-byte (after JSON
|
|
normalisation to defeat ordering nondeterminism).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
|
|
import httpx
|
|
import pytest
|
|
|
|
_REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
_COMMITTED_OPENAPI = _REPO_ROOT / "docs" / "openapi.json"
|
|
|
|
|
|
async def test_dev_mode_openapi_endpoint_matches_committed_docs(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""Runtime ``GET /openapi.json`` (dev mode) must equal ``docs/openapi.json``."""
|
|
# The gate's own committed snapshot must exist — otherwise the dev
|
|
# workflow ``make openapi`` has been skipped.
|
|
assert _COMMITTED_OPENAPI.is_file(), (
|
|
f"{_COMMITTED_OPENAPI} not found — run `make openapi`"
|
|
)
|
|
|
|
# Force dev-mode so ``openapi_url="/openapi.json"`` is registered.
|
|
monkeypatch.setenv("ENV", "DEV")
|
|
|
|
from everos.entrypoints.api.app import create_app
|
|
|
|
app = create_app(lifespan_providers=[])
|
|
transport = httpx.ASGITransport(app=app)
|
|
async with (
|
|
app.router.lifespan_context(app),
|
|
httpx.AsyncClient(transport=transport, base_url="http://test") as client,
|
|
):
|
|
resp = await client.get("/openapi.json")
|
|
assert resp.status_code == 200, resp.text
|
|
runtime_schema = resp.json()
|
|
|
|
committed_schema = json.loads(_COMMITTED_OPENAPI.read_text(encoding="utf-8"))
|
|
|
|
if runtime_schema != committed_schema:
|
|
# Emit a concise diff to help locate the drift cause.
|
|
import difflib
|
|
|
|
runtime_rendered = json.dumps(runtime_schema, indent=2, ensure_ascii=False)
|
|
committed_rendered = json.dumps(committed_schema, indent=2, ensure_ascii=False)
|
|
diff = "\n".join(
|
|
list(
|
|
difflib.unified_diff(
|
|
committed_rendered.splitlines(),
|
|
runtime_rendered.splitlines(),
|
|
fromfile="docs/openapi.json (committed)",
|
|
tofile="GET /openapi.json (runtime)",
|
|
lineterm="",
|
|
)
|
|
)[:120]
|
|
)
|
|
raise AssertionError(
|
|
"runtime /openapi.json drifts from docs/openapi.json; "
|
|
"run `make openapi` and commit the result.\n\n" + diff
|
|
)
|
|
|
|
|
|
# Keep ``os`` legit in case future scenarios need direct env reads.
|
|
_ = os
|