Partial layouts still use MODE=pcb. Missing copper is INSUFFICIENT or
skipped, never Euclidean stand-in tracks. Via≠Pad unchanged. DELETE
/api/projects/{id} is owner-only with a dashboard confirm dialog.
75 lines
2.7 KiB
Python
75 lines
2.7 KiB
Python
"""Owner-only project delete: files+meta gone; other owners and auth users untouched."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from fastapi.testclient import TestClient
|
|
|
|
from backend.services import projects as proj_svc
|
|
from backend.services.storage import LocalStorageBackend
|
|
|
|
|
|
def _client(tmp_path: Path) -> TestClient:
|
|
from backend.main import app
|
|
|
|
app.state.storage = LocalStorageBackend(tmp_path)
|
|
return TestClient(app)
|
|
|
|
|
|
def test_owner_delete_removes_project_files_not_auth(tmp_path: Path):
|
|
client = _client(tmp_path)
|
|
auth_users = tmp_path / "auth" / "users.json"
|
|
auth_users.parent.mkdir(parents=True)
|
|
auth_users.write_text('{"users":[{"email":"keep@example.com"}]}\n')
|
|
|
|
meta = client.post("/api/projects", json={"name": "mine"}).json()
|
|
pid = meta["id"]
|
|
prefix = f"users/local/projects/{pid}"
|
|
storage = client.app.state.storage
|
|
storage.write_json(f"{prefix}/report.json", {"findings": []})
|
|
storage.write_text(f"{prefix}/uploads/netlist.asc", "*PADS-PCB*\n")
|
|
|
|
resp = client.delete(f"/api/projects/{pid}")
|
|
assert resp.status_code == 200, resp.text
|
|
assert resp.json()["ok"] is True
|
|
assert not storage.exists(f"{prefix}/project.json")
|
|
assert not storage.exists(f"{prefix}/report.json")
|
|
assert auth_users.is_file()
|
|
assert "keep@example.com" in auth_users.read_text()
|
|
listed = client.get("/api/projects").json()
|
|
assert all(p["id"] != pid for p in listed)
|
|
|
|
|
|
def test_delete_another_owners_project_is_404(tmp_path: Path):
|
|
client = _client(tmp_path)
|
|
storage = client.app.state.storage
|
|
other = proj_svc.create_project(storage, "alice", "secret-board")
|
|
key = f"users/alice/projects/{other.id}/project.json"
|
|
report = f"users/alice/projects/{other.id}/report.json"
|
|
storage.write_json(report, {"findings": [{"id": 1}]})
|
|
alice_users = tmp_path / "users" / "alice" / "profile.json"
|
|
alice_users.parent.mkdir(parents=True, exist_ok=True)
|
|
alice_users.write_text('{"user_id":"alice"}\n')
|
|
|
|
resp = client.delete(f"/api/projects/{other.id}")
|
|
assert resp.status_code == 404
|
|
assert storage.exists(key)
|
|
assert storage.exists(report)
|
|
assert alice_users.is_file()
|
|
|
|
|
|
def test_collaborator_cannot_delete_owner_project(tmp_path: Path):
|
|
client = _client(tmp_path)
|
|
storage = client.app.state.storage
|
|
other = proj_svc.create_project(storage, "alice", "shared")
|
|
proj_svc.add_collaborator(storage, "alice", other.id, "local")
|
|
key = f"users/alice/projects/{other.id}/project.json"
|
|
|
|
resp = client.delete(f"/api/projects/{other.id}")
|
|
assert resp.status_code == 403, resp.text
|
|
assert storage.exists(key)
|
|
meta = proj_svc.get_project(storage, "alice", other.id)
|
|
assert meta is not None
|
|
assert "local" in meta.collaborators
|