def _make_set_with_subclass(client, name="Enemy", sub_name="Bat"): set_id = client.post("/sets", json={"name": name}).json()["id"] main_class_id = client.post(f"/sets/{set_id}/main-classes", json={"name": name}).json()["id"] sub_class_id = client.post(f"/main-classes/{main_class_id}/sub-classes", json={"name": sub_name}).json()["id"] return set_id, sub_class_id def _make_frame(client, session_name="run01", frame_index=0): payload = {"session_name": session_name, "frame_index": frame_index, "image_path": "x.png", "width": 1280, "height": 720} return client.post("/frames", json=payload).json()["id"] def _make_reviewed_frame_with_label(client, set_id, sub_class_id, frame_index): frame_id = _make_frame(client, frame_index=frame_index) client.post( f"/frames/{frame_id}/sets/{set_id}/labels", json={"sub_class_id": sub_class_id, "x": 1, "y": 2, "width": 3, "height": 4}, ) client.put(f"/frames/{frame_id}/sets/{set_id}/status", json={"status": "reviewed"}) return frame_id def test_promote_defaults_to_reviewed_frames(client): set_id, sub_class_id = _make_set_with_subclass(client) reviewed_frame = _make_reviewed_frame_with_label(client, set_id, sub_class_id, frame_index=0) _make_frame(client, frame_index=1) # stays unlabeled, should not be promoted resp = client.post(f"/sets/{set_id}/dataset-versions", json={"name": "v1"}) assert resp.status_code == 201 assert resp.json()["frame_count"] == 1 detail = client.get(f"/dataset-versions/{resp.json()['id']}").json() assert [f["frame"]["id"] for f in detail["frames"]] == [reviewed_frame] assert detail["frames"][0]["labels"][0]["sub_class_id"] == sub_class_id def test_promote_rejects_duplicate_name(client): set_id, sub_class_id = _make_set_with_subclass(client) _make_reviewed_frame_with_label(client, set_id, sub_class_id, frame_index=0) client.post(f"/sets/{set_id}/dataset-versions", json={"name": "v1"}) resp = client.post(f"/sets/{set_id}/dataset-versions", json={"name": "v1"}) assert resp.status_code == 409 def test_promote_rejects_empty_selection(client): set_id, _ = _make_set_with_subclass(client) resp = client.post(f"/sets/{set_id}/dataset-versions", json={"name": "v1"}) assert resp.status_code == 422 def test_promote_with_explicit_frame_ids(client): set_id, sub_class_id = _make_set_with_subclass(client) frame_id = _make_frame(client, frame_index=0) # not reviewed, but explicitly selected client.post( f"/frames/{frame_id}/sets/{set_id}/labels", json={"sub_class_id": sub_class_id, "x": 0, "y": 0, "width": 1, "height": 1}, ) resp = client.post(f"/sets/{set_id}/dataset-versions", json={"name": "v1", "frame_ids": [frame_id]}) assert resp.status_code == 201 assert resp.json()["frame_count"] == 1 def test_promote_rejects_unknown_frame_ids(client): set_id, _ = _make_set_with_subclass(client) resp = client.post(f"/sets/{set_id}/dataset-versions", json={"name": "v1", "frame_ids": [999]}) assert resp.status_code == 422 def test_promote_requires_existing_set(client): resp = client.post("/sets/999/dataset-versions", json={"name": "v1"}) assert resp.status_code == 404 def test_dataset_version_is_frozen_after_label_edits(client): set_id, sub_class_id = _make_set_with_subclass(client) frame_id = _make_reviewed_frame_with_label(client, set_id, sub_class_id, frame_index=0) version_id = client.post(f"/sets/{set_id}/dataset-versions", json={"name": "v1"}).json()["id"] original_labels = client.get(f"/dataset-versions/{version_id}").json()["frames"][0]["labels"] assert original_labels[0]["x"] == 1 # edit the live label after promotion label_id = client.get(f"/frames/{frame_id}/sets/{set_id}/labels").json()[0]["id"] client.patch(f"/labels/{label_id}", json={"x": 999}) # the promoted snapshot must be unaffected frozen_labels = client.get(f"/dataset-versions/{version_id}").json()["frames"][0]["labels"] assert frozen_labels[0]["x"] == 1 # deleting the live label must not touch the snapshot either client.delete(f"/labels/{label_id}") frozen_labels_after_delete = client.get(f"/dataset-versions/{version_id}").json()["frames"][0]["labels"] assert len(frozen_labels_after_delete) == 1 def test_list_dataset_versions(client): set_id, sub_class_id = _make_set_with_subclass(client) _make_reviewed_frame_with_label(client, set_id, sub_class_id, frame_index=0) client.post(f"/sets/{set_id}/dataset-versions", json={"name": "v1"}) resp = client.get(f"/sets/{set_id}/dataset-versions") assert resp.status_code == 200 assert [v["name"] for v in resp.json()] == ["v1"] assert resp.json()[0]["frame_count"] == 1 def test_get_missing_dataset_version(client): resp = client.get("/dataset-versions/999") assert resp.status_code == 404