From 2f68a3fae49fa8e0948221aaa99a6d416d82383f Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 3 Jun 2026 20:22:57 +0200 Subject: [PATCH] fix(api/v2): omit project max_permission (null) when not expanded The project read handler left MaxPermission at its zero value when expand=permissions was not requested, which serialised as 0 (PermissionRead) instead of being omitted. Force PermissionUnknown so the field marshals as null, matching the list operation's behaviour and avoiding a misleading read permission for projects the caller may own. Assert the null shape in the ReadOne/Normal webtest. --- pkg/routes/api/v2/projects.go | 7 ++++++- pkg/webtests/huma_project_test.go | 3 +++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/pkg/routes/api/v2/projects.go b/pkg/routes/api/v2/projects.go index c8e45018d..37ff4bd1e 100644 --- a/pkg/routes/api/v2/projects.go +++ b/pkg/routes/api/v2/projects.go @@ -126,9 +126,14 @@ func projectsRead(ctx context.Context, in *struct { } // ReadOne doesn't act on Expand itself; the caller's max permission comes // from DoReadOne's CanRead result. Surface it on the model only when asked, - // matching the list operation's expand=permissions behaviour. + // matching the list operation's expand=permissions behaviour. When not + // expanded, force PermissionUnknown so max_permission marshals as null + // instead of defaulting to the zero value (0 = PermissionRead), which would + // be a misleading "read" permission for projects the caller may fully own. if models.ProjectExpandable(in.Expand) == models.ProjectExpandableRights { project.MaxPermission = models.Permission(maxPermission) + } else { + project.MaxPermission = models.PermissionUnknown } // PreconditionFailed wants the unquoted etag; response header uses RFC 9110 quoted form. etag := fmt.Sprintf("%d-%d", project.ID, project.Updated.UnixNano()) diff --git a/pkg/webtests/huma_project_test.go b/pkg/webtests/huma_project_test.go index da820e3f8..df24b107b 100644 --- a/pkg/webtests/huma_project_test.go +++ b/pkg/webtests/huma_project_test.go @@ -69,6 +69,9 @@ func TestHumaProject(t *testing.T) { assert.Contains(t, rec.Body.String(), `"title":"Test1"`) assert.NotContains(t, rec.Body.String(), `"title":"Test2"`) assert.Contains(t, rec.Body.String(), `"username":"user1"`) + // Without expand=permissions, max_permission must be null rather than + // defaulting to 0 (which would falsely read as PermissionRead). + assert.Contains(t, rec.Body.String(), `"max_permission":null`) assert.NotEmpty(t, rec.Result().Header.Get("ETag")) }) t.Run("Expand permissions", func(t *testing.T) {