Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions src/features/workspaces/files/workspace-file-routes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { beforeEach, describe, expect, it, vi } from "vitest";

const getSessionFromRequest = vi.hoisted(() => vi.fn());
const readWorkspaceFilePreview = vi.hoisted(() => vi.fn());
const readWorkspaceFileSource = vi.hoisted(() => vi.fn());
const recordOperationalFailure = vi.hoisted(() => vi.fn());

vi.mock("cloudflare:workers", () => ({ env: {} }));
vi.mock("#/lib/auth-queries.server", () => ({ getSessionFromRequest }));
vi.mock("#/features/workspaces/persistence/workspace-files", () => ({
readWorkspaceFilePreview,
readWorkspaceFileSource,
}));
vi.mock("#/integrations/observability/operational-events", () => ({ recordOperationalFailure }));
vi.mock("#/integrations/posthog/server-context", () => ({
getTelemetryRequestDetails: () => ({}),
}));

import { WorkspaceItemNotFoundError } from "#/features/workspaces/persistence/workspace-postgres-support";
import { handleWorkspaceFileContent } from "#/routes/api/v1/workspaces.$workspaceId.files.$itemId.content";
import { handleWorkspaceFilePreview } from "#/routes/api/v1/workspaces.$workspaceId.files.$itemId.preview";

beforeEach(() => {
vi.clearAllMocks();
getSessionFromRequest.mockResolvedValue({ user: { id: "user-1" } });
});

describe("workspace file routes", () => {
it.each([
["preview", handleWorkspaceFilePreview, readWorkspaceFilePreview],
["content", handleWorkspaceFileContent, readWorkspaceFileSource],
])(
"returns 404 without capturing a failure when the %s item is missing",
async (_, handler, read) => {
read.mockRejectedValue(new WorkspaceItemNotFoundError());

const response = await handler(
new Request("https://thinkex.test/file"),
"workspace-1",
"missing-item",
);

expect(response.status).toBe(404);
expect(recordOperationalFailure).not.toHaveBeenCalled();
},
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ export type Transaction = Parameters<Parameters<Database["transaction"]>[0]>[0];
export type QueryExecutor = Database | Transaction;
export type ItemRow = typeof workspaceItems.$inferSelect;

export class WorkspaceItemNotFoundError extends Error {
constructor() {
super("Workspace item not found.");
this.name = "WorkspaceItemNotFoundError";
}
}

export async function withWorkspaceTransaction<T>(run: (transaction: Transaction) => Promise<T>) {
return await withDb((db) => db.transaction(run));
}
Expand Down Expand Up @@ -114,7 +121,7 @@ export async function requireActiveWorkspaceItemRow(
itemId: string,
) {
const row = await getActiveWorkspaceItemRow(db, workspaceId, itemId);
if (!row) throw new Error("Workspace item not found.");
if (!row) throw new WorkspaceItemNotFoundError();
return row;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { env } from "cloudflare:workers";
import { createFileRoute } from "@tanstack/react-router";

import { readWorkspaceFileSource } from "#/features/workspaces/persistence/workspace-files";
import { WorkspaceItemNotFoundError } from "#/features/workspaces/persistence/workspace-postgres-support";
import { WorkspaceForbiddenError } from "#/features/workspaces/server/permissions";
import { apiError, apiFailure, getRequestId } from "#/lib/api/http";
import { getSessionFromRequest } from "#/lib/auth-queries.server";
Expand Down Expand Up @@ -50,6 +51,9 @@ async function handleWorkspaceFileContent(request: Request, workspaceId: string,
"You do not have permission to view files in this workspace.",
);
}
if (error instanceof WorkspaceItemNotFoundError) {
return apiError(requestId, 404, "FILE_NOT_FOUND", "This workspace file no longer exists.");
}

return apiFailure({
cause: error,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { env } from "cloudflare:workers";
import { createFileRoute } from "@tanstack/react-router";
import { WORKSPACE_FILE_PREVIEW_CONTENT_TYPE } from "#/features/workspaces/files/workspace-file-preview.constants";
import { readWorkspaceFilePreview } from "#/features/workspaces/persistence/workspace-files";
import { WorkspaceItemNotFoundError } from "#/features/workspaces/persistence/workspace-postgres-support";
import { WorkspaceForbiddenError } from "#/features/workspaces/server/permissions";
import { apiError, apiFailure, getRequestId } from "#/lib/api/http";
import { getSessionFromRequest } from "#/lib/auth-queries.server";
Expand Down Expand Up @@ -66,6 +67,9 @@ async function handleWorkspaceFilePreview(request: Request, workspaceId: string,
"You do not have permission to view files in this workspace.",
);
}
if (error instanceof WorkspaceItemNotFoundError) {
return apiError(requestId, 404, "PREVIEW_NOT_FOUND", "This workspace file no longer exists.");
}

return apiFailure({
cause: error,
Expand Down
Loading