forked from CopilotKit/CopilotKit
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlifecycle.test.ts
More file actions
94 lines (81 loc) · 3.5 KB
/
Copy pathlifecycle.test.ts
File metadata and controls
94 lines (81 loc) · 3.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
import { describe, it, expect, vi, beforeEach } from "vitest";
// ---------------------------------------------------------------------------
// Hoisted mocks — vi.hoisted() runs before vi.mock factories so the spies
// are available inside the factory closures. We capture every
// `docker compose ...` invocation by recording execFileSync calls.
// ---------------------------------------------------------------------------
const { execFileSyncMock, execSyncMock, existsSyncMock } = vi.hoisted(() => ({
execFileSyncMock: vi.fn(),
execSyncMock: vi.fn(),
existsSyncMock: vi.fn(),
}));
vi.mock("node:child_process", () => ({
execFileSync: (...args: unknown[]) => execFileSyncMock(...args),
execSync: (...args: unknown[]) => execSyncMock(...args),
spawn: vi.fn(),
}));
vi.mock("node:fs", () => {
// stageSharedModules() short-circuits when INTEGRATIONS_DIR is absent.
const api = {
existsSync: (...args: unknown[]) => existsSyncMock(...args),
readdirSync: vi.fn(() => []),
readFileSync: vi.fn(() => "{}"),
};
return { default: api, ...api };
});
import { rebuild } from "./lifecycle.js";
/** Pull out the compose argv (after the `-f <file>` prefix) for each call. */
function composeCalls(): string[][] {
return execFileSyncMock.mock.calls
.filter((c) => c[0] === "docker")
.map((c) => c[1] as string[])
.filter((argv) => argv[0] === "compose")
.map((argv) => argv.slice(3)); // drop ["compose", "-f", "<file>"]
}
beforeEach(() => {
execFileSyncMock.mockReset();
execSyncMock.mockReset();
existsSyncMock.mockReset();
// No integrations dir → stageSharedModules() is a no-op.
existsSyncMock.mockReturnValue(false);
// Default: compose returns empty string.
execFileSyncMock.mockReturnValue("");
});
describe("rebuild() — targeted slugs", () => {
it("includes the infra profile in the build invocation so aimock resolves", async () => {
await rebuild(["langgraph-python"]);
const buildCall = composeCalls().find((argv) => argv.includes("build"));
expect(buildCall).toBeDefined();
// infra profile must be present alongside the slug profile
expect(buildCall).toContain("--profile");
expect(buildCall).toContain("infra");
expect(buildCall).toContain("langgraph-python");
// sanity: --profile infra appears before build
const infraIdx = buildCall!.indexOf("infra");
const buildIdx = buildCall!.indexOf("build");
expect(infraIdx).toBeLessThan(buildIdx);
});
it("force-recreates the targeted service so a stale container is replaced", async () => {
await rebuild(["langgraph-python"]);
const recreateCall = composeCalls().find(
(argv) => argv.includes("up") && argv.includes("--force-recreate"),
);
expect(recreateCall).toBeDefined();
expect(recreateCall).toContain("infra"); // infra profile included here too
expect(recreateCall).toContain("langgraph-python");
expect(recreateCall).toContain("-d");
});
it("recreates EVERY targeted slug regardless of prior running state", async () => {
// isRunning is no longer consulted for the targeted path — force-recreate
// is unconditional. Verify both slugs get rebuilt + recreated.
await rebuild(["a", "b"]);
const buildCall = composeCalls().find((argv) => argv.includes("build"));
expect(buildCall).toContain("a");
expect(buildCall).toContain("b");
const recreateCall = composeCalls().find((argv) =>
argv.includes("--force-recreate"),
);
expect(recreateCall).toContain("a");
expect(recreateCall).toContain("b");
});
});