Skip to content
Open
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
83 changes: 47 additions & 36 deletions packages/cli/src/__tests__/hetzner-cov.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { afterEach, beforeEach, describe, expect, it, mock, spyOn } from "bun:test";
import { isString } from "@openrouter/spawn-shared";
import { mockBunSpawn, mockClackPrompts } from "./test-helpers";

mockClackPrompts();
Expand Down Expand Up @@ -585,21 +586,14 @@ describe("hetzner/createServer", () => {
},
},
};
let createAttempts = 0;
let callCount = 0;
global.fetch = mock(() => {
const mockFetch = mock((url: string | URL | Request, init?: RequestInit) => {
callCount++;
if (callCount <= 1) {
// Token validation
return Promise.resolve(
new Response(
JSON.stringify({
servers: [],
}),
),
);
}
if (callCount <= 2) {
// SSH keys
const urlStr = isString(url) ? url : url instanceof URL ? url.href : url.url;
const method = init?.method ?? "GET";
// SSH keys lookup
if (urlStr.includes("/ssh_keys")) {
return Promise.resolve(
new Response(
JSON.stringify({
Expand All @@ -608,24 +602,16 @@ describe("hetzner/createServer", () => {
),
);
}
if (callCount <= 3) {
// First create attempt — resource_limit_exceeded (HTTP 403)
// Delete orphaned IP (DELETE /primary_ips/NNN — must come before list check)
if (urlStr.includes("/primary_ips/") && method === "DELETE") {
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) {
// List primary IPs for cleanup
// List primary IPs for cleanup
if (urlStr.includes("/primary_ips")) {
return Promise.resolve(
new Response(
JSON.stringify({
Expand All @@ -645,22 +631,47 @@ describe("hetzner/createServer", () => {
),
);
}
if (callCount <= 5) {
// Delete orphaned IP 100
// Create server (POST /servers)
if (urlStr.includes("/servers") && method === "POST") {
createAttempts++;
if (createAttempts <= 1) {
// First create attempt — resource_limit_exceeded (HTTP 403)
return Promise.resolve(
new Response(
JSON.stringify({
error: {
code: "resource_limit_exceeded",
message: "primary_ip_limit",
},
}),
{
status: 403,
},
),
);
}
// Retry create — success
return Promise.resolve(new Response(JSON.stringify(serverResp)));
}
// Token validation — GET /servers (and any other GET)
if (urlStr.includes("/servers")) {
return Promise.resolve(
new Response("", {
status: 204,
}),
new Response(
JSON.stringify({
servers: [],
}),
),
);
}
// Retry create — success
return Promise.resolve(new Response(JSON.stringify(serverResp)));
// Fallback
return Promise.resolve(new Response(JSON.stringify({})));
});
global.fetch = mockFetch;
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