Milestone 2’s core deterministic scanner path

This commit is contained in:
2026-04-25 22:32:05 +02:00
parent 3b2d1667bb
commit 3d9032a386
11 changed files with 853 additions and 2 deletions

View File

@@ -96,3 +96,51 @@ def test_capability_must_belong_to_repository(tmp_path):
assert "ability" in str(exc)
else:
raise AssertionError("expected a NotFoundError")
def test_analyze_repository_records_snapshot_and_observed_facts(tmp_path):
source = tmp_path / "repo"
source.mkdir()
(source / "README.md").write_text("# Example\n", encoding="utf-8")
(source / "requirements.txt").write_text("fastapi\n", encoding="utf-8")
(source / "app.py").write_text(
"from fastapi import FastAPI\n"
"app = FastAPI()\n"
'@app.get("/health")\n'
"def health():\n"
" return {}\n",
encoding="utf-8",
)
service = make_service(tmp_path)
repository = service.register_repository(
name="Example",
url=str(source),
description="A local fixture repository",
)
summary = service.analyze_repository(repository.id)
assert summary.analysis_run.status == "completed"
assert summary.snapshot is not None
assert summary.snapshot.file_count == 3
assert service.get_repository(repository.id).status == "analyzed"
fact_names = {(fact.kind, fact.name, fact.path) for fact in summary.facts}
assert ("documentation", "README", "README.md") in fact_names
assert ("framework", "FastAPI", "requirements.txt") in fact_names
assert ("interface", "python route decorator", "app.py") in fact_names
def test_analyze_repository_failure_is_recorded(tmp_path):
service = make_service(tmp_path)
repository = service.register_repository(
name="Missing",
url=str(tmp_path / "does-not-exist"),
)
summary = service.analyze_repository(repository.id)
assert summary.analysis_run.status == "failed"
assert summary.snapshot is None
assert "does not exist" in (summary.analysis_run.error_message or "")
assert service.get_repository(repository.id).status == "analysis_failed"

View File

@@ -0,0 +1,36 @@
from repo_registry.repo_scanning.scanner import DeterministicScanner
def test_deterministic_scanner_extracts_structural_facts(tmp_path):
repo = tmp_path / "sample"
repo.mkdir()
(repo / "README.md").write_text("# MailRouter\n", encoding="utf-8")
(repo / "pyproject.toml").write_text(
'[project]\ndependencies = ["fastapi", "pytest"]\n',
encoding="utf-8",
)
(repo / "src").mkdir()
(repo / "src" / "routes.py").write_text(
"from fastapi import APIRouter\n"
"router = APIRouter()\n"
'@router.post("/classify-email")\n'
"def classify_email():\n"
" return {}\n",
encoding="utf-8",
)
(repo / "tests").mkdir()
(repo / "tests" / "test_routes.py").write_text("def test_ok(): pass\n", encoding="utf-8")
result = DeterministicScanner().scan(repo)
facts = {(fact.kind, fact.name, fact.path) for fact in result.facts}
assert result.file_count == 4
assert ("documentation", "README", "README.md") in facts
assert ("manifest", "pyproject.toml", "pyproject.toml") in facts
assert ("test", "test_routes.py", "tests/test_routes.py") in facts
assert ("framework", "FastAPI", "pyproject.toml") in facts
assert ("framework", "pytest", "pyproject.toml") in facts
assert ("interface", "python route decorator", "src/routes.py") in facts
languages = {fact.name: fact.metadata["file_count"] for fact in result.facts if fact.kind == "language"}
assert languages == {"Python": 2}

View File

@@ -67,3 +67,43 @@ def test_api_manual_registry_loop(tmp_path):
assert search_response.json()
finally:
app.dependency_overrides.clear()
def test_api_analysis_run_loop(tmp_path):
source = tmp_path / "repo"
source.mkdir()
(source / "README.md").write_text("# Searchable\n", encoding="utf-8")
(source / "package.json").write_text(
'{"dependencies":{"react":"latest","vite":"latest"}}',
encoding="utf-8",
)
def override_settings():
return Settings(database_path=str(tmp_path / "api-analysis.sqlite3"))
app.dependency_overrides[get_settings] = override_settings
client = TestClient(app)
try:
repository_response = client.post(
"/repos",
json={"name": "Frontend", "url": str(source)},
)
repository_id = repository_response.json()["id"]
run_response = client.post(f"/repos/{repository_id}/analysis-runs", json={})
assert run_response.status_code == 201
run = run_response.json()
assert run["analysis_run"]["status"] == "completed"
assert run["snapshot"]["file_count"] == 2
facts_response = client.get(f"/repos/{repository_id}/observed-facts")
assert facts_response.status_code == 200
fact_names = {
(fact["kind"], fact["name"], fact["path"])
for fact in facts_response.json()
}
assert ("documentation", "README", "README.md") in fact_names
assert ("framework", "React", "package.json") in fact_names
assert ("framework", "Vite", "package.json") in fact_names
finally:
app.dependency_overrides.clear()