generated from coulomb/repo-seed
repository CRUD
This commit is contained in:
@@ -113,6 +113,8 @@ The v0.1 API covers the main registration, analysis, review, search, and inspect
|
||||
GET /repos
|
||||
POST /repos
|
||||
GET /repos/{id}
|
||||
PATCH /repos/{id}
|
||||
DELETE /repos/{id}
|
||||
POST /repos/{id}/analysis-runs
|
||||
GET /repos/{id}/analysis-runs
|
||||
GET /repos/{id}/analysis-runs/{run_id}
|
||||
|
||||
@@ -62,6 +62,24 @@ class RegistryService:
|
||||
def get_repository(self, repository_id: int) -> Repository:
|
||||
return self.store.get_repository(repository_id)
|
||||
|
||||
def update_repository(
|
||||
self,
|
||||
repository_id: int,
|
||||
*,
|
||||
name: str | None = None,
|
||||
description: str | None = None,
|
||||
branch: str | None = None,
|
||||
) -> Repository:
|
||||
return self.store.update_repository(
|
||||
repository_id,
|
||||
name=name,
|
||||
description=description,
|
||||
branch=branch,
|
||||
)
|
||||
|
||||
def delete_repository(self, repository_id: int) -> None:
|
||||
self.store.delete_repository(repository_id)
|
||||
|
||||
def analyze_repository(
|
||||
self,
|
||||
repository_id: int,
|
||||
|
||||
@@ -82,6 +82,52 @@ class RegistryStore:
|
||||
repository_id = int(cursor.lastrowid)
|
||||
return self.get_repository(repository_id)
|
||||
|
||||
def update_repository(
|
||||
self,
|
||||
repository_id: int,
|
||||
*,
|
||||
name: str | None = None,
|
||||
description: str | None = None,
|
||||
branch: str | None = None,
|
||||
) -> Repository:
|
||||
self.get_repository(repository_id)
|
||||
assignments: list[str] = []
|
||||
values: list[str | int | None] = []
|
||||
if name is not None:
|
||||
assignments.append("name = ?")
|
||||
values.append(name)
|
||||
if description is not None:
|
||||
assignments.append("description = ?")
|
||||
values.append(description)
|
||||
if branch is not None:
|
||||
assignments.append("branch = ?")
|
||||
values.append(branch)
|
||||
if not assignments:
|
||||
return self.get_repository(repository_id)
|
||||
|
||||
values.append(repository_id)
|
||||
with self.connect() as connection:
|
||||
cursor = connection.execute(
|
||||
f"""
|
||||
UPDATE repositories
|
||||
SET {", ".join(assignments)}, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ?
|
||||
""",
|
||||
values,
|
||||
)
|
||||
if cursor.rowcount == 0:
|
||||
raise NotFoundError(f"repository {repository_id} was not found")
|
||||
return self.get_repository(repository_id)
|
||||
|
||||
def delete_repository(self, repository_id: int) -> None:
|
||||
with self.connect() as connection:
|
||||
cursor = connection.execute(
|
||||
"DELETE FROM repositories WHERE id = ?",
|
||||
(repository_id,),
|
||||
)
|
||||
if cursor.rowcount == 0:
|
||||
raise NotFoundError(f"repository {repository_id} was not found")
|
||||
|
||||
def update_repository_status(self, repository_id: int, status: str) -> None:
|
||||
with self.connect() as connection:
|
||||
cursor = connection.execute(
|
||||
|
||||
@@ -48,6 +48,24 @@ class RepositoryCreate(BaseModel):
|
||||
}
|
||||
|
||||
|
||||
class RepositoryUpdate(BaseModel):
|
||||
name: str | None = None
|
||||
description: str | None = None
|
||||
branch: str | None = None
|
||||
|
||||
model_config = {
|
||||
"json_schema_extra": {
|
||||
"examples": [
|
||||
{
|
||||
"name": "Renamed Repository",
|
||||
"description": "Updated curator-facing summary.",
|
||||
"branch": "main",
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class AbilityCreate(BaseModel):
|
||||
name: str
|
||||
description: str = ""
|
||||
@@ -305,6 +323,34 @@ def get_repository(
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@app.patch("/repos/{repository_id}")
|
||||
def update_repository(
|
||||
repository_id: int,
|
||||
payload: RepositoryUpdate,
|
||||
service: RegistryService = Depends(get_service),
|
||||
) -> dict[str, object]:
|
||||
try:
|
||||
return asdict(
|
||||
service.update_repository(
|
||||
repository_id,
|
||||
**payload.model_dump(exclude_unset=True),
|
||||
)
|
||||
)
|
||||
except NotFoundError as exc:
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@app.delete("/repos/{repository_id}", status_code=204)
|
||||
def delete_repository(
|
||||
repository_id: int,
|
||||
service: RegistryService = Depends(get_service),
|
||||
) -> None:
|
||||
try:
|
||||
service.delete_repository(repository_id)
|
||||
except NotFoundError as exc:
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@app.post("/repos/{repository_id}/analysis-runs", status_code=201)
|
||||
def create_analysis_run(
|
||||
repository_id: int,
|
||||
|
||||
@@ -61,6 +61,37 @@ def test_manual_registry_builds_ability_map(tmp_path):
|
||||
assert capability.evidence[0].strength == "strong"
|
||||
|
||||
|
||||
def test_repository_update_and_delete(tmp_path):
|
||||
service = make_service(tmp_path)
|
||||
repository = service.register_repository(
|
||||
name="Original",
|
||||
url="https://example.com/original.git",
|
||||
description="Original description.",
|
||||
)
|
||||
ability_id = service.add_ability(repository.id, name="Original Ability")
|
||||
|
||||
updated = service.update_repository(
|
||||
repository.id,
|
||||
name="Updated",
|
||||
description="Updated description.",
|
||||
branch="develop",
|
||||
)
|
||||
|
||||
assert updated.name == "Updated"
|
||||
assert updated.description == "Updated description."
|
||||
assert updated.branch == "develop"
|
||||
assert service.ability_map(repository.id).abilities[0].id == ability_id
|
||||
|
||||
service.delete_repository(repository.id)
|
||||
|
||||
try:
|
||||
service.get_repository(repository.id)
|
||||
except NotFoundError as exc:
|
||||
assert "repository" in str(exc)
|
||||
else:
|
||||
raise AssertionError("expected a NotFoundError")
|
||||
|
||||
|
||||
def test_search_matches_approved_abilities_and_capabilities(tmp_path):
|
||||
service = make_service(tmp_path)
|
||||
repository = service.register_repository(
|
||||
|
||||
@@ -24,6 +24,16 @@ def test_api_manual_registry_loop(tmp_path):
|
||||
assert repository_response.status_code == 201
|
||||
repository_id = repository_response.json()["id"]
|
||||
|
||||
update_response = client.patch(
|
||||
f"/repos/{repository_id}",
|
||||
json={
|
||||
"name": "MailRouter Updated",
|
||||
"description": "Updated mail routing summary.",
|
||||
},
|
||||
)
|
||||
assert update_response.status_code == 200
|
||||
assert update_response.json()["name"] == "MailRouter Updated"
|
||||
|
||||
ability_response = client.post(
|
||||
f"/repos/{repository_id}/abilities",
|
||||
json={
|
||||
@@ -60,7 +70,7 @@ def test_api_manual_registry_loop(tmp_path):
|
||||
map_response = client.get(f"/repos/{repository_id}/ability-map")
|
||||
assert map_response.status_code == 200
|
||||
ability_map = map_response.json()
|
||||
assert ability_map["repository"]["name"] == "MailRouter"
|
||||
assert ability_map["repository"]["name"] == "MailRouter Updated"
|
||||
assert ability_map["abilities"][0]["capabilities"][0]["name"] == (
|
||||
"Classify Incoming Email"
|
||||
)
|
||||
@@ -68,6 +78,11 @@ def test_api_manual_registry_loop(tmp_path):
|
||||
search_response = client.get("/search", params={"q": "email"})
|
||||
assert search_response.status_code == 200
|
||||
assert search_response.json()
|
||||
|
||||
delete_response = client.delete(f"/repos/{repository_id}")
|
||||
assert delete_response.status_code == 204
|
||||
missing_response = client.get(f"/repos/{repository_id}")
|
||||
assert missing_response.status_code == 404
|
||||
finally:
|
||||
app.dependency_overrides.clear()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user