mirror of
https://github.com/makeplane/plane.git
synced 2026-05-18 05:05:30 +02:00
* test(api): add regression tests for create-project endpoint Cover three scenarios: - project_lead set to the creator's own user_id - project_lead set to a different workspace member - project_lead omitted (baseline) The first two currently fail on preview because of a UUID coercion bug in ProjectMember.objects.create — see follow-up commit. * fix(api): pass project_lead_id (not User instance) when creating ProjectMember The create-project endpoint built a ProjectMember row with member_id=serializer.instance.project_lead, which resolves to a User instance via Django's related descriptor instead of a UUID. Django's UUIDField coercion then fails with AttributeError: 'User' object has no attribute 'replace', which the generic exception handler converts to a 400 "Please provide valid detail" — but only after the Project row was already persisted, leaving an orphaned project without default states. Fix: - Use project_lead_id (FK ID, no descriptor lookup) on both the guard comparison and the ProjectMember creation. - Wrap the post-save flow in transaction.atomic() so any future exception triggers a clean rollback. - Defer model_activity.delay() with transaction.on_commit() so the activity log only fires after a successful commit. - Capture the exception with log_exception() in the generic catch so future regressions surface in api logs. Note: a related data integrity issue exists where ProjectCreateSerializer doesn't create a ProjectIdentifier row (unlike its frontend counterpart). Out of scope here, will follow up in a separate PR. * fix(api): return 500 on unexpected errors and harden project create Address review feedback from @sriramveeraghanta on PR #8966: - The catch-all `except Exception` now returns 500 instead of 400. Reusing the generic 400 response on a server-side crash was the anti-pattern that hid the original ghost-create bug for nine months; a 500 lets clients distinguish between "bad input" and "server fault". - The `IntegrityError` branch no longer falls through silently when the message is unrecognised. It re-raises so the catch-all `except` logs the exception and returns a 500. - `transaction.on_commit()` now schedules `model_activity.delay` via `functools.partial` instead of a lambda, avoiding late-binding closure semantics. - `ProjectCreateSerializer.validate()` now rejects `project_lead` values that are not active workspace members, surfacing the error under the `project_lead` field key (rather than as `non_field_errors`) so API clients can react programmatically. * test(api): harden assertions and cover rollback / workspace-membership Address review feedback from @sriramveeraghanta on PR #8966: - The three existing tests now look up the created project via `Project.objects.get(id=response.data["id"])` instead of `.first()`. The assertion now fails for the right reason if the wrong project is returned by the endpoint. - New `test_create_project_with_lead_not_in_workspace_returns_400` guards the workspace-membership validation added to `ProjectCreateSerializer.validate()`. Expects a 400 with a field-shaped error and zero rows persisted. - New `test_model_activity_not_called_on_rollback` locks in the `transaction.on_commit()` semantics: when an exception is raised inside the atomic block (forced via mocking `State.objects.bulk_create`), the response is 500, no Project / ProjectMember / State rows are persisted, and the deferred `model_activity.delay` task is never dispatched. This prevents a future refactor from silently regressing the rollback contract. * fix(api): mark on_commit dispatch as robust against broker failures Address coderabbit re-review feedback on PR #8966. Without robust=True, an exception raised by model_activity.delay (e.g., a Celery broker outage) propagates out of the on_commit callback and is caught by the outer `except Exception` handler, which returns a 500 despite the project, ProjectMember rows and default States having already been committed. The client sees a 500 and assumes the create failed — the same class of mismatch between actual state and reported status that the original bug exhibited, just at the post-commit phase. Set robust=True so Django logs the dispatch failure internally via the standard transaction logger and the response stays 201, reflecting the persisted state. Switch from `functools.partial` to a nested function (`_dispatch_model_activity`) for the on_commit callable. Django's robust on_commit logging path reads `func.__qualname__` to format the error message; `partial` objects lack that dunder by default, and the `functools.update_wrapper` workaround turns out to be brittle when the wrapped callable is replaced by a Mock (which the new regression test relies on). A nested function exposes `__qualname__` natively, and the locals it closes over are bound at definition time and never rebound before the callback fires, so the late-binding-closure motivation for `partial` over `lambda` does not apply here. A new test, test_response_still_201_when_broker_dispatch_fails, mirrors test_model_activity_not_called_on_rollback to lock in the post-commit branch. It uses `@pytest.mark.django_db(transaction=True)` so the surrounding test transaction is actually committed and the `on_commit` callback fires (the default wrapper suppresses it via rollback). * fix(api): handle unrecognised IntegrityError consistently Address coderabbit re-review feedback on PR #8966. The previous fix used `raise` inside the IntegrityError handler with the intent of "letting the catch-all `except Exception` below log it and return 500". Coderabbit correctly flagged that `raise` exits the try/except entirely — sibling except clauses don't fire — so unrecognised integrity errors actually skipped `log_exception` and the consistent 500 JSON shape, contradicting the stated intent. Replicate the catch-all behaviour inline: log the exception via `log_exception(e)` and return the same generic 500 response with `{"error": "An unexpected error occurred"}`. The client now gets a uniform error shape regardless of which `except` branch handled it. --------- Co-authored-by: Jose Antonio Martinez <257598434+jamartineztelecoengineer84-dotcom@users.noreply.github.com>