def test_project_crud_and_frames(client, monkeypatch): monkeypatch.setattr("routers.projects.get_presigned_url", lambda key, expires=3600: f"http://storage/{key}") created = client.post("/api/projects", json={ "name": "Demo", "description": "desc", "thumbnail_url": "thumb.jpg", "parse_fps": 12, }) assert created.status_code == 201 project_id = created.json()["id"] frame = client.post(f"/api/projects/{project_id}/frames", json={ "project_id": project_id, "frame_index": 0, "image_url": "frames/0.jpg", "width": 640, "height": 360, }) assert frame.status_code == 201 frame_id = frame.json()["id"] listing = client.get("/api/projects") assert listing.status_code == 200 assert listing.json()[0]["frame_count"] == 1 assert listing.json()[0]["thumbnail_url"] == "http://storage/thumb.jpg" frames = client.get(f"/api/projects/{project_id}/frames") assert frames.status_code == 200 assert frames.json()[0]["image_url"] == "http://storage/frames/0.jpg" single_frame = client.get(f"/api/projects/{project_id}/frames/{frame_id}") assert single_frame.status_code == 200 assert single_frame.json()["frame_index"] == 0 updated = client.patch(f"/api/projects/{project_id}", json={"name": "Renamed", "status": "ready"}) assert updated.status_code == 200 assert updated.json()["name"] == "Renamed" assert updated.json()["status"] == "ready" deleted = client.delete(f"/api/projects/{project_id}") assert deleted.status_code == 204 assert client.get(f"/api/projects/{project_id}").status_code == 404 def test_project_and_frame_404s(client): assert client.get("/api/projects/999").status_code == 404 assert client.patch("/api/projects/999", json={"name": "x"}).status_code == 404 assert client.delete("/api/projects/999").status_code == 404 assert client.post("/api/projects/999/frames", json={ "project_id": 999, "frame_index": 0, "image_url": "missing.jpg", }).status_code == 404 assert client.get("/api/projects/999/frames").status_code == 404 assert client.get("/api/projects/999/frames/1").status_code == 404