fix: bound repository collection identity projection
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a06ed7-828d-7ca0-a8d4-0c3e5a0c4102
This commit is contained in:
parent
4cef58cd98
commit
3578dc2f32
10 changed files with 3331 additions and 9 deletions
168
tests/test_repository_collection_projection.py
Normal file
168
tests/test_repository_collection_projection.py
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
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
|
||||
Loading…
Add table
Add a link
Reference in a new issue