canned-prompts/service/tests/conftest.py

64 lines
2 KiB
Python
Raw Normal View History

CANP-WP-0006 T03: read API Search, versions, manifests, archives and the index, over HTTP. The route shape is the decision worth recording. Package ids contain `/`, so the obvious /packages/{id}/{version} is ambiguous under a greedy path parameter. Rather than invent an HTTP-specific identifier, the routes speak the format's own <registry>:<id>@<version> syntax and parse it — `:` and `@` are both legal in a path segment, and each route keeps a distinct prefix so greediness cannot swallow a neighbouring one. The API therefore exercises section 3.2's reference notation instead of working around it. A bare id present in more than one registry returns 409 with the candidates, never a guess. 409 rather than 300 because the request is answerable once the caller says which registry they meant. Omitting a version applies section 17.1's selector rules, so a prerelease is never chosen implicitly. Validation is delegated to reference/, installed into the service environment rather than reimplemented. One validator means the service and the CLI cannot disagree about what a valid package is; a service accepting something the CLI rejects would be the divergence this project exists to prevent. Importing it is not changing it — reference/ stays the dependency-light conformance witness. Storage keeps the format's distinctions: an immutable package version, its files as content rather than parsed rows, and an index entry recording arrival. Only reserved paths and manifest-referenced files are stored (section 2), and a re-publish of identical content is accepted while different content under the same id@version is a conflict (section 17). Handles a real test-vs-production difference: SQLite autoincrements INTEGER PRIMARY KEY only, never BIGINT, so the SQLite-backed tests could not insert a row. BigInteger().with_variant(Integer, "sqlite") keeps BIGINT on PostgreSQL while letting the tests exercise the same models and migration. Verified live against a seeded store holding this repo's examples and four helix-forge prompt packages. Service tests 11 -> 22. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bjefh8NUiEiahN4JLwoSKM Assistant: claude-code Assistant-Model: opus Assistant-Process: 388925@bnt-lap001 Assistant-Session: 3507023f-e0fd-4a1e-9d90-a0d4217d1502
2026-09-06 20:23:25 +02:00
from __future__ import annotations
import shutil
from pathlib import Path
import pytest
from alembic import command
from alembic.config import Config
from fastapi.testclient import TestClient
from sqlalchemy.orm import Session
from canned_prompts_service.api import create_app
from canned_prompts_service.db import make_engine, make_session_factory
from canned_prompts_service.settings import Settings
from canned_prompts_service import store
ROOT = Path(__file__).resolve().parents[1]
REPO = ROOT.parent
@pytest.fixture()
def db_url(tmp_path: Path) -> str:
url = f"sqlite:///{tmp_path / 'svc.db'}"
config = Config(str(ROOT / "alembic.ini"))
config.set_main_option("script_location", str(ROOT / "migrations"))
config.set_main_option("sqlalchemy.url", url)
command.upgrade(config, "head")
return url
@pytest.fixture()
def session(db_url: str) -> Session:
with make_session_factory(make_engine(db_url))() as s:
yield s
@pytest.fixture()
def client(db_url: str) -> TestClient:
return TestClient(create_app(Settings(database_url=db_url), make_engine(db_url)))
@pytest.fixture()
def seeded(db_url: str) -> TestClient:
"""A store holding the repo's own example packages."""
engine = make_engine(db_url)
with make_session_factory(engine)() as s:
for name in ("house-style", "pqrst-estimate"):
store.ingest(s, "default", "local", REPO / "examples" / name,
source=f"examples/{name}", method="publish")
s.commit()
return TestClient(create_app(Settings(database_url=db_url), engine))
@pytest.fixture()
def two_registries(db_url: str, tmp_path: Path) -> TestClient:
"""The same id in two registries — § 3.2 ambiguity."""
engine = make_engine(db_url)
with make_session_factory(engine)() as s:
for registry in ("local", "house"):
store.ingest(s, "default", registry, REPO / "examples" / "house-style",
source=registry, method="publish")
s.commit()
return TestClient(create_app(Settings(database_url=db_url), engine))