* fix(cli): separate unit tests from integration tests and fix test cleanup - Rename 14 non-backend test files to *_unit.test.ts convention - Add UNIT_ONLY env var guard in setup.ts to skip cargo build/backend startup - Add test:unit and test:integration scripts to package.json - Use setsid on Linux for process group management so stop() kills both cargo and the windmill child process - Fix exit handler to kill process group instead of just the direct child - Add cleanupStaleTestResources() to drop orphaned windmill_test_* databases and kill orphaned backend processes on startup - Rewrite TESTING.md with current bun-based instructions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(cli): fix process group approach - kill by db name instead of setsid The setsid approach didn't work because setsid forks, making the PID we get from Bun.spawn ephemeral. Instead, kill orphaned windmill child processes by matching our unique database name in /proc/pid/environ. Also add afterAll hook in setup.ts so full async cleanup (process kill + database drop) runs when all tests complete normally, not just on SIGINT/SIGTERM. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(cli): address PR review feedback - Remove duplicate cleanupStaleTestResources() call in getTestBackend() (already called in setup.ts) - Add regex guard on database names before SQL interpolation - Extract shared killWindmillProcessesByEnvMatch() helper to deduplicate process-killing logic - Remove redundant test:integration script (test already runs everything) - Flip setup.ts to if/else pattern for readability Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
200 lines
6.8 KiB
TypeScript
200 lines
6.8 KiB
TypeScript
import { expect, test } from "bun:test";
|
|
import { addWorkspace, allWorkspaces } from "../workspace.ts";
|
|
import { withTestConfig, clearTestRemotes } from "./test_config_helpers.ts";
|
|
|
|
// Test workspace conflict detection
|
|
test("addWorkspace: prevents duplicate workspace names", async () => {
|
|
await withTestConfig(async (testConfigDir) => {
|
|
await clearTestRemotes(testConfigDir);
|
|
|
|
// Add first workspace
|
|
const workspace1 = {
|
|
name: "test_workspace",
|
|
remote: "http://localhost:8001/",
|
|
workspaceId: "workspace1",
|
|
token: "token1"
|
|
};
|
|
|
|
await addWorkspace(workspace1, { force: true, configDir: testConfigDir });
|
|
|
|
// Try to add workspace with same name but different details
|
|
const workspace2 = {
|
|
name: "test_workspace", // Same name
|
|
remote: "http://localhost:8002/", // Different remote
|
|
workspaceId: "workspace2", // Different ID
|
|
token: "token2"
|
|
};
|
|
|
|
// Force non-interactive mode so addWorkspace throws instead of prompting
|
|
const origStdinTTY = process.stdin.isTTY;
|
|
const origStdoutTTY = process.stdout.isTTY;
|
|
try {
|
|
process.stdin.isTTY = false as any;
|
|
process.stdout.isTTY = false as any;
|
|
|
|
// Should throw error in non-interactive mode without force
|
|
await expect(
|
|
addWorkspace(workspace2, { configDir: testConfigDir })
|
|
).rejects.toThrow("Workspace name conflict. Use --force to overwrite or choose a different name.");
|
|
} finally {
|
|
process.stdin.isTTY = origStdinTTY;
|
|
process.stdout.isTTY = origStdoutTTY;
|
|
}
|
|
|
|
// Should succeed with force flag
|
|
await addWorkspace(workspace2, { force: true, configDir: testConfigDir });
|
|
|
|
// Verify the workspace was overwritten
|
|
const workspaces = await allWorkspaces(testConfigDir);
|
|
expect(workspaces.length).toEqual(1);
|
|
expect(workspaces[0].name).toEqual("test_workspace");
|
|
expect(workspaces[0].remote).toEqual("http://localhost:8002/");
|
|
expect(workspaces[0].workspaceId).toEqual("workspace2");
|
|
});
|
|
});
|
|
|
|
test("addWorkspace: prevents duplicate (remote, workspaceId) tuples", async () => {
|
|
await withTestConfig(async (testConfigDir) => {
|
|
await clearTestRemotes(testConfigDir);
|
|
|
|
// Add first workspace
|
|
const workspace1 = {
|
|
name: "first_workspace",
|
|
remote: "http://localhost:8001/",
|
|
workspaceId: "test",
|
|
token: "token1"
|
|
};
|
|
|
|
await addWorkspace(workspace1, { force: true, configDir: testConfigDir });
|
|
|
|
// Try to add workspace with same (remote, workspaceId) but different name
|
|
const workspace2 = {
|
|
name: "second_workspace", // Different name
|
|
remote: "http://localhost:8001/", // Same remote
|
|
workspaceId: "test", // Same workspaceId
|
|
token: "token2"
|
|
};
|
|
|
|
// Should throw error in non-interactive mode without force
|
|
await expect(
|
|
addWorkspace(workspace2, { configDir: testConfigDir })
|
|
).rejects.toThrow('Backend constraint violation: (http://localhost:8001/, test) already exists as "first_workspace". Use --force to overwrite.');
|
|
|
|
// Should succeed with force flag (overwrites first workspace)
|
|
await addWorkspace(workspace2, { force: true, configDir: testConfigDir });
|
|
|
|
// Verify the first workspace was removed and second was added
|
|
const workspaces = await allWorkspaces(testConfigDir);
|
|
expect(workspaces.length).toEqual(1);
|
|
expect(workspaces[0].name).toEqual("second_workspace");
|
|
expect(workspaces[0].remote).toEqual("http://localhost:8001/");
|
|
expect(workspaces[0].workspaceId).toEqual("test");
|
|
});
|
|
});
|
|
|
|
test("addWorkspace: allows same workspace (name, remote, workspaceId) with token update", async () => {
|
|
await withTestConfig(async (testConfigDir) => {
|
|
await clearTestRemotes(testConfigDir);
|
|
|
|
// Add first workspace
|
|
const workspace1 = {
|
|
name: "same_workspace",
|
|
remote: "http://localhost:8001/",
|
|
workspaceId: "test",
|
|
token: "old_token"
|
|
};
|
|
|
|
await addWorkspace(workspace1, { force: true, configDir: testConfigDir });
|
|
|
|
// Add same workspace with updated token
|
|
const workspace2 = {
|
|
name: "same_workspace", // Same name
|
|
remote: "http://localhost:8001/", // Same remote
|
|
workspaceId: "test", // Same workspaceId
|
|
token: "new_token" // Different token
|
|
};
|
|
|
|
// Should succeed without force (just token update)
|
|
await addWorkspace(workspace2, { configDir: testConfigDir });
|
|
|
|
// Verify token was updated
|
|
const workspaces = await allWorkspaces(testConfigDir);
|
|
expect(workspaces.length).toEqual(1);
|
|
expect(workspaces[0].name).toEqual("same_workspace");
|
|
expect(workspaces[0].token).toEqual("new_token");
|
|
});
|
|
});
|
|
|
|
test("addWorkspace: returns true on successful add", async () => {
|
|
await withTestConfig(async (testConfigDir) => {
|
|
await clearTestRemotes(testConfigDir);
|
|
|
|
const workspace = {
|
|
name: "return_test",
|
|
remote: "http://localhost:8001/",
|
|
workspaceId: "test",
|
|
token: "token1"
|
|
};
|
|
|
|
const result = await addWorkspace(workspace, { force: true, configDir: testConfigDir });
|
|
expect(result).toEqual(true);
|
|
});
|
|
});
|
|
|
|
test("addWorkspace: returns true when force-overwriting conflict", async () => {
|
|
await withTestConfig(async (testConfigDir) => {
|
|
await clearTestRemotes(testConfigDir);
|
|
|
|
const workspace1 = {
|
|
name: "force_test",
|
|
remote: "http://localhost:8001/",
|
|
workspaceId: "workspace1",
|
|
token: "token1"
|
|
};
|
|
await addWorkspace(workspace1, { force: true, configDir: testConfigDir });
|
|
|
|
const workspace2 = {
|
|
name: "force_test",
|
|
remote: "http://localhost:8002/",
|
|
workspaceId: "workspace2",
|
|
token: "token2"
|
|
};
|
|
const result = await addWorkspace(workspace2, { force: true, configDir: testConfigDir });
|
|
expect(result).toEqual(true);
|
|
});
|
|
});
|
|
|
|
test("addWorkspace: allows different workspaces on different remotes", async () => {
|
|
await withTestConfig(async (testConfigDir) => {
|
|
await clearTestRemotes(testConfigDir);
|
|
|
|
// Add workspace on first remote
|
|
const workspace1 = {
|
|
name: "workspace_remote1",
|
|
remote: "http://localhost:8001/",
|
|
workspaceId: "test",
|
|
token: "token1"
|
|
};
|
|
|
|
await addWorkspace(workspace1, { force: true, configDir: testConfigDir });
|
|
|
|
// Add workspace with same workspaceId on different remote (should be allowed)
|
|
const workspace2 = {
|
|
name: "workspace_remote2",
|
|
remote: "http://localhost:8002/", // Different remote
|
|
workspaceId: "test", // Same workspaceId (OK on different remote)
|
|
token: "token2"
|
|
};
|
|
|
|
// Should succeed (different remotes)
|
|
await addWorkspace(workspace2, { configDir: testConfigDir });
|
|
|
|
// Verify both workspaces exist
|
|
const workspaces = await allWorkspaces(testConfigDir);
|
|
expect(workspaces.length).toEqual(2);
|
|
|
|
const names = workspaces.map(w => w.name).sort();
|
|
expect(names).toEqual(["workspace_remote1", "workspace_remote2"]);
|
|
});
|
|
});
|