state-hub/tests/test_repository_collection_projection.py
tegwick 3578dc2f32 fix: bound repository collection identity projection
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a06ed7-828d-7ca0-a8d4-0c3e5a0c4102
2026-09-05 02:03:52 +02:00

168 lines
7.1 KiB
Python

from __future__ import annotations
import time
import uuid
import pytest
from sqlalchemy import delete, event, update
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
from api.models.managed_repo import ManagedRepo
from api.models.repository_rename import RepositorySlug
from tests.conftest import create_test_domain, create_test_repo
@pytest.mark.asyncio
@pytest.mark.parametrize("repository_count", [4, 131, 501])
@pytest.mark.parametrize("filter_domain", [False, True])
async def test_repository_collection_projects_identities_with_one_registry_query(
client, test_engine, monkeypatch, repository_count, filter_domain
):
domain = await create_test_domain(client)
repositories = [
ManagedRepo(
id=uuid.uuid4(),
domain_id=uuid.UUID(domain["id"]),
slug=f"bounded-repo-{index}",
name=f"Bounded repo {index:04d}",
)
for index in range(repository_count)
]
factory = async_sessionmaker(test_engine, class_=AsyncSession, expire_on_commit=False)
async with factory() as session:
session.add_all(repositories)
await session.flush()
session.add_all([
RepositorySlug(repo_id=repo.id, slug=repo.slug, kind="canonical")
for repo in repositories[:-1]
])
session.add(
RepositorySlug(
repo_id=repositories[0].id,
slug="bounded-repo-old-name",
kind="alias",
protected=True,
)
)
await session.commit()
async def reject_per_repository_resolution(*_args, **_kwargs):
raise AssertionError("collection must not resolve repository identities one at a time")
monkeypatch.setattr(
"api.routers.repos.repository_resolution_for_id",
reject_per_repository_resolution,
)
statements: list[str] = []
def capture_statement(_conn, _cursor, statement, _parameters, _context, _executemany):
statements.append(statement)
event.listen(test_engine.sync_engine, "before_cursor_execute", capture_statement)
try:
started = time.perf_counter()
response = await client.get("/repos/", params={"domain": domain["slug"]} if filter_domain else {})
elapsed = time.perf_counter() - started
finally:
event.remove(test_engine.sync_engine, "before_cursor_execute", capture_statement)
assert response.status_code == 200, response.text
assert len(response.json()) == repository_count
assert all(repo["domain_slug"] == domain["slug"] for repo in response.json())
by_slug = {repository["slug"]: repository for repository in response.json()}
assert by_slug["bounded-repo-0"]["aliases"] == ["bounded-repo-old-name"]
assert by_slug["bounded-repo-0"]["slug_status"] == "canonical"
assert by_slug[repositories[-1].slug]["aliases"] == []
assert by_slug[repositories[-1].slug]["canonical_slug"] == repositories[-1].slug
registry_selects = [
statement
for statement in statements
if statement.lstrip().upper().startswith("SELECT")
and "repository_slugs" in statement.lower()
]
assert len(registry_selects) == 1
selects = [statement for statement in statements if statement.lstrip().upper().startswith("SELECT")]
assert len(selects) == (3 if filter_domain else 2), selects
print(f"\ncollection: repositories={repository_count}, filtered={filter_domain}, selects={len(selects)}, elapsed_seconds={elapsed:.4f}")
@pytest.mark.asyncio
async def test_empty_repository_collection_skips_registry_query(client, test_engine):
statements = []
def capture(_conn, _cursor, statement, *_args):
statements.append(statement)
event.listen(test_engine.sync_engine, "before_cursor_execute", capture)
try:
response = await client.get("/repos/")
finally:
event.remove(test_engine.sync_engine, "before_cursor_execute", capture)
assert response.status_code == 200
assert response.json() == []
assert not any("repository_slugs" in statement for statement in statements)
@pytest.mark.asyncio
@pytest.mark.parametrize("registry_state", ["missing", "alias_only", "canonical_mismatch"])
async def test_collection_matches_detail_identity_compatibility(
client, test_engine, registry_state
):
domain = await create_test_domain(client)
repo = await create_test_repo(client, domain_slug=domain["slug"])
factory = async_sessionmaker(test_engine, class_=AsyncSession, expire_on_commit=False)
async with factory() as session:
if registry_state == "missing":
await session.execute(delete(RepositorySlug).where(RepositorySlug.repo_id == repo["id"]))
else:
await session.execute(
update(RepositorySlug).where(RepositorySlug.repo_id == repo["id"]).values(kind="alias")
)
if registry_state == "canonical_mismatch":
session.add(RepositorySlug(repo_id=repo["id"], slug="different-canonical", kind="canonical"))
await session.commit()
detail = await client.get(f"/repos/{repo['slug']}")
collection = await client.get("/repos/")
if registry_state == "missing":
assert detail.status_code == collection.status_code == 200
for field in ("canonical_slug", "requested_slug", "aliases", "slug_status"):
assert collection.json()[0][field] == detail.json()[field]
else:
assert detail.status_code == collection.status_code == 409
assert collection.json() == detail.json()
@pytest.mark.asyncio
async def test_collection_filters_secondary_domain_and_classification(client, test_engine):
primary = await create_test_domain(client)
secondary = await create_test_domain(client, slug="secondary", name="Secondary")
factory = async_sessionmaker(test_engine, class_=AsyncSession, expire_on_commit=False)
async with factory() as session:
session.add_all([
ManagedRepo(
domain_id=uuid.UUID(primary["id"]), slug="included", name="Included",
secondary_domains=[secondary["slug"]], category="service",
capability_tags=["projection"], business_stake=["internal"],
),
ManagedRepo(
domain_id=uuid.UUID(primary["id"]), slug="excluded", name="Excluded",
category="service", capability_tags=["projection"], business_stake=["internal"],
),
])
await session.commit()
params = {"domain": secondary["slug"], "category": "service",
"capability_tag": "projection", "business_stake": "internal"}
response = await client.get("/repos/", params=params)
assert response.status_code == 200, response.text
assert [repo["slug"] for repo in response.json()] == ["included"]
assert response.json()[0]["domain_slug"] == primary["slug"]
for key in ("category", "capability_tag", "business_stake"):
response = await client.get("/repos/", params={**params, key: "absent"})
assert response.status_code == 200
assert response.json() == []
response = await client.get("/repos/", params={"domain": "missing"})
assert response.status_code == 404