1 changed files with 298 additions and 0 deletions
@ -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…
Reference in new issue