Browse Source

test(18-01): add workflow route tests with fs mocking

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
handoff-20260429-1057
shrimbly 6 months ago
parent
commit
f7625ed1ca
  1. 298
      src/app/api/workflow/__tests__/route.test.ts

298
src/app/api/workflow/__tests__/route.test.ts

@ -0,0 +1,298 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { NextRequest } from "next/server";
// Mock fs/promises before importing the route
const mockStat = vi.fn();
const mockMkdir = vi.fn();
const mockWriteFile = vi.fn();
vi.mock("fs/promises", () => ({
stat: (...args: unknown[]) => mockStat(...args),
mkdir: (...args: unknown[]) => mockMkdir(...args),
writeFile: (...args: unknown[]) => mockWriteFile(...args),
}));
// Mock logger to avoid console noise during tests
vi.mock("@/utils/logger", () => ({
logger: {
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
},
}));
import { POST, GET } from "../route";
// Helper to create mock NextRequest for POST
function createMockPostRequest(body: unknown): NextRequest {
return {
json: vi.fn().mockResolvedValue(body),
} as unknown as NextRequest;
}
// Helper to create mock NextRequest for GET
function createMockGetRequest(params: Record<string, string>): NextRequest {
return {
nextUrl: {
searchParams: new URLSearchParams(params),
},
} as unknown as NextRequest;
}
describe("/api/workflow route", () => {
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
vi.resetAllMocks();
});
describe("POST - Save workflow", () => {
it("should save workflow successfully", async () => {
const mockWorkflow = {
nodes: [{ id: "node1", type: "prompt" }],
edges: [],
};
mockStat.mockResolvedValue({
isDirectory: () => true,
});
mockMkdir.mockResolvedValue(undefined);
mockWriteFile.mockResolvedValue(undefined);
const request = createMockPostRequest({
directoryPath: "/test/dir",
filename: "my-workflow",
workflow: mockWorkflow,
});
const response = await POST(request);
const data = await response.json();
expect(data.success).toBe(true);
expect(data.filePath).toBe("/test/dir/my-workflow.json");
expect(mockWriteFile).toHaveBeenCalledWith(
"/test/dir/my-workflow.json",
JSON.stringify(mockWorkflow, null, 2),
"utf-8"
);
});
it("should sanitize filename with special characters", async () => {
const mockWorkflow = { nodes: [], edges: [] };
mockStat.mockResolvedValue({
isDirectory: () => true,
});
mockMkdir.mockResolvedValue(undefined);
mockWriteFile.mockResolvedValue(undefined);
const request = createMockPostRequest({
directoryPath: "/test/dir",
filename: "my workflow!@#$%",
workflow: mockWorkflow,
});
const response = await POST(request);
const data = await response.json();
expect(data.success).toBe(true);
expect(data.filePath).toBe("/test/dir/my_workflow_____.json");
});
it("should create inputs and generations subfolders", async () => {
const mockWorkflow = { nodes: [], edges: [] };
mockStat.mockResolvedValue({
isDirectory: () => true,
});
mockMkdir.mockResolvedValue(undefined);
mockWriteFile.mockResolvedValue(undefined);
const request = createMockPostRequest({
directoryPath: "/test/dir",
filename: "workflow",
workflow: mockWorkflow,
});
await POST(request);
expect(mockMkdir).toHaveBeenCalledWith("/test/dir/inputs", { recursive: true });
expect(mockMkdir).toHaveBeenCalledWith("/test/dir/generations", { recursive: true });
});
it("should reject missing directoryPath", async () => {
const request = createMockPostRequest({
filename: "workflow",
workflow: { nodes: [], edges: [] },
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(400);
expect(data.success).toBe(false);
expect(data.error).toBe("Missing required fields");
});
it("should reject missing filename", async () => {
const request = createMockPostRequest({
directoryPath: "/test/dir",
workflow: { nodes: [], edges: [] },
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(400);
expect(data.success).toBe(false);
expect(data.error).toBe("Missing required fields");
});
it("should reject missing workflow", async () => {
const request = createMockPostRequest({
directoryPath: "/test/dir",
filename: "workflow",
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(400);
expect(data.success).toBe(false);
expect(data.error).toBe("Missing required fields");
});
it("should reject non-directory path", async () => {
mockStat.mockResolvedValue({
isDirectory: () => false,
});
const request = createMockPostRequest({
directoryPath: "/test/file.txt",
filename: "workflow",
workflow: { nodes: [], edges: [] },
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(400);
expect(data.success).toBe(false);
expect(data.error).toBe("Path is not a directory");
});
it("should reject non-existent directory", async () => {
mockStat.mockRejectedValue(new Error("ENOENT"));
const request = createMockPostRequest({
directoryPath: "/nonexistent/dir",
filename: "workflow",
workflow: { nodes: [], edges: [] },
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(400);
expect(data.success).toBe(false);
expect(data.error).toBe("Directory does not exist");
});
it("should continue saving even if subfolder creation fails", async () => {
const mockWorkflow = { nodes: [], edges: [] };
mockStat.mockResolvedValue({
isDirectory: () => true,
});
mockMkdir.mockRejectedValue(new Error("Permission denied"));
mockWriteFile.mockResolvedValue(undefined);
const request = createMockPostRequest({
directoryPath: "/test/dir",
filename: "workflow",
workflow: mockWorkflow,
});
const response = await POST(request);
const data = await response.json();
expect(data.success).toBe(true);
expect(data.filePath).toBe("/test/dir/workflow.json");
});
it("should return 500 on write failure", async () => {
mockStat.mockResolvedValue({
isDirectory: () => true,
});
mockMkdir.mockResolvedValue(undefined);
mockWriteFile.mockRejectedValue(new Error("Disk full"));
const request = createMockPostRequest({
directoryPath: "/test/dir",
filename: "workflow",
workflow: { nodes: [], edges: [] },
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(500);
expect(data.success).toBe(false);
expect(data.error).toBe("Disk full");
});
});
describe("GET - Validate directory", () => {
it("should return exists: true for existing directory", async () => {
mockStat.mockResolvedValue({
isDirectory: () => true,
});
const request = createMockGetRequest({ path: "/test/dir" });
const response = await GET(request);
const data = await response.json();
expect(data.success).toBe(true);
expect(data.exists).toBe(true);
expect(data.isDirectory).toBe(true);
});
it("should return isDirectory: false for file path", async () => {
mockStat.mockResolvedValue({
isDirectory: () => false,
});
const request = createMockGetRequest({ path: "/test/file.txt" });
const response = await GET(request);
const data = await response.json();
expect(data.success).toBe(true);
expect(data.exists).toBe(true);
expect(data.isDirectory).toBe(false);
});
it("should return exists: false for non-existent path", async () => {
mockStat.mockRejectedValue(new Error("ENOENT"));
const request = createMockGetRequest({ path: "/nonexistent" });
const response = await GET(request);
const data = await response.json();
expect(data.success).toBe(true);
expect(data.exists).toBe(false);
expect(data.isDirectory).toBe(false);
});
it("should reject missing path parameter", async () => {
const request = createMockGetRequest({});
const response = await GET(request);
const data = await response.json();
expect(response.status).toBe(400);
expect(data.success).toBe(false);
expect(data.error).toBe("Path parameter required");
});
});
});
Loading…
Cancel
Save