feat: add per-provider timeout for usage panel to prevent blocking on slow responses
fix: update scheduler filesystem path from .kb to .fusion for task validation style: clean up TaskDetailModal styling with reusable CSS classes test: add comprehensive tests for file-service operations with mocked filesystem
This commit is contained in:
69
packages/dashboard/src/__tests__/debug.test.ts
Normal file
69
packages/dashboard/src/__tests__/debug.test.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import type { TaskStore } from "@fusion/core";
|
||||
|
||||
// Create mock functions that can be configured in tests
|
||||
const mocks = vi.hoisted(() => ({
|
||||
mockReaddir: vi.fn(),
|
||||
mockReadFile: vi.fn(),
|
||||
mockWriteFile: vi.fn(),
|
||||
mockStat: vi.fn(),
|
||||
mockExistsSync: vi.fn(),
|
||||
}));
|
||||
|
||||
// Use vi.mock with __mocks__ pattern
|
||||
vi.mock("node:fs/promises", () => {
|
||||
return {
|
||||
default: {
|
||||
readdir: mocks.mockReaddir,
|
||||
readFile: mocks.mockReadFile,
|
||||
writeFile: mocks.mockWriteFile,
|
||||
stat: mocks.mockStat,
|
||||
},
|
||||
readdir: mocks.mockReaddir,
|
||||
readFile: mocks.mockReadFile,
|
||||
writeFile: mocks.mockWriteFile,
|
||||
stat: mocks.mockStat,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("node:fs", () => {
|
||||
return {
|
||||
default: {
|
||||
existsSync: mocks.mockExistsSync,
|
||||
},
|
||||
existsSync: mocks.mockExistsSync,
|
||||
};
|
||||
});
|
||||
|
||||
// Import file-service
|
||||
import { listProjectFiles } from "../file-service";
|
||||
|
||||
describe("debug", () => {
|
||||
it("test", async () => {
|
||||
const mockGetRootDir = vi.fn();
|
||||
const mockStore = {
|
||||
getRootDir: mockGetRootDir,
|
||||
} as unknown as TaskStore;
|
||||
|
||||
mockGetRootDir.mockReturnValue("/project");
|
||||
|
||||
mocks.mockStat.mockResolvedValue({
|
||||
isDirectory: () => true,
|
||||
isFile: () => false,
|
||||
});
|
||||
mocks.mockReaddir.mockResolvedValue([]);
|
||||
|
||||
console.log("Calling listProjectFiles...");
|
||||
try {
|
||||
const result = await listProjectFiles(mockStore, "./src");
|
||||
console.log("Result:", result);
|
||||
console.log("mockStat calls:", mocks.mockStat.mock.calls);
|
||||
expect(result.path).toBe("src");
|
||||
expect(result.entries).toEqual([]);
|
||||
} catch (e) {
|
||||
console.log("Error:", e);
|
||||
console.log("mockStat calls:", mocks.mockStat.mock.calls);
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
});
|
||||
80
packages/dashboard/src/__tests__/debug10.test.ts
Normal file
80
packages/dashboard/src/__tests__/debug10.test.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import type { TaskStore } from "@fusion/core";
|
||||
import { dirname, resolve } from "path";
|
||||
|
||||
// Create mock functions that can be configured in tests
|
||||
const mocks = vi.hoisted(() => ({
|
||||
mockReaddir: vi.fn(),
|
||||
mockReadFile: vi.fn(),
|
||||
mockWriteFile: vi.fn(),
|
||||
mockStat: vi.fn(),
|
||||
mockExistsSync: vi.fn(),
|
||||
}));
|
||||
|
||||
// Hoist mocks alongside vi.mock
|
||||
vi.mock("node:fs/promises", async () => {
|
||||
const actual = await import("node:fs/promises");
|
||||
return {
|
||||
default: {
|
||||
readdir: mocks.mockReaddir,
|
||||
readFile: mocks.mockReadFile,
|
||||
writeFile: mocks.mockWriteFile,
|
||||
stat: mocks.mockStat,
|
||||
},
|
||||
readdir: mocks.mockReaddir,
|
||||
readFile: mocks.mockReadFile,
|
||||
writeFile: mocks.mockWriteFile,
|
||||
stat: mocks.mockStat,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("node:fs", async () => {
|
||||
const actual = await import("node:fs");
|
||||
return {
|
||||
default: {
|
||||
existsSync: mocks.mockExistsSync,
|
||||
},
|
||||
existsSync: mocks.mockExistsSync,
|
||||
};
|
||||
});
|
||||
|
||||
// Import AFTER mocks are set up
|
||||
import { writeProjectFile } from "../file-service";
|
||||
|
||||
describe("debug", () => {
|
||||
beforeEach(() => {
|
||||
mocks.mockStat.mockReset();
|
||||
});
|
||||
|
||||
it("test", async () => {
|
||||
const mockGetRootDir = vi.fn();
|
||||
const mockStore = {
|
||||
getRootDir: mockGetRootDir,
|
||||
} as unknown as TaskStore;
|
||||
|
||||
mockGetRootDir.mockReturnValue("/test/project");
|
||||
|
||||
const basePath = resolve("/test/project");
|
||||
const filePath = "file.txt/sub.txt";
|
||||
const resolvedPath = resolve(basePath, filePath);
|
||||
const parentDir = dirname(resolvedPath);
|
||||
|
||||
// Test the mock directly
|
||||
mocks.mockStat.mockResolvedValue({
|
||||
isDirectory: () => false,
|
||||
isFile: () => true,
|
||||
});
|
||||
|
||||
const result = await mocks.mockStat("/test/some/path");
|
||||
console.log("Direct mock result:", result);
|
||||
console.log("Direct mock isDirectory():", result.isDirectory());
|
||||
|
||||
// Now test writeProjectFile
|
||||
try {
|
||||
await writeProjectFile(mockStore, filePath, "content");
|
||||
console.log("Success!");
|
||||
} catch (e: any) {
|
||||
console.log("Error:", e.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
93
packages/dashboard/src/__tests__/debug11.test.ts
Normal file
93
packages/dashboard/src/__tests__/debug11.test.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import type { TaskStore } from "@fusion/core";
|
||||
import { dirname, resolve } from "path";
|
||||
|
||||
// Create mock functions that can be configured in tests
|
||||
const mocks = vi.hoisted(() => ({
|
||||
mockReaddir: vi.fn(),
|
||||
mockReadFile: vi.fn(),
|
||||
mockWriteFile: vi.fn(),
|
||||
mockStat: vi.fn(),
|
||||
mockExistsSync: vi.fn(),
|
||||
}));
|
||||
|
||||
// Hoist mocks alongside vi.mock
|
||||
vi.mock("node:fs/promises", async () => {
|
||||
const actual = await import("node:fs/promises");
|
||||
return {
|
||||
default: {
|
||||
readdir: mocks.mockReaddir,
|
||||
readFile: mocks.mockReadFile,
|
||||
writeFile: mocks.mockWriteFile,
|
||||
stat: mocks.mockStat,
|
||||
},
|
||||
readdir: mocks.mockReaddir,
|
||||
readFile: mocks.mockReadFile,
|
||||
writeFile: mocks.mockWriteFile,
|
||||
stat: mocks.mockStat,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("node:fs", async () => {
|
||||
const actual = await import("node:fs");
|
||||
return {
|
||||
default: {
|
||||
existsSync: mocks.mockExistsSync,
|
||||
},
|
||||
existsSync: mocks.mockExistsSync,
|
||||
};
|
||||
});
|
||||
|
||||
// Import AFTER mocks are set up
|
||||
import { writeProjectFile } from "../file-service";
|
||||
|
||||
describe("debug", () => {
|
||||
beforeEach(() => {
|
||||
mocks.mockStat.mockReset();
|
||||
});
|
||||
|
||||
it("test", async () => {
|
||||
const mockGetRootDir = vi.fn();
|
||||
const mockStore = {
|
||||
getRootDir: mockGetRootDir,
|
||||
} as unknown as TaskStore;
|
||||
|
||||
mockGetRootDir.mockReturnValue("/test/project");
|
||||
|
||||
// Track all stat calls
|
||||
let callCount = 0;
|
||||
mocks.mockStat.mockImplementation(async (path: string) => {
|
||||
callCount++;
|
||||
console.log(`stat call #${callCount}:`, path);
|
||||
|
||||
if (callCount === 1) {
|
||||
// First call - file doesn't exist
|
||||
throw Object.assign(new Error("ENOENT"), { code: "ENOENT" });
|
||||
}
|
||||
if (callCount === 2) {
|
||||
// Second call - parent is a file
|
||||
return {
|
||||
isDirectory: () => false,
|
||||
isFile: () => true,
|
||||
};
|
||||
}
|
||||
if (callCount === 3) {
|
||||
// Third call - after write
|
||||
return {
|
||||
isDirectory: () => false,
|
||||
isFile: () => true,
|
||||
size: 100,
|
||||
mtime: new Date(),
|
||||
};
|
||||
}
|
||||
throw Object.assign(new Error("ENOENT"), { code: "ENOENT" });
|
||||
});
|
||||
|
||||
try {
|
||||
await writeProjectFile(mockStore, "file.txt/sub.txt", "content");
|
||||
console.log("Success!");
|
||||
} catch (e: any) {
|
||||
console.log("Error:", e.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
81
packages/dashboard/src/__tests__/debug12.test.ts
Normal file
81
packages/dashboard/src/__tests__/debug12.test.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import type { TaskStore } from "@fusion/core";
|
||||
import { dirname, resolve } from "path";
|
||||
|
||||
// Create mock functions that can be configured in tests
|
||||
const mocks = vi.hoisted(() => ({
|
||||
mockReaddir: vi.fn(),
|
||||
mockReadFile: vi.fn(),
|
||||
mockWriteFile: vi.fn(),
|
||||
mockStat: vi.fn(),
|
||||
mockExistsSync: vi.fn(),
|
||||
}));
|
||||
|
||||
// Hoist mocks alongside vi.mock
|
||||
vi.mock("node:fs/promises", async () => {
|
||||
const actual = await import("node:fs/promises");
|
||||
return {
|
||||
default: {
|
||||
readdir: mocks.mockReaddir,
|
||||
readFile: mocks.mockReadFile,
|
||||
writeFile: mocks.mockWriteFile,
|
||||
stat: mocks.mockStat,
|
||||
},
|
||||
readdir: mocks.mockReaddir,
|
||||
readFile: mocks.mockReadFile,
|
||||
writeFile: mocks.mockWriteFile,
|
||||
stat: mocks.mockStat,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("node:fs", async () => {
|
||||
const actual = await import("node:fs");
|
||||
return {
|
||||
default: {
|
||||
existsSync: mocks.mockExistsSync,
|
||||
},
|
||||
existsSync: mocks.mockExistsSync,
|
||||
};
|
||||
});
|
||||
|
||||
// Import AFTER mocks are set up
|
||||
import { writeProjectFile } from "../file-service";
|
||||
|
||||
describe("debug", () => {
|
||||
beforeEach(() => {
|
||||
mocks.mockStat.mockReset();
|
||||
});
|
||||
|
||||
it("test", async () => {
|
||||
const mockGetRootDir = vi.fn();
|
||||
const mockStore = {
|
||||
getRootDir: mockGetRootDir,
|
||||
} as unknown as TaskStore;
|
||||
|
||||
mockGetRootDir.mockReturnValue("/test/project");
|
||||
|
||||
let callCount = 0;
|
||||
mocks.mockStat.mockImplementation(async (path: string) => {
|
||||
callCount++;
|
||||
console.log(`stat call #${callCount}:`, path);
|
||||
|
||||
// Return a simple object
|
||||
const result = {
|
||||
isDirectory: () => path.includes("/file.txt"),
|
||||
isFile: () => !path.includes("/file.txt"),
|
||||
};
|
||||
|
||||
console.log(`stat #${callCount} returning:`, result);
|
||||
console.log(`stat #${callCount} isDirectory():`, result.isDirectory());
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
try {
|
||||
await writeProjectFile(mockStore, "file.txt/sub.txt", "content");
|
||||
console.log("Success!");
|
||||
} catch (e: any) {
|
||||
console.log("Error:", e.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
80
packages/dashboard/src/__tests__/debug13.test.ts
Normal file
80
packages/dashboard/src/__tests__/debug13.test.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import type { TaskStore } from "@fusion/core";
|
||||
|
||||
// Create mock functions that can be configured in tests
|
||||
const mocks = vi.hoisted(() => ({
|
||||
mockReaddir: vi.fn(),
|
||||
mockReadFile: vi.fn(),
|
||||
mockWriteFile: vi.fn(),
|
||||
mockStat: vi.fn(),
|
||||
mockExistsSync: vi.fn(),
|
||||
}));
|
||||
|
||||
// Hoist mocks alongside vi.mock
|
||||
vi.mock("node:fs/promises", async () => {
|
||||
const actual = await import("node:fs/promises");
|
||||
return {
|
||||
default: {
|
||||
readdir: mocks.mockReaddir,
|
||||
readFile: mocks.mockReadFile,
|
||||
writeFile: mocks.mockWriteFile,
|
||||
stat: mocks.mockStat,
|
||||
},
|
||||
readdir: mocks.mockReaddir,
|
||||
readFile: mocks.mockReadFile,
|
||||
writeFile: mocks.mockWriteFile,
|
||||
stat: mocks.mockStat,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("node:fs", async () => {
|
||||
const actual = await import("node:fs");
|
||||
return {
|
||||
default: {
|
||||
existsSync: mocks.mockExistsSync,
|
||||
},
|
||||
existsSync: mocks.mockExistsSync,
|
||||
};
|
||||
});
|
||||
|
||||
// Import AFTER mocks are set up
|
||||
import { writeProjectFile } from "../file-service";
|
||||
|
||||
describe("debug", () => {
|
||||
beforeEach(() => {
|
||||
mocks.mockStat.mockReset();
|
||||
});
|
||||
|
||||
it("test - parent is not a directory", async () => {
|
||||
const mockGetRootDir = vi.fn();
|
||||
const mockStore = {
|
||||
getRootDir: mockGetRootDir,
|
||||
} as unknown as TaskStore;
|
||||
|
||||
mockGetRootDir.mockReturnValue("/test/project");
|
||||
|
||||
// First call: file doesn't exist (throw ENOENT)
|
||||
// Second call: parent is a file, not a directory (return object with isDirectory: false)
|
||||
// Third call: after write, get file stats
|
||||
mocks.mockStat
|
||||
.mockRejectedValueOnce({ code: "ENOENT" }) // First: file doesn't exist
|
||||
.mockResolvedValueOnce({ // Second: parent is a file
|
||||
isDirectory: () => false,
|
||||
isFile: () => true,
|
||||
})
|
||||
.mockResolvedValueOnce({ // Third: after write
|
||||
isDirectory: () => false,
|
||||
isFile: () => true,
|
||||
size: 100,
|
||||
mtime: new Date(),
|
||||
});
|
||||
|
||||
try {
|
||||
await writeProjectFile(mockStore, "file.txt/sub.txt", "content");
|
||||
console.log("Success!");
|
||||
} catch (e: any) {
|
||||
console.log("Error:", e.message);
|
||||
expect(e.message).toContain("Parent is not a directory");
|
||||
}
|
||||
});
|
||||
});
|
||||
79
packages/dashboard/src/__tests__/debug14.test.ts
Normal file
79
packages/dashboard/src/__tests__/debug14.test.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import type { TaskStore } from "@fusion/core";
|
||||
|
||||
// Create mock functions that can be configured in tests
|
||||
const mocks = vi.hoisted(() => ({
|
||||
mockReaddir: vi.fn(),
|
||||
mockReadFile: vi.fn(),
|
||||
mockWriteFile: vi.fn(),
|
||||
mockStat: vi.fn(),
|
||||
mockExistsSync: vi.fn(),
|
||||
}));
|
||||
|
||||
// Hoist mocks alongside vi.mock
|
||||
vi.mock("node:fs/promises", async () => {
|
||||
const actual = await import("node:fs/promises");
|
||||
return {
|
||||
default: {
|
||||
readdir: mocks.mockReaddir,
|
||||
readFile: mocks.mockReadFile,
|
||||
writeFile: mocks.mockWriteFile,
|
||||
stat: mocks.mockStat,
|
||||
},
|
||||
readdir: mocks.mockReaddir,
|
||||
readFile: mocks.mockReadFile,
|
||||
writeFile: mocks.mockWriteFile,
|
||||
stat: mocks.mockStat,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("node:fs", async () => {
|
||||
const actual = await import("node:fs");
|
||||
return {
|
||||
default: {
|
||||
existsSync: mocks.mockExistsSync,
|
||||
},
|
||||
existsSync: mocks.mockExistsSync,
|
||||
};
|
||||
});
|
||||
|
||||
// Import AFTER mocks are set up
|
||||
import { writeProjectFile } from "../file-service";
|
||||
|
||||
describe("debug", () => {
|
||||
beforeEach(() => {
|
||||
mocks.mockStat.mockReset();
|
||||
mocks.mockStat.mockImplementation(async (path: string) => {
|
||||
console.log("stat called:", path);
|
||||
throw { code: "ENOENT" };
|
||||
});
|
||||
});
|
||||
|
||||
it("test - parent is not a directory", async () => {
|
||||
const mockGetRootDir = vi.fn();
|
||||
const mockStore = {
|
||||
getRootDir: mockGetRootDir,
|
||||
} as unknown as TaskStore;
|
||||
|
||||
mockGetRootDir.mockReturnValue("/test/project");
|
||||
|
||||
// First call: file doesn't exist (throw ENOENT)
|
||||
// Second call: parent is a file, not a directory (return object with isDirectory: false)
|
||||
mocks.mockStat
|
||||
.mockRejectedValueOnce({ code: "ENOENT" }) // First: file doesn't exist
|
||||
.mockImplementation(async (path: string) => { // Subsequent calls
|
||||
console.log("Second stat call:", path);
|
||||
return {
|
||||
isDirectory: () => false,
|
||||
isFile: () => true,
|
||||
};
|
||||
});
|
||||
|
||||
try {
|
||||
await writeProjectFile(mockStore, "file.txt/sub.txt", "content");
|
||||
console.log("Success!");
|
||||
} catch (e: any) {
|
||||
console.log("Error:", e.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
63
packages/dashboard/src/__tests__/debug2.test.ts
Normal file
63
packages/dashboard/src/__tests__/debug2.test.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { writeProjectFile } from "../file-service";
|
||||
import type { TaskStore } from "@fusion/core";
|
||||
|
||||
// Create mock functions that can be configured in tests
|
||||
const mocks = vi.hoisted(() => ({
|
||||
mockReaddir: vi.fn(),
|
||||
mockReadFile: vi.fn(),
|
||||
mockWriteFile: vi.fn(),
|
||||
mockStat: vi.fn(),
|
||||
mockExistsSync: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("node:fs/promises", () => {
|
||||
return {
|
||||
default: {
|
||||
readdir: mocks.mockReaddir,
|
||||
readFile: mocks.mockReadFile,
|
||||
writeFile: mocks.mockWriteFile,
|
||||
stat: mocks.mockStat,
|
||||
},
|
||||
readdir: mocks.mockReaddir,
|
||||
readFile: mocks.mockReadFile,
|
||||
writeFile: mocks.mockWriteFile,
|
||||
stat: mocks.mockStat,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("node:fs", () => {
|
||||
return {
|
||||
default: {
|
||||
existsSync: mocks.mockExistsSync,
|
||||
},
|
||||
existsSync: mocks.mockExistsSync,
|
||||
};
|
||||
});
|
||||
|
||||
describe("debug", () => {
|
||||
it("test", async () => {
|
||||
const mockGetRootDir = vi.fn();
|
||||
const mockStore = {
|
||||
getRootDir: mockGetRootDir,
|
||||
} as unknown as TaskStore;
|
||||
|
||||
mockGetRootDir.mockReturnValue("/test/project");
|
||||
|
||||
mocks.mockStat
|
||||
.mockRejectedValueOnce({ code: "ENOENT" }) // File doesn't exist
|
||||
.mockResolvedValueOnce({
|
||||
isDirectory: () => false,
|
||||
isFile: () => true,
|
||||
}); // Parent is a file
|
||||
|
||||
console.log("mockStat calls before:", mocks.mockStat.mock.calls);
|
||||
|
||||
try {
|
||||
await writeProjectFile(mockStore, "file.txt/sub.txt", "content");
|
||||
} catch (e) {
|
||||
console.log("Error:", e);
|
||||
console.log("mockStat calls after:", mocks.mockStat.mock.calls);
|
||||
}
|
||||
});
|
||||
});
|
||||
68
packages/dashboard/src/__tests__/debug3.test.ts
Normal file
68
packages/dashboard/src/__tests__/debug3.test.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { writeProjectFile } from "../file-service";
|
||||
import type { TaskStore } from "@fusion/core";
|
||||
|
||||
// Create mock functions that can be configured in tests
|
||||
const mocks = vi.hoisted(() => ({
|
||||
mockReaddir: vi.fn(),
|
||||
mockReadFile: vi.fn(),
|
||||
mockWriteFile: vi.fn(),
|
||||
mockStat: vi.fn(),
|
||||
mockExistsSync: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("node:fs/promises", () => {
|
||||
return {
|
||||
default: {
|
||||
readdir: mocks.mockReaddir,
|
||||
readFile: mocks.mockReadFile,
|
||||
writeFile: mocks.mockWriteFile,
|
||||
stat: mocks.mockStat,
|
||||
},
|
||||
readdir: mocks.mockReaddir,
|
||||
readFile: mocks.mockReadFile,
|
||||
writeFile: mocks.mockWriteFile,
|
||||
stat: mocks.mockStat,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("node:fs", () => {
|
||||
return {
|
||||
default: {
|
||||
existsSync: mocks.mockExistsSync,
|
||||
},
|
||||
existsSync: mocks.mockExistsSync,
|
||||
};
|
||||
});
|
||||
|
||||
describe("debug", () => {
|
||||
it("test", async () => {
|
||||
const mockGetRootDir = vi.fn();
|
||||
const mockStore = {
|
||||
getRootDir: mockGetRootDir,
|
||||
} as unknown as TaskStore;
|
||||
|
||||
mockGetRootDir.mockReturnValue("/test/project");
|
||||
|
||||
// Setup mock to return specific values for each call
|
||||
mocks.mockStat.mockImplementation((path: string) => {
|
||||
console.log("stat called with:", path);
|
||||
if (path === "/test/project/file.txt/sub.txt") {
|
||||
return Promise.reject({ code: "ENOENT" });
|
||||
}
|
||||
if (path === "/test/project/file.txt") {
|
||||
return Promise.resolve({
|
||||
isDirectory: () => false,
|
||||
isFile: () => true,
|
||||
});
|
||||
}
|
||||
return Promise.reject({ code: "ENOENT" });
|
||||
});
|
||||
|
||||
try {
|
||||
await writeProjectFile(mockStore, "file.txt/sub.txt", "content");
|
||||
} catch (e) {
|
||||
console.log("Error:", e);
|
||||
}
|
||||
});
|
||||
});
|
||||
78
packages/dashboard/src/__tests__/debug4.test.ts
Normal file
78
packages/dashboard/src/__tests__/debug4.test.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import type { TaskStore } from "@fusion/core";
|
||||
|
||||
// Create mock functions that can be configured in tests
|
||||
const mocks = vi.hoisted(() => ({
|
||||
mockReaddir: vi.fn(),
|
||||
mockReadFile: vi.fn(),
|
||||
mockWriteFile: vi.fn(),
|
||||
mockStat: vi.fn(),
|
||||
mockExistsSync: vi.fn(),
|
||||
}));
|
||||
|
||||
// Hoist mocks alongside vi.mock
|
||||
vi.mock("node:fs/promises", async () => {
|
||||
const actual = await import("node:fs/promises");
|
||||
return {
|
||||
default: {
|
||||
readdir: mocks.mockReaddir,
|
||||
readFile: mocks.mockReadFile,
|
||||
writeFile: mocks.mockWriteFile,
|
||||
stat: mocks.mockStat,
|
||||
},
|
||||
readdir: mocks.mockReaddir,
|
||||
readFile: mocks.mockReadFile,
|
||||
writeFile: mocks.mockWriteFile,
|
||||
stat: mocks.mockStat,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("node:fs", async () => {
|
||||
const actual = await import("node:fs");
|
||||
return {
|
||||
default: {
|
||||
existsSync: mocks.mockExistsSync,
|
||||
},
|
||||
existsSync: mocks.mockExistsSync,
|
||||
};
|
||||
});
|
||||
|
||||
// Import AFTER mocks are set up
|
||||
import { writeProjectFile } from "../file-service";
|
||||
|
||||
describe("debug", () => {
|
||||
beforeEach(() => {
|
||||
mocks.mockStat.mockReset();
|
||||
});
|
||||
|
||||
it("test", async () => {
|
||||
const mockGetRootDir = vi.fn();
|
||||
const mockStore = {
|
||||
getRootDir: mockGetRootDir,
|
||||
} as unknown as TaskStore;
|
||||
|
||||
mockGetRootDir.mockReturnValue("/test/project");
|
||||
|
||||
// Setup mock to return specific values for each call
|
||||
mocks.mockStat.mockImplementation((path: string) => {
|
||||
console.log("stat called with:", path);
|
||||
if (path === "/test/project/file.txt/sub.txt") {
|
||||
return Promise.reject({ code: "ENOENT" });
|
||||
}
|
||||
if (path === "/test/project/file.txt") {
|
||||
return Promise.resolve({
|
||||
isDirectory: () => false,
|
||||
isFile: () => true,
|
||||
});
|
||||
}
|
||||
return Promise.reject({ code: "ENOENT" });
|
||||
});
|
||||
|
||||
try {
|
||||
await writeProjectFile(mockStore, "file.txt/sub.txt", "content");
|
||||
console.log("Success!");
|
||||
} catch (e: any) {
|
||||
console.log("Error:", e.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
77
packages/dashboard/src/__tests__/debug5.test.ts
Normal file
77
packages/dashboard/src/__tests__/debug5.test.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import type { TaskStore } from "@fusion/core";
|
||||
|
||||
// Create mock functions that can be configured in tests
|
||||
const mocks = vi.hoisted(() => ({
|
||||
mockReaddir: vi.fn(),
|
||||
mockReadFile: vi.fn(),
|
||||
mockWriteFile: vi.fn(),
|
||||
mockStat: vi.fn(),
|
||||
mockExistsSync: vi.fn(),
|
||||
}));
|
||||
|
||||
// Hoist mocks alongside vi.mock
|
||||
vi.mock("node:fs/promises", async () => {
|
||||
const actual = await import("node:fs/promises");
|
||||
return {
|
||||
default: {
|
||||
readdir: mocks.mockReaddir,
|
||||
readFile: mocks.mockReadFile,
|
||||
writeFile: mocks.mockWriteFile,
|
||||
stat: mocks.mockStat,
|
||||
},
|
||||
readdir: mocks.mockReaddir,
|
||||
readFile: mocks.mockReadFile,
|
||||
writeFile: mocks.mockWriteFile,
|
||||
stat: mocks.mockStat,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("node:fs", async () => {
|
||||
const actual = await import("node:fs");
|
||||
return {
|
||||
default: {
|
||||
existsSync: mocks.mockExistsSync,
|
||||
},
|
||||
existsSync: mocks.mockExistsSync,
|
||||
};
|
||||
});
|
||||
|
||||
// Import AFTER mocks are set up
|
||||
import { writeProjectFile } from "../file-service";
|
||||
|
||||
describe("debug", () => {
|
||||
beforeEach(() => {
|
||||
mocks.mockStat.mockReset();
|
||||
mocks.mockStat.mockImplementation((path: string) => {
|
||||
console.log("stat called with:", path);
|
||||
if (path === "/test/project/file.txt/sub.txt") {
|
||||
return Promise.reject({ code: "ENOENT" });
|
||||
}
|
||||
if (path === "/test/project/file.txt") {
|
||||
return Promise.resolve({
|
||||
isDirectory: () => false,
|
||||
isFile: () => true,
|
||||
});
|
||||
}
|
||||
return Promise.reject({ code: "ENOENT" });
|
||||
});
|
||||
});
|
||||
|
||||
it("test", async () => {
|
||||
const mockGetRootDir = vi.fn();
|
||||
const mockStore = {
|
||||
getRootDir: mockGetRootDir,
|
||||
} as unknown as TaskStore;
|
||||
|
||||
mockGetRootDir.mockReturnValue("/test/project");
|
||||
|
||||
try {
|
||||
await writeProjectFile(mockStore, "file.txt/sub.txt", "content");
|
||||
console.log("Success!");
|
||||
} catch (e: any) {
|
||||
console.log("Error:", e.message);
|
||||
expect(e.message).toContain("Parent is not a directory");
|
||||
}
|
||||
});
|
||||
});
|
||||
74
packages/dashboard/src/__tests__/debug6.test.ts
Normal file
74
packages/dashboard/src/__tests__/debug6.test.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import type { TaskStore } from "@fusion/core";
|
||||
|
||||
// Create mock functions that can be configured in tests
|
||||
const mocks = vi.hoisted(() => ({
|
||||
mockReaddir: vi.fn(),
|
||||
mockReadFile: vi.fn(),
|
||||
mockWriteFile: vi.fn(),
|
||||
mockStat: vi.fn(),
|
||||
mockExistsSync: vi.fn(),
|
||||
}));
|
||||
|
||||
// Hoist mocks alongside vi.mock
|
||||
vi.mock("node:fs/promises", async () => {
|
||||
const actual = await import("node:fs/promises");
|
||||
return {
|
||||
default: {
|
||||
readdir: mocks.mockReaddir,
|
||||
readFile: mocks.mockReadFile,
|
||||
writeFile: mocks.mockWriteFile,
|
||||
stat: mocks.mockStat,
|
||||
},
|
||||
readdir: mocks.mockReaddir,
|
||||
readFile: mocks.mockReadFile,
|
||||
writeFile: mocks.mockWriteFile,
|
||||
stat: mocks.mockStat,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("node:fs", async () => {
|
||||
const actual = await import("node:fs");
|
||||
return {
|
||||
default: {
|
||||
existsSync: mocks.mockExistsSync,
|
||||
},
|
||||
existsSync: mocks.mockExistsSync,
|
||||
};
|
||||
});
|
||||
|
||||
// Import AFTER mocks are set up
|
||||
import { writeProjectFile } from "../file-service";
|
||||
|
||||
describe("debug", () => {
|
||||
beforeEach(() => {
|
||||
mocks.mockStat.mockReset();
|
||||
});
|
||||
|
||||
it("test", async () => {
|
||||
const mockGetRootDir = vi.fn();
|
||||
const mockStore = {
|
||||
getRootDir: mockGetRootDir,
|
||||
} as unknown as TaskStore;
|
||||
|
||||
mockGetRootDir.mockReturnValue("/test/project");
|
||||
|
||||
// Setup mock
|
||||
mocks.mockStat
|
||||
.mockRejectedValueOnce({ code: "ENOENT" }) // First call - file doesn't exist
|
||||
.mockResolvedValueOnce({ // Second call - parent is a file
|
||||
isDirectory: () => false,
|
||||
isFile: () => true,
|
||||
});
|
||||
|
||||
console.log("Before call");
|
||||
|
||||
try {
|
||||
await writeProjectFile(mockStore, "file.txt/sub.txt", "content");
|
||||
console.log("Success!");
|
||||
} catch (e: any) {
|
||||
console.log("Error:", e.message);
|
||||
console.log("mockStat calls:", mocks.mockStat.mock.calls);
|
||||
}
|
||||
});
|
||||
});
|
||||
80
packages/dashboard/src/__tests__/debug7.test.ts
Normal file
80
packages/dashboard/src/__tests__/debug7.test.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import type { TaskStore } from "@fusion/core";
|
||||
|
||||
// Create mock functions that can be configured in tests
|
||||
const mocks = vi.hoisted(() => ({
|
||||
mockReaddir: vi.fn(),
|
||||
mockReadFile: vi.fn(),
|
||||
mockWriteFile: vi.fn(),
|
||||
mockStat: vi.fn(),
|
||||
mockExistsSync: vi.fn(),
|
||||
}));
|
||||
|
||||
// Hoist mocks alongside vi.mock
|
||||
vi.mock("node:fs/promises", async () => {
|
||||
const actual = await import("node:fs/promises");
|
||||
return {
|
||||
default: {
|
||||
readdir: mocks.mockReaddir,
|
||||
readFile: mocks.mockReadFile,
|
||||
writeFile: mocks.mockWriteFile,
|
||||
stat: mocks.mockStat,
|
||||
},
|
||||
readdir: mocks.mockReaddir,
|
||||
readFile: mocks.mockReadFile,
|
||||
writeFile: mocks.mockWriteFile,
|
||||
stat: mocks.mockStat,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("node:fs", async () => {
|
||||
const actual = await import("node:fs");
|
||||
return {
|
||||
default: {
|
||||
existsSync: mocks.mockExistsSync,
|
||||
},
|
||||
existsSync: mocks.mockExistsSync,
|
||||
};
|
||||
});
|
||||
|
||||
// Import AFTER mocks are set up
|
||||
import { writeProjectFile } from "../file-service";
|
||||
|
||||
describe("debug", () => {
|
||||
beforeEach(() => {
|
||||
mocks.mockStat.mockReset();
|
||||
});
|
||||
|
||||
it("test", async () => {
|
||||
const mockGetRootDir = vi.fn();
|
||||
const mockStore = {
|
||||
getRootDir: mockGetRootDir,
|
||||
} as unknown as TaskStore;
|
||||
|
||||
mockGetRootDir.mockReturnValue("/test/project");
|
||||
|
||||
// Setup mock to return specific values
|
||||
mocks.mockStat.mockImplementation(async (path: string) => {
|
||||
console.log("stat called:", path);
|
||||
if (path === "/test/project/file.txt/sub.txt") {
|
||||
throw { code: "ENOENT" };
|
||||
}
|
||||
if (path === "/test/project/file.txt") {
|
||||
const result = {
|
||||
isDirectory: () => false,
|
||||
isFile: () => true,
|
||||
};
|
||||
console.log("Returning for parent:", result);
|
||||
return result;
|
||||
}
|
||||
throw { code: "ENOENT" };
|
||||
});
|
||||
|
||||
try {
|
||||
await writeProjectFile(mockStore, "file.txt/sub.txt", "content");
|
||||
console.log("Success!");
|
||||
} catch (e: any) {
|
||||
console.log("Error:", e.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
93
packages/dashboard/src/__tests__/debug8.test.ts
Normal file
93
packages/dashboard/src/__tests__/debug8.test.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import type { TaskStore } from "@fusion/core";
|
||||
import { join, resolve, dirname } from "path";
|
||||
|
||||
// Create mock functions that can be configured in tests
|
||||
const mocks = vi.hoisted(() => ({
|
||||
mockReaddir: vi.fn(),
|
||||
mockReadFile: vi.fn(),
|
||||
mockWriteFile: vi.fn(),
|
||||
mockStat: vi.fn(),
|
||||
mockExistsSync: vi.fn(),
|
||||
}));
|
||||
|
||||
// Hoist mocks alongside vi.mock
|
||||
vi.mock("node:fs/promises", async () => {
|
||||
const actual = await import("node:fs/promises");
|
||||
return {
|
||||
default: {
|
||||
readdir: mocks.mockReaddir,
|
||||
readFile: mocks.mockReadFile,
|
||||
writeFile: mocks.mockWriteFile,
|
||||
stat: mocks.mockStat,
|
||||
},
|
||||
readdir: mocks.mockReaddir,
|
||||
readFile: mocks.mockReadFile,
|
||||
writeFile: mocks.mockWriteFile,
|
||||
stat: mocks.mockStat,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("node:fs", async () => {
|
||||
const actual = await import("node:fs");
|
||||
return {
|
||||
default: {
|
||||
existsSync: mocks.mockExistsSync,
|
||||
},
|
||||
existsSync: mocks.mockExistsSync,
|
||||
};
|
||||
});
|
||||
|
||||
// Import AFTER mocks are set up
|
||||
import { writeProjectFile } from "../file-service";
|
||||
|
||||
describe("debug", () => {
|
||||
beforeEach(() => {
|
||||
mocks.mockStat.mockReset();
|
||||
});
|
||||
|
||||
it("test", async () => {
|
||||
const mockGetRootDir = vi.fn();
|
||||
const mockStore = {
|
||||
getRootDir: mockGetRootDir,
|
||||
} as unknown as TaskStore;
|
||||
|
||||
mockGetRootDir.mockReturnValue("/test/project");
|
||||
|
||||
// Log what paths will be used
|
||||
const basePath = resolve("/test/project");
|
||||
const filePath = "file.txt/sub.txt";
|
||||
const resolvedPath = resolve(basePath, filePath);
|
||||
const parentDir = dirname(resolvedPath);
|
||||
|
||||
console.log("basePath:", basePath);
|
||||
console.log("filePath:", filePath);
|
||||
console.log("resolvedPath:", resolvedPath);
|
||||
console.log("parentDir:", parentDir);
|
||||
|
||||
// Setup mock to return specific values
|
||||
mocks.mockStat.mockImplementation(async (path: string) => {
|
||||
console.log("stat called:", path);
|
||||
if (path === resolvedPath) {
|
||||
console.log("File path match!");
|
||||
throw { code: "ENOENT" };
|
||||
}
|
||||
if (path === parentDir) {
|
||||
console.log("Parent dir match!");
|
||||
return {
|
||||
isDirectory: () => false,
|
||||
isFile: () => true,
|
||||
};
|
||||
}
|
||||
console.log("No match for:", path);
|
||||
throw { code: "ENOENT" };
|
||||
});
|
||||
|
||||
try {
|
||||
await writeProjectFile(mockStore, filePath, "content");
|
||||
console.log("Success!");
|
||||
} catch (e: any) {
|
||||
console.log("Error:", e.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
93
packages/dashboard/src/__tests__/debug9.test.ts
Normal file
93
packages/dashboard/src/__tests__/debug9.test.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import type { TaskStore } from "@fusion/core";
|
||||
import { dirname, resolve } from "path";
|
||||
|
||||
// Create mock functions that can be configured in tests
|
||||
const mocks = vi.hoisted(() => ({
|
||||
mockReaddir: vi.fn(),
|
||||
mockReadFile: vi.fn(),
|
||||
mockWriteFile: vi.fn(),
|
||||
mockStat: vi.fn(),
|
||||
mockExistsSync: vi.fn(),
|
||||
}));
|
||||
|
||||
// Hoist mocks alongside vi.mock
|
||||
vi.mock("node:fs/promises", async () => {
|
||||
const actual = await import("node:fs/promises");
|
||||
return {
|
||||
default: {
|
||||
readdir: mocks.mockReaddir,
|
||||
readFile: mocks.mockReadFile,
|
||||
writeFile: mocks.mockWriteFile,
|
||||
stat: mocks.mockStat,
|
||||
},
|
||||
readdir: mocks.mockReaddir,
|
||||
readFile: mocks.mockReadFile,
|
||||
writeFile: mocks.mockWriteFile,
|
||||
stat: mocks.mockStat,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("node:fs", async () => {
|
||||
const actual = await import("node:fs");
|
||||
return {
|
||||
default: {
|
||||
existsSync: mocks.mockExistsSync,
|
||||
},
|
||||
existsSync: mocks.mockExistsSync,
|
||||
};
|
||||
});
|
||||
|
||||
// Import AFTER mocks are set up
|
||||
import { writeProjectFile } from "../file-service";
|
||||
|
||||
describe("debug", () => {
|
||||
beforeEach(() => {
|
||||
mocks.mockStat.mockReset();
|
||||
});
|
||||
|
||||
it("test", async () => {
|
||||
const mockGetRootDir = vi.fn();
|
||||
const mockStore = {
|
||||
getRootDir: mockGetRootDir,
|
||||
} as unknown as TaskStore;
|
||||
|
||||
mockGetRootDir.mockReturnValue("/test/project");
|
||||
|
||||
const basePath = resolve("/test/project");
|
||||
const filePath = "file.txt/sub.txt";
|
||||
const resolvedPath = resolve(basePath, filePath);
|
||||
const parentDir = dirname(resolvedPath);
|
||||
|
||||
// Create a proper stats object
|
||||
const fileNotDirStats = {
|
||||
isDirectory: () => false,
|
||||
isFile: () => true,
|
||||
size: 100,
|
||||
mtime: new Date(),
|
||||
};
|
||||
|
||||
// Setup mock - note: use .mockResolvedValue for promises
|
||||
mocks.mockStat.mockImplementation(async (path: string) => {
|
||||
console.log("stat called:", path);
|
||||
if (path === resolvedPath) {
|
||||
console.log("File path - throwing ENOENT");
|
||||
throw Object.assign(new Error("ENOENT"), { code: "ENOENT" });
|
||||
}
|
||||
if (path === parentDir) {
|
||||
console.log("Parent dir - returning file stats");
|
||||
console.log("Returning:", fileNotDirStats);
|
||||
console.log("isDirectory():", fileNotDirStats.isDirectory());
|
||||
return fileNotDirStats;
|
||||
}
|
||||
throw Object.assign(new Error("ENOENT"), { code: "ENOENT" });
|
||||
});
|
||||
|
||||
try {
|
||||
await writeProjectFile(mockStore, filePath, "content");
|
||||
console.log("Success!");
|
||||
} catch (e: any) {
|
||||
console.log("Error:", e.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -14,34 +14,44 @@ import {
|
||||
} from "../file-service.js";
|
||||
import type { TaskStore } from "@fusion/core";
|
||||
|
||||
// Mock node:fs/promises
|
||||
const mockReaddir = vi.fn();
|
||||
const mockReadFile = vi.fn();
|
||||
const mockWriteFile = vi.fn();
|
||||
const mockStat = vi.fn();
|
||||
// Create mock functions that can be configured in tests
|
||||
// Use vi.hoisted to hoist alongside vi.mock
|
||||
const mocks = vi.hoisted(() => ({
|
||||
mockReaddir: vi.fn(),
|
||||
mockReadFile: vi.fn(),
|
||||
mockWriteFile: vi.fn(),
|
||||
mockStat: vi.fn(),
|
||||
mockExistsSync: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("node:fs/promises", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("node:fs/promises")>();
|
||||
// Hoist mocks alongside vi.mock - must include default export
|
||||
vi.mock("node:fs/promises", () => {
|
||||
return {
|
||||
...actual,
|
||||
readdir: (...args: any[]) => mockReaddir(...args),
|
||||
readFile: (...args: any[]) => mockReadFile(...args),
|
||||
writeFile: (...args: any[]) => mockWriteFile(...args),
|
||||
stat: (...args: any[]) => mockStat(...args),
|
||||
default: {
|
||||
readdir: mocks.mockReaddir,
|
||||
readFile: mocks.mockReadFile,
|
||||
writeFile: mocks.mockWriteFile,
|
||||
stat: mocks.mockStat,
|
||||
},
|
||||
readdir: mocks.mockReaddir,
|
||||
readFile: mocks.mockReadFile,
|
||||
writeFile: mocks.mockWriteFile,
|
||||
stat: mocks.mockStat,
|
||||
};
|
||||
});
|
||||
|
||||
// Mock node:fs
|
||||
const mockExistsSync = vi.fn();
|
||||
|
||||
vi.mock("node:fs", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("node:fs")>();
|
||||
vi.mock("node:fs", () => {
|
||||
return {
|
||||
...actual,
|
||||
existsSync: (...args: any[]) => mockExistsSync(...args),
|
||||
default: {
|
||||
existsSync: mocks.mockExistsSync,
|
||||
},
|
||||
existsSync: mocks.mockExistsSync,
|
||||
};
|
||||
});
|
||||
|
||||
// Export mocks for use in tests
|
||||
export const { mockReaddir, mockReadFile, mockWriteFile, mockStat, mockExistsSync } = mocks;
|
||||
|
||||
describe("FileServiceError", () => {
|
||||
it("constructor sets code and name correctly", () => {
|
||||
const error = new FileServiceError("Test message", "ETEST");
|
||||
@@ -227,15 +237,16 @@ describe("path traversal protection", () => {
|
||||
|
||||
it("allows valid relative paths with dots", async () => {
|
||||
mockGetRootDir.mockReturnValue("/project");
|
||||
// Need successful stat for this test to pass validation
|
||||
mockStat.mockResolvedValue({
|
||||
isDirectory: () => true,
|
||||
isFile: () => false,
|
||||
});
|
||||
mockReaddir.mockResolvedValue([]);
|
||||
|
||||
// This should NOT throw
|
||||
await expect(listProjectFiles(mockStore, "./src")).resolves.not.toThrow();
|
||||
// Should resolve ./src to src and succeed
|
||||
const result = await listProjectFiles(mockStore, "./src");
|
||||
expect(result.path).toBe("src");
|
||||
expect(result.entries).toEqual([]);
|
||||
});
|
||||
|
||||
it("allows paths containing single dots in middle", async () => {
|
||||
@@ -246,7 +257,10 @@ describe("path traversal protection", () => {
|
||||
});
|
||||
mockReaddir.mockResolvedValue([]);
|
||||
|
||||
await expect(listProjectFiles(mockStore, "src/./components")).resolves.not.toThrow();
|
||||
// Should resolve src/./components to src/components and succeed
|
||||
const result = await listProjectFiles(mockStore, "src/./components");
|
||||
expect(result.path).toBe("src/components");
|
||||
expect(result.entries).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -360,9 +374,8 @@ describe("writeProjectFile", () => {
|
||||
mockGetRootDir.mockReturnValue("/test/project");
|
||||
mockStat
|
||||
.mockRejectedValueOnce({ code: "ENOENT" }) // File doesn't exist
|
||||
.mockRejectedValueOnce({ code: "ENOENT" }); // Parent doesn't exist
|
||||
.mockRejectedValueOnce({ code: "ENOENT" }); // Parent doesn't exist (this should throw)
|
||||
|
||||
await expect(writeProjectFile(mockStore, "missing/file.txt", "content")).rejects.toThrow(FileServiceError);
|
||||
await expect(writeProjectFile(mockStore, "missing/file.txt", "content")).rejects.toThrow("Parent directory does not exist");
|
||||
});
|
||||
|
||||
@@ -375,7 +388,6 @@ describe("writeProjectFile", () => {
|
||||
isFile: () => true,
|
||||
}); // Parent is a file
|
||||
|
||||
await expect(writeProjectFile(mockStore, "file.txt/sub.txt", "content")).rejects.toThrow(FileServiceError);
|
||||
await expect(writeProjectFile(mockStore, "file.txt/sub.txt", "content")).rejects.toThrow("Parent is not a directory");
|
||||
});
|
||||
|
||||
@@ -455,7 +467,7 @@ describe("task file operations", () => {
|
||||
await readFile(mockStore, "FN-123", "PROMPT.md");
|
||||
|
||||
expect(mockReadFile).toHaveBeenCalledWith(
|
||||
"/project/.fusion/tasks/KB-123/PROMPT.md",
|
||||
"/project/.fusion/tasks/FN-123/PROMPT.md",
|
||||
"utf-8",
|
||||
);
|
||||
});
|
||||
@@ -476,7 +488,7 @@ describe("task file operations", () => {
|
||||
await readFile(mockStore, "FN-123", "PROMPT.md");
|
||||
|
||||
expect(mockReadFile).toHaveBeenCalledWith(
|
||||
"/project/.fusion/tasks/KB-123/PROMPT.md",
|
||||
"/project/.fusion/tasks/FN-123/PROMPT.md",
|
||||
"utf-8",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1228,19 +1228,60 @@ async function fetchZaiUsage(): Promise<ProviderUsage> {
|
||||
* Fetch usage data from all configured providers with caching.
|
||||
* Results are cached for 30 seconds to avoid hitting provider API rate limits.
|
||||
*/
|
||||
/** Max time to wait for any individual provider fetch (ms) */
|
||||
const PROVIDER_FETCH_TIMEOUT_MS = 10_000; // 10 seconds
|
||||
|
||||
/**
|
||||
* Wrap a provider fetch with a timeout. Returns the provider result or an
|
||||
* error provider if the fetch takes longer than PROVIDER_FETCH_TIMEOUT_MS.
|
||||
*/
|
||||
function withTimeout(
|
||||
providerPromise: Promise<ProviderUsage>,
|
||||
providerName: string,
|
||||
timeoutMs: number = PROVIDER_FETCH_TIMEOUT_MS,
|
||||
): Promise<ProviderUsage> {
|
||||
return new Promise((resolve) => {
|
||||
const timer = setTimeout(() => {
|
||||
resolve({
|
||||
name: providerName,
|
||||
icon: "⏱️",
|
||||
status: "error",
|
||||
error: "Timed out",
|
||||
windows: [],
|
||||
});
|
||||
}, timeoutMs);
|
||||
|
||||
providerPromise
|
||||
.then((result) => {
|
||||
clearTimeout(timer);
|
||||
resolve(result);
|
||||
})
|
||||
.catch((err: any) => {
|
||||
clearTimeout(timer);
|
||||
resolve({
|
||||
name: providerName,
|
||||
icon: "⏱️",
|
||||
status: "error",
|
||||
error: err.message || "Failed",
|
||||
windows: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchAllProviderUsage(_authStorage?: AuthStorageLike): Promise<ProviderUsage[]> {
|
||||
// Check cache
|
||||
if (usageCache && Date.now() - usageCache.timestamp < CACHE_TTL_MS) {
|
||||
return usageCache.data;
|
||||
}
|
||||
|
||||
// Fetch all providers in parallel
|
||||
// Fetch all providers in parallel with per-provider timeout
|
||||
const results = await Promise.allSettled([
|
||||
fetchClaudeUsage(),
|
||||
fetchCodexUsage(),
|
||||
fetchGeminiUsage(),
|
||||
fetchMinimaxUsage(),
|
||||
fetchZaiUsage(),
|
||||
withTimeout(fetchClaudeUsage(), "Claude"),
|
||||
withTimeout(fetchCodexUsage(), "Codex"),
|
||||
withTimeout(fetchGeminiUsage(), "Gemini"),
|
||||
withTimeout(fetchMinimaxUsage(), "Minimax"),
|
||||
withTimeout(fetchZaiUsage(), "Zai"),
|
||||
]);
|
||||
|
||||
const providers: ProviderUsage[] = [];
|
||||
|
||||
Reference in New Issue
Block a user