From a588cefd2b35607b94748c43db29cd142a3ca2e1 Mon Sep 17 00:00:00 2001 From: mehmet turac Date: Sun, 23 Aug 2026 03:48:14 +0000 Subject: [PATCH] test(daemon): add regression tests for headerless /raw/ reads of workspace-bound projects (#7206) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test(daemon): add regression tests for headerless raw reads of workspace-bound projects Closes #7072. The /raw/ and /preview/ routes call authorizeProjectRequest with { mode: 'read', allowNavigationQuery: true }. Before the enforceLocalProjectDataPlaneRequest refactor, these routes delegated to enforceVerifiedWorkspaceResourceRead, which required an explicit workspace context for any workspace-bound project and returned WORKSPACE_CONTEXT_REQUIRED (400) when headers were absent — breaking local/desktop preview iframes and MCP preview URLs for projects with a personal workspace_projects binding. The two new test cases pin the corrected behaviour: a headerless request to authorizeProjectRequest with allowNavigationQuery: true must succeed (return true, not call any remote verifier) for both personal and team workspace-bound projects, matching exactly what an iframe src or a bare loopback fetch sends. * chore: retrigger CI after flaky workspace-restoration P0 * chore: retrigger CI after repeated workspace-restoration flake --- .../collab/project-request-authority.test.ts | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/apps/daemon/tests/collab/project-request-authority.test.ts b/apps/daemon/tests/collab/project-request-authority.test.ts index 529900058c..e64355b5c8 100644 --- a/apps/daemon/tests/collab/project-request-authority.test.ts +++ b/apps/daemon/tests/collab/project-request-authority.test.ts @@ -691,4 +691,78 @@ describe('createAuthorizeProjectRequest', () => { expect.any(String), ); }); + + // Regression for https://github.com/nexu-io/open-design/issues/7072: + // A personal project with a stale or valid workspace_projects binding must + // be readable without workspace headers when the caller supplies no + // workspace context at all (e.g. an iframe src, MCP preview URL, or bare + // loopback fetch). The /raw/ and /preview/ routes call authorizeProjectRequest + // with { mode: 'read', allowNavigationQuery: true }; returning + // WORKSPACE_CONTEXT_REQUIRED for these headerless loopback reads broke + // desktop preview for local-only personal projects. + it('allows a headerless iframe raw read of a personal workspace-bound project', async () => { + const row = { + workspaceId: 'workspace-a', + visibility: 'personal', + resourceState: 'active', + createdByWorkspaceMemberId: 'member-a', + syncState: 'local_only', + }; + const verify = vi.fn(async () => ({ + ok: false as const, + status: 400 as const, + code: 'WORKSPACE_CONTEXT_REQUIRED', + message: 'an explicit workspace context is required', + })); + const sendApiError = vi.fn(); + const authorize = createAuthorizeProjectRequest({ + db: {}, + getWorkspaceProject: () => row, + getWorkspaceProjectByProjectId: () => row, + verifyWorkspaceRequestAuthority: verify, + sendApiError, + }); + + await expect(authorize( + request({}), + response(), + 'project-a', + { mode: 'read', allowNavigationQuery: true }, + )).resolves.toBe(true); + expect(verify).not.toHaveBeenCalled(); + expect(sendApiError).not.toHaveBeenCalled(); + }); + + it('allows a headerless iframe raw read of a team workspace-bound project', async () => { + const row = { + workspaceId: 'workspace-a', + visibility: 'team', + resourceState: 'active', + createdByWorkspaceMemberId: 'member-a', + syncState: 'local_only', + }; + const verify = vi.fn(async () => ({ + ok: false as const, + status: 400 as const, + code: 'WORKSPACE_CONTEXT_REQUIRED', + message: 'an explicit workspace context is required', + })); + const sendApiError = vi.fn(); + const authorize = createAuthorizeProjectRequest({ + db: {}, + getWorkspaceProject: () => row, + getWorkspaceProjectByProjectId: () => row, + verifyWorkspaceRequestAuthority: verify, + sendApiError, + }); + + await expect(authorize( + request({}), + response(), + 'project-a', + { mode: 'read', allowNavigationQuery: true }, + )).resolves.toBe(true); + expect(verify).not.toHaveBeenCalled(); + expect(sendApiError).not.toHaveBeenCalled(); + }); });