diff --git a/server/mergin/sync/public_api_v2_controller.py b/server/mergin/sync/public_api_v2_controller.py index e7806865..823d6e5f 100644 --- a/server/mergin/sync/public_api_v2_controller.py +++ b/server/mergin/sync/public_api_v2_controller.py @@ -234,10 +234,6 @@ def create_project_version(id): v_next_version = ProjectVersion.to_v_name(next_version) version_dir = os.path.join(project.storage.project_dir, v_next_version) - pv = project.get_latest_version() - if pv and pv.name != version: - return ProjectVersionExists(version, pv.name).response(409) - try: ChangesSchema().validate(changes) upload_changes = ChangesSchema().dump(changes) @@ -283,6 +279,10 @@ def create_project_version(id): if requested_storage > project.workspace.storage: return StorageLimitHit(current_usage, project.workspace.storage).response(422) + pv = project.get_latest_version() + if pv and pv.name != version: + return ProjectVersionExists(version, pv.name).response(409) + # we have done all checks but this request is just a dry-run if request.json.get("check_only", False): return NoContent, 204 diff --git a/server/mergin/tests/test_public_api_v2.py b/server/mergin/tests/test_public_api_v2.py index 56caa7ff..976ffe86 100644 --- a/server/mergin/tests/test_public_api_v2.py +++ b/server/mergin/tests/test_public_api_v2.py @@ -1079,6 +1079,41 @@ def test_create_version_failures(client): assert response.status_code == 409 +def test_create_version_permanent_error_takes_priority(client): + """Permanent errors (e.g. storage limit) must be reported before a + version conflict, otherwise clients would rebase/retry an upload that + is bound to fail anyway.""" + project = Project.query.filter_by( + workspace_id=test_workspace_id, name=test_project + ).first() + + data = { + "version": "v0", + "changes": _get_changes_without_added(test_project_dir), + "check_only": True, + } + with patch.object( + Configuration, + "GLOBAL_STORAGE", + 0, + ): + response = client.post(f"v2/projects/{project.id}/versions", json=data) + assert response.status_code == 422 + assert response.json["code"] == StorageLimitHit.code + + # same must hold for the real (non check_only) upload + data["check_only"] = False + with patch.object( + Configuration, + "GLOBAL_STORAGE", + 0, + ): + response = client.post(f"v2/projects/{project.id}/versions", json=data) + assert response.status_code == 422 + assert response.json["code"] == StorageLimitHit.code + assert project.latest_version == 1 + + def test_upload_chunk(client): """Test pushing a chunk to a project""" project = Project.query.filter_by(