Skip to content
Merged
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
108 changes: 6 additions & 102 deletions apps/desktop/renderer/src/lib/ai-api-config.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { mkdir, mkdtemp, readFile, rm, writeFile } from "fs/promises";
import { mkdtemp, rm } from "fs/promises";
import { tmpdir } from "os";
import { join } from "path";
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";

let configDir = "";
let testHomeDir = "";
let originalUserDataDir;
let originalHome;
Expand All @@ -15,25 +14,14 @@ async function loadConfigModule() {
return import("../../../../../packages/core-models/config");
}

function getTestAppDataDir(homeDir) {
if (process.platform === "darwin") {
return join(homeDir, "Library", "Application Support", "dev-Workflow");
}
if (process.platform === "win32") {
return join(homeDir, "AppData", "Roaming", "dev-Workflow");
}
return join(process.env.XDG_CONFIG_HOME || join(homeDir, ".config"), "dev-Workflow");
}

describe("AI API config", () => {
beforeEach(async () => {
originalUserDataDir = process.env.DEV_WORKFLOW_USER_DATA_DIR;
originalHome = process.env.HOME;
originalAppData = process.env.APPDATA;
originalXdgConfigHome = process.env.XDG_CONFIG_HOME;
configDir = await mkdtemp(join(tmpdir(), "dev-workflow-config-"));
testHomeDir = await mkdtemp(join(tmpdir(), "dev-workflow-home-"));
process.env.DEV_WORKFLOW_USER_DATA_DIR = configDir;
delete process.env.DEV_WORKFLOW_USER_DATA_DIR;
process.env.HOME = testHomeDir;
process.env.APPDATA = join(testHomeDir, "AppData", "Roaming");
process.env.XDG_CONFIG_HOME = join(testHomeDir, ".config");
Expand All @@ -60,9 +48,7 @@ describe("AI API config", () => {
} else {
process.env.XDG_CONFIG_HOME = originalXdgConfigHome;
}
if (configDir) await rm(configDir, { recursive: true, force: true });
if (testHomeDir) await rm(testHomeDir, { recursive: true, force: true });
configDir = "";
testHomeDir = "";
});

Expand Down Expand Up @@ -131,92 +117,10 @@ describe("AI API config", () => {
expect(await readAiApiProfiles()).toHaveLength(1);
});

test("merges system AI API profiles with user overrides", async () => {
const homeDir = await mkdtemp(join(tmpdir(), "dev-workflow-home-"));
process.env.HOME = homeDir;
process.env.APPDATA = join(homeDir, "AppData", "Roaming");
delete process.env.DEV_WORKFLOW_USER_DATA_DIR;
const appDataDir = getTestAppDataDir(homeDir);
await mkdir(appDataDir, { recursive: true });
await writeFile(
join(appDataDir, "config.json"),
JSON.stringify({
aiBackendOverride: "codex",
aiApiProfiles: [
{
id: "deepseek",
name: "deepseek",
baseUrl: "https://api.deepseek.com",
apiKey: "sk-deepseek",
model: "deepseek-v4-flash",
},
],
}, null, 2)
);

const { CONFIG_FILE, readAiApiProfilesForUi, readAiBackendOverride, saveAiApiProfile } = await loadConfigModule();

expect(await readAiBackendOverride()).toBe("codex");
expect(await readAiApiProfilesForUi()).toEqual([
{
id: "deepseek",
name: "deepseek",
baseUrl: "https://api.deepseek.com",
model: "deepseek-v4-flash",
hasApiKey: true,
},
]);

await saveAiApiProfile({
id: "deepseek",
name: "deepseek",
baseUrl: "https://gateway.example/v1",
apiKey: "",
model: "deepseek-chat",
});

expect(await readAiApiProfilesForUi()).toEqual([
{
id: "deepseek",
name: "deepseek",
baseUrl: "https://gateway.example/v1",
model: "deepseek-chat",
hasApiKey: true,
},
]);
expect(JSON.parse(await readFile(CONFIG_FILE, "utf-8")).aiApiProfiles).toHaveLength(1);
test("uses one shared config file for user and system settings", async () => {
const { CONFIG_FILE, SYSTEM_CONFIG_FILE, getDefaultDesktopUserDataDir, getSystemDesktopUserDataDir } = await loadConfigModule();

await rm(homeDir, { recursive: true, force: true });
});

test("stores a user deletion marker for system AI API profiles", async () => {
const homeDir = await mkdtemp(join(tmpdir(), "dev-workflow-home-"));
process.env.HOME = homeDir;
process.env.APPDATA = join(homeDir, "AppData", "Roaming");
delete process.env.DEV_WORKFLOW_USER_DATA_DIR;
const appDataDir = getTestAppDataDir(homeDir);
await mkdir(appDataDir, { recursive: true });
await writeFile(
join(appDataDir, "config.json"),
JSON.stringify({
aiApiProfiles: [
{
id: "deepseek",
name: "deepseek",
baseUrl: "https://api.deepseek.com",
apiKey: "sk-deepseek",
model: "deepseek-v4-flash",
},
],
}, null, 2)
);

const { CONFIG_FILE, deleteAiApiProfile, readAiApiProfilesForUi } = await loadConfigModule();

expect(await deleteAiApiProfile("deepseek")).toEqual([]);
expect(await readAiApiProfilesForUi()).toEqual([]);
expect(JSON.parse(await readFile(CONFIG_FILE, "utf-8")).deletedAiApiProfileIds).toEqual(["deepseek"]);

await rm(homeDir, { recursive: true, force: true });
expect(getSystemDesktopUserDataDir()).toBe(getDefaultDesktopUserDataDir());
expect(SYSTEM_CONFIG_FILE).toBe(CONFIG_FILE);
});
});
21 changes: 8 additions & 13 deletions apps/desktop/renderer/src/lib/skills-storage.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { mkdir, mkdtemp, readFile, rm, stat, writeFile } from "fs/promises";
import { mkdir, mkdtemp, rm, stat, writeFile } from "fs/promises";
import { tmpdir } from "os";
import { join } from "path";
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";

let userDataDir = "";
let homeDir = "";
let originalUserDataDir;
let originalHome;
Expand Down Expand Up @@ -40,9 +39,8 @@ describe("managed skills storage", () => {
originalHome = process.env.HOME;
originalAppData = process.env.APPDATA;
originalXdgConfigHome = process.env.XDG_CONFIG_HOME;
userDataDir = await mkdtemp(join(tmpdir(), "dev-workflow-user-data-"));
homeDir = await mkdtemp(join(tmpdir(), "dev-workflow-home-"));
process.env.DEV_WORKFLOW_USER_DATA_DIR = userDataDir;
delete process.env.DEV_WORKFLOW_USER_DATA_DIR;
process.env.HOME = homeDir;
process.env.APPDATA = join(homeDir, "AppData", "Roaming");
process.env.XDG_CONFIG_HOME = join(homeDir, ".config");
Expand All @@ -69,15 +67,14 @@ describe("managed skills storage", () => {
} else {
process.env.XDG_CONFIG_HOME = originalXdgConfigHome;
}
if (userDataDir) await rm(userDataDir, { recursive: true, force: true });
if (homeDir) await rm(homeDir, { recursive: true, force: true });
userDataDir = "";
homeDir = "";
});

test("merges system skills with user overrides", async () => {
test("reads managed skills from the shared storage directory", async () => {
const { config, skills } = await loadModules();
await writeSkill(config.getSystemSkillsDir(), "review", "review", "System review", "system body");
expect(config.getSystemSkillsDir()).toBe(config.getSkillsDir());

await writeSkill(config.getSkillsDir(), "review", "review", "User review", "user body");
await writeSkill(config.getSkillsDir(), "local", "local", "Local only", "local body");

Expand All @@ -90,15 +87,14 @@ describe("managed skills storage", () => {
expect(skills.readManagedSkillContentSync("review")).toContain("user body");
});

test("stores user deletion markers for system skills", async () => {
test("deletes managed skills directly from the shared storage directory", async () => {
const { config, skills } = await loadModules();
await writeSkill(config.getSystemSkillsDir(), "review", "review", "System review", "system body");
await writeSkill(config.getSkillsDir(), "review", "review", "System review", "system body");

await skills.deleteManagedSkill("review");

expect((await skills.listManagedSkills()).some((skill) => skill.slug === "review")).toBe(false);
await expect(stat(join(config.getSystemSkillsDir(), "review", "SKILL.md"))).resolves.toBeTruthy();
expect(await readFile(join(config.getSkillsDir(), "review", ".deleted"), "utf-8")).toBe("");
await expect(stat(join(config.getSkillsDir(), "review", "SKILL.md"))).rejects.toBeTruthy();

await skills.saveManagedSkill({
name: "review",
Expand All @@ -107,6 +103,5 @@ describe("managed skills storage", () => {
});

expect(skills.readManagedSkillContentSync("review")).toContain("restored body");
await expect(stat(join(config.getSkillsDir(), "review", ".deleted"))).rejects.toBeTruthy();
});
});
14 changes: 5 additions & 9 deletions apps/desktop/renderer/src/lib/workflow-storage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { tmpdir } from "os";
import { join } from "path";
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";

let userDataDir = "";
let homeDir = "";
let originalUserDataDir;
let originalHome;
Expand Down Expand Up @@ -49,9 +48,8 @@ describe("workflow storage", () => {
originalHome = process.env.HOME;
originalAppData = process.env.APPDATA;
originalXdgConfigHome = process.env.XDG_CONFIG_HOME;
userDataDir = await mkdtemp(join(tmpdir(), "dev-workflow-user-data-"));
homeDir = await mkdtemp(join(tmpdir(), "dev-workflow-home-"));
process.env.DEV_WORKFLOW_USER_DATA_DIR = userDataDir;
delete process.env.DEV_WORKFLOW_USER_DATA_DIR;
process.env.HOME = homeDir;
process.env.APPDATA = join(homeDir, "AppData", "Roaming");
process.env.XDG_CONFIG_HOME = join(homeDir, ".config");
Expand All @@ -78,20 +76,18 @@ describe("workflow storage", () => {
} else {
process.env.XDG_CONFIG_HOME = originalXdgConfigHome;
}
if (userDataDir) await rm(userDataDir, { recursive: true, force: true });
if (homeDir) await rm(homeDir, { recursive: true, force: true });
userDataDir = "";
homeDir = "";
});

test("uses user workflows before system workflows", async () => {
test("uses the shared workflow directory", async () => {
const { config, workflow } = await loadModules();
await writeWorkflow(config.getSystemWorkflowDir(), "default.json", "System Default");

expect(config.getSystemWorkflowDir()).toBe(config.getWorkflowDir());

await writeWorkflow(config.getWorkflowDir(), "default.json", "User Default");
await writeWorkflow(config.getSystemWorkflowDir(), "system-only.json", "System Only");

expect(workflow.getWorkflowFilePath("default.json")).toBe(join(config.getWorkflowDir(), "default.json"));
expect(workflow.getWorkflowFilePath("system-only.json")).toBe(join(config.getSystemWorkflowDir(), "system-only.json"));
expect(workflow.readWorkflowFileSync("default.json").name).toBe("User Default");
});
});
30 changes: 8 additions & 22 deletions apps/desktop/renderer/src/lib/workfolders-storage.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { mkdir, mkdtemp, readFile, rm, writeFile } from "fs/promises";
import { mkdir, mkdtemp, rm, writeFile } from "fs/promises";
import { tmpdir } from "os";
import { dirname, join } from "path";
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";

let userDataDir = "";
let homeDir = "";
let originalUserDataDir;
let originalHome;
Expand All @@ -28,9 +27,8 @@ describe("workfolders storage", () => {
originalHome = process.env.HOME;
originalAppData = process.env.APPDATA;
originalXdgConfigHome = process.env.XDG_CONFIG_HOME;
userDataDir = await mkdtemp(join(tmpdir(), "dev-workflow-user-data-"));
homeDir = await mkdtemp(join(tmpdir(), "dev-workflow-home-"));
process.env.DEV_WORKFLOW_USER_DATA_DIR = userDataDir;
delete process.env.DEV_WORKFLOW_USER_DATA_DIR;
process.env.HOME = homeDir;
process.env.APPDATA = join(homeDir, "AppData", "Roaming");
process.env.XDG_CONFIG_HOME = join(homeDir, ".config");
Expand All @@ -57,44 +55,36 @@ describe("workfolders storage", () => {
} else {
process.env.XDG_CONFIG_HOME = originalXdgConfigHome;
}
if (userDataDir) await rm(userDataDir, { recursive: true, force: true });
if (homeDir) await rm(homeDir, { recursive: true, force: true });
userDataDir = "";
homeDir = "";
});

test("merges system workfolders with user folders", async () => {
test("reads workfolders from the shared storage directory", async () => {
const { config, workfolders } = await loadModules();
const userBaseDir = await config.getBaseDir();
const systemBaseDir = config.getSystemBaseDir();
await writeWorkfolders(config.getWorkfoldersFile(systemBaseDir), [
{ name: "System", path: "/tmp/system", tasks: [{ taskId: "system-task", status: "running" }] },
{ name: "Shared", path: "/tmp/shared", tasks: [] },
]);

expect(systemBaseDir).toBe(userBaseDir);

await writeWorkfolders(config.getWorkfoldersFile(userBaseDir), [
{ name: "Shared User", path: "/tmp/shared", tasks: [{ taskId: "user-task", status: "done" }] },
{ name: "User", path: "/tmp/user", tasks: [] },
]);

expect(await workfolders.readWorkfolders()).toEqual([
{ name: "System", path: "/tmp/system", tasks: [] },
{ name: "Shared User", path: "/tmp/shared", tasks: [{ taskId: "user-task", runId: "user-task", status: "done" }] },
{ name: "User", path: "/tmp/user", tasks: [] },
]);
});

test("stores user deletion markers for system workfolders", async () => {
test("removes workfolders directly from the shared storage file", async () => {
const { config, workfolders } = await loadModules();
const userBaseDir = await config.getBaseDir();
const systemBaseDir = config.getSystemBaseDir();
await writeWorkfolders(config.getWorkfoldersFile(systemBaseDir), [
await writeWorkfolders(config.getWorkfoldersFile(userBaseDir), [
{ name: "System", path: "/tmp/system", tasks: [] },
]);

expect(await workfolders.removeWorkfolder("/tmp/system")).toEqual([]);
expect(JSON.parse(await readFile(config.getWorkfoldersFile(userBaseDir), "utf-8"))).toEqual([
{ name: "System", path: "/tmp/system", tasks: [], deleted: true },
]);

await workfolders.saveWorkfolders([
{ name: "User", path: "/tmp/user", tasks: [] },
Expand All @@ -103,9 +93,5 @@ describe("workfolders storage", () => {
expect(await workfolders.readWorkfolders()).toEqual([
{ name: "User", path: "/tmp/user", tasks: [] },
]);
expect(JSON.parse(await readFile(config.getWorkfoldersFile(userBaseDir), "utf-8"))).toEqual([
{ name: "System", path: "/tmp/system", tasks: [], deleted: true },
{ name: "User", path: "/tmp/user", tasks: [] },
]);
});
});
Loading
Loading