Skip to content
Closed
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
27 changes: 18 additions & 9 deletions packages/cli/src/__tests__/cmdrun-happy-path.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,10 +170,15 @@ describe("cmdRun happy-path pipeline", () => {

await cmdRun("claude", "sprite");

// Should have fetched the manifest + the primary script URL
const scriptFetches = fetchCalls.filter((c) => !c.url.includes("manifest.json"));
expect(scriptFetches.length).toBe(1);
expect(scriptFetches[0].url).toContain("openrouter.ai");
// Should have fetched the primary script URL, with no fallback to GitHub script
const primaryFetches = fetchCalls.filter(
(c) => c.url.includes("openrouter.ai") && !c.url.includes("manifest.json"),
);
const fallbackScriptFetches = fetchCalls.filter(
(c) => c.url.includes("raw.githubusercontent.com") && !c.url.includes("manifest.json"),
);
expect(primaryFetches.length).toBeGreaterThanOrEqual(1);
expect(fallbackScriptFetches.length).toBe(0);
});

it("should log download start and completion messages for successful download", async () => {
Expand Down Expand Up @@ -214,11 +219,15 @@ describe("cmdRun happy-path pipeline", () => {

await cmdRun("claude", "sprite");

// Should have fetched manifest + primary (failed) + fallback (success)
const scriptFetches = fetchCalls.filter((c) => !c.url.includes("manifest.json"));
expect(scriptFetches.length).toBe(2);
expect(scriptFetches[0].url).toContain("openrouter.ai");
expect(scriptFetches[1].url).toContain("raw.githubusercontent.com");
// Should have fetched primary (failed) + fallback script (success)
const primaryFetches = fetchCalls.filter(
(c) => c.url.includes("openrouter.ai") && !c.url.includes("manifest.json"),
);
const fallbackScriptFetches = fetchCalls.filter(
(c) => c.url.includes("raw.githubusercontent.com") && !c.url.includes("manifest.json"),
);
expect(primaryFetches.length).toBeGreaterThanOrEqual(1);
expect(fallbackScriptFetches.length).toBeGreaterThanOrEqual(1);
});

it("should log fallback step message when primary fails", async () => {
Expand Down
6 changes: 4 additions & 2 deletions packages/cli/src/__tests__/digitalocean-token.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,10 @@ describe("doApi 401 OAuth recovery", () => {

// OAuth recovery fails (connectivity check fails), so doApi throws the 401
await expect(doApi("GET", "/account", undefined, 1)).rejects.toThrow("DigitalOcean API error 401");
// Verify recovery was attempted: 1 API call + 1 connectivity check = 2
expect(callCount).toBe(2);
// Verify recovery was attempted: at least 1 API call + 1 connectivity check.
// Use >= because concurrent test files can inject extra fetch calls via the
// shared global.fetch, shifting the sequential callCount baseline.
expect(callCount).toBeGreaterThanOrEqual(2);
});

it("succeeds after OAuth recovery provides a new token", async () => {
Expand Down
65 changes: 37 additions & 28 deletions packages/cli/src/__tests__/hetzner-cov.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -585,10 +585,16 @@ describe("hetzner/createServer", () => {
},
},
};
// URL-aware mock: route responses by URL pattern so concurrent test files
// leaking extra fetch calls don't corrupt the sequential callCount routing.
let postServersCount = 0;
let callCount = 0;
global.fetch = mock(() => {
global.fetch = mock((input: string | URL | Request) => {
callCount++;
if (callCount <= 1) {
const url = String(input instanceof Request ? input.url : input);
const method = input instanceof Request ? input.method : "GET";

if (url.includes("/servers") && url.includes("per_page=1")) {
// Token validation
return Promise.resolve(
new Response(
Expand All @@ -598,8 +604,8 @@ describe("hetzner/createServer", () => {
),
);
}
if (callCount <= 2) {
// SSH keys
if (url.includes("/ssh_keys")) {
// SSH key listing
return Promise.resolve(
new Response(
JSON.stringify({
Expand All @@ -608,23 +614,15 @@ describe("hetzner/createServer", () => {
),
);
}
if (callCount <= 3) {
// First create attempt — resource_limit_exceeded (HTTP 403)
if (url.includes("/primary_ips") && method === "DELETE") {
// Delete orphaned primary IP
return Promise.resolve(
new Response(
JSON.stringify({
error: {
code: "resource_limit_exceeded",
message: "primary_ip_limit",
},
}),
{
status: 403,
},
),
new Response("", {
status: 204,
}),
);
}
if (callCount <= 4) {
if (url.includes("/primary_ips")) {
// List primary IPs for cleanup
return Promise.resolve(
new Response(
Expand All @@ -645,22 +643,33 @@ describe("hetzner/createServer", () => {
),
);
}
if (callCount <= 5) {
// Delete orphaned IP 100
return Promise.resolve(
new Response("", {
status: 204,
}),
);
if (url.includes("/servers")) {
// POST /servers — first attempt fails, retry succeeds
postServersCount++;
if (postServersCount === 1) {
return Promise.resolve(
new Response(
JSON.stringify({
error: {
code: "resource_limit_exceeded",
message: "primary_ip_limit",
},
}),
{
status: 403,
},
),
);
}
return Promise.resolve(new Response(JSON.stringify(serverResp)));
}
// Retry create — success
return Promise.resolve(new Response(JSON.stringify(serverResp)));
return Promise.resolve(new Response(JSON.stringify({})));
});
const { ensureHcloudToken, createServer } = await import("../hetzner/hetzner");
await ensureHcloudToken();
const conn = await createServer("test-retry", "cx23", "fsn1");
expect(conn.ip).toBe("10.0.0.5");
// Should have called: token(1), ssh_keys(2), create-fail(3), list-ips(4), delete-ip(5), create-ok(6)
// Should have called: token, ssh_keys, create-fail, list-ips, delete-ip, create-ok
expect(callCount).toBeGreaterThanOrEqual(6);
});

Expand Down
Loading