feat(#194): Fix workspace ID transmission mismatch between API and client
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed

- Update WorkspaceGuard to support query string as fallback (backward compatibility)
- Priority order: Header > Param > Body > Query
- Update web client to send workspace ID via X-Workspace-Id header (recommended)
- Extend apiRequest helpers to accept workspace ID option
- Update fetchTasks to use header instead of query parameter
- Add comprehensive tests for all workspace ID transmission methods
- Tests passing: API 11 tests, Web 6 new tests (total 494)

This ensures consistent workspace ID handling with proper multi-tenant isolation
while maintaining backward compatibility with existing query string approaches.

Fixes #194

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-03 22:38:13 -06:00
parent ae4221968e
commit 88be403c86
27 changed files with 706 additions and 33 deletions

View File

@@ -59,7 +59,7 @@ describe("Task API Client", (): void => {
const result = await fetchTasks();
expect(apiGet).toHaveBeenCalledWith("/api/tasks");
expect(apiGet).toHaveBeenCalledWith("/api/tasks", undefined);
expect(result).toEqual(mockTasks);
});
@@ -75,7 +75,7 @@ describe("Task API Client", (): void => {
await fetchTasks({ status: TaskStatus.IN_PROGRESS });
expect(apiGet).toHaveBeenCalledWith("/api/tasks?status=IN_PROGRESS");
expect(apiGet).toHaveBeenCalledWith("/api/tasks?status=IN_PROGRESS", undefined);
});
it("should fetch tasks with multiple filters", async (): Promise<void> => {
@@ -84,7 +84,30 @@ describe("Task API Client", (): void => {
await fetchTasks({ status: TaskStatus.IN_PROGRESS, priority: TaskPriority.HIGH });
expect(apiGet).toHaveBeenCalledWith("/api/tasks?status=IN_PROGRESS&priority=HIGH");
expect(apiGet).toHaveBeenCalledWith("/api/tasks?status=IN_PROGRESS&priority=HIGH", undefined);
});
it("should fetch tasks with workspace ID", async (): Promise<void> => {
const mockTasks: Task[] = [];
vi.mocked(apiGet).mockResolvedValueOnce({ data: mockTasks });
await fetchTasks({ workspaceId: "workspace-123" });
// WorkspaceId should be sent via header (second param), not query string
expect(apiGet).toHaveBeenCalledWith("/api/tasks", "workspace-123");
});
it("should fetch tasks with filters and workspace ID", async (): Promise<void> => {
const mockTasks: Task[] = [];
vi.mocked(apiGet).mockResolvedValueOnce({ data: mockTasks });
await fetchTasks({
status: TaskStatus.IN_PROGRESS,
workspaceId: "workspace-456",
});
// Status in query, workspace in header
expect(apiGet).toHaveBeenCalledWith("/api/tasks?status=IN_PROGRESS", "workspace-456");
});
describe("error handling", (): void => {
@@ -138,7 +161,7 @@ describe("Task API Client", (): void => {
await fetchTasks({ invalidFilter: "value" } as any);
// Function should ignore invalid filters and call without query params
expect(apiGet).toHaveBeenCalledWith("/api/tasks");
expect(apiGet).toHaveBeenCalledWith("/api/tasks", undefined);
});
it("should handle empty response data", async (): Promise<void> => {