mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-07-09 17:18:34 +00:00
fix(tests): resolve mock test failures in PR #3355
Use URL-based mock dispatch instead of sequential callCount thresholds to prevent telemetry flush from injecting spurious fetch calls that shift the mock response sequence. Agent: pr-maintainer Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a359f0234f
commit
8e52e0a608
2 changed files with 124 additions and 103 deletions
|
|
@ -88,34 +88,34 @@ describe("doApi 401 OAuth recovery", () => {
|
|||
|
||||
it("attempts OAuth recovery on 401 before throwing", async () => {
|
||||
state.token = "expired-token";
|
||||
let callCount = 0;
|
||||
let apiCalls = 0;
|
||||
let oauthChecks = 0;
|
||||
globalThis.fetch = mock((url: string | URL | Request) => {
|
||||
callCount++;
|
||||
const urlStr = String(url);
|
||||
// First call: the actual API call returning 401
|
||||
if (callCount === 1) {
|
||||
// OAuth connectivity check — fail it so tryDoOAuth returns null quickly
|
||||
// (avoids starting a real Bun.serve OAuth server)
|
||||
if (urlStr.includes("cloud.digitalocean.com")) {
|
||||
oauthChecks++;
|
||||
return Promise.reject(new Error("network unavailable"));
|
||||
}
|
||||
// DigitalOcean API calls — return 401
|
||||
if (urlStr.includes("api.digitalocean.com")) {
|
||||
apiCalls++;
|
||||
return Promise.resolve(
|
||||
new Response("Unauthorized", {
|
||||
status: 401,
|
||||
}),
|
||||
);
|
||||
}
|
||||
// Second call: OAuth connectivity check — fail it so tryDoOAuth returns null quickly
|
||||
// (avoids starting a real Bun.serve OAuth server)
|
||||
if (urlStr.includes("cloud.digitalocean.com")) {
|
||||
return Promise.reject(new Error("network unavailable"));
|
||||
}
|
||||
return Promise.resolve(
|
||||
new Response("Unauthorized", {
|
||||
status: 401,
|
||||
}),
|
||||
);
|
||||
// Ignore unrelated calls (e.g. telemetry flush)
|
||||
return Promise.resolve(new Response("ok"));
|
||||
});
|
||||
|
||||
// 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 (API call + OAuth connectivity check + retries)
|
||||
expect(callCount).toBeGreaterThanOrEqual(2);
|
||||
// Verify recovery was attempted: 1 API call + 1 connectivity check
|
||||
expect(apiCalls).toBe(1);
|
||||
expect(oauthChecks).toBe(1);
|
||||
});
|
||||
|
||||
it("succeeds after OAuth recovery provides a new token", async () => {
|
||||
|
|
|
|||
|
|
@ -585,47 +585,46 @@ describe("hetzner/createServer", () => {
|
|||
},
|
||||
},
|
||||
};
|
||||
let callCount = 0;
|
||||
global.fetch = mock(() => {
|
||||
callCount++;
|
||||
if (callCount <= 1) {
|
||||
// Token validation
|
||||
return Promise.resolve(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
servers: [],
|
||||
}),
|
||||
),
|
||||
);
|
||||
let hetznerCalls = 0;
|
||||
let postServerAttempts = 0;
|
||||
global.fetch = mock((url: string | URL | Request, opts?: RequestInit) => {
|
||||
const urlStr = String(url);
|
||||
// Ignore non-Hetzner calls (e.g. telemetry flush)
|
||||
if (!urlStr.includes("api.hetzner.cloud")) {
|
||||
return Promise.resolve(new Response("ok"));
|
||||
}
|
||||
if (callCount <= 2) {
|
||||
// SSH keys
|
||||
return Promise.resolve(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
ssh_keys: [],
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (callCount <= 3) {
|
||||
// First create attempt — resource_limit_exceeded (HTTP 403)
|
||||
return Promise.resolve(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
error: {
|
||||
code: "resource_limit_exceeded",
|
||||
message: "primary_ip_limit",
|
||||
hetznerCalls++;
|
||||
const method = opts?.method ?? "GET";
|
||||
// POST /servers — first attempt fails, retry succeeds
|
||||
if (method === "POST" && urlStr.includes("/servers")) {
|
||||
postServerAttempts++;
|
||||
if (postServerAttempts === 1) {
|
||||
return Promise.resolve(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
error: {
|
||||
code: "resource_limit_exceeded",
|
||||
message: "primary_ip_limit",
|
||||
},
|
||||
}),
|
||||
{
|
||||
status: 403,
|
||||
},
|
||||
}),
|
||||
{
|
||||
status: 403,
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return Promise.resolve(new Response(JSON.stringify(serverResp)));
|
||||
}
|
||||
// DELETE /primary_ips — cleanup orphaned IP
|
||||
if (method === "DELETE" && urlStr.includes("/primary_ips/")) {
|
||||
return Promise.resolve(
|
||||
new Response("", {
|
||||
status: 204,
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (callCount <= 4) {
|
||||
// List primary IPs for cleanup
|
||||
// GET /primary_ips — list IPs for cleanup
|
||||
if (urlStr.includes("/primary_ips")) {
|
||||
return Promise.resolve(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
|
|
@ -645,40 +644,8 @@ describe("hetzner/createServer", () => {
|
|||
),
|
||||
);
|
||||
}
|
||||
if (callCount <= 5) {
|
||||
// Delete orphaned IP 100
|
||||
return Promise.resolve(
|
||||
new Response("", {
|
||||
status: 204,
|
||||
}),
|
||||
);
|
||||
}
|
||||
// Retry create — success
|
||||
return Promise.resolve(new Response(JSON.stringify(serverResp)));
|
||||
});
|
||||
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)
|
||||
expect(callCount).toBeGreaterThanOrEqual(6);
|
||||
});
|
||||
|
||||
it("throws with guidance when resource limit hit and no orphaned IPs to clean", async () => {
|
||||
process.env.HCLOUD_TOKEN = "test-token";
|
||||
let callCount = 0;
|
||||
global.fetch = mock(() => {
|
||||
callCount++;
|
||||
if (callCount <= 1) {
|
||||
return Promise.resolve(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
servers: [],
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (callCount <= 2) {
|
||||
// GET /ssh_keys
|
||||
if (urlStr.includes("/ssh_keys")) {
|
||||
return Promise.resolve(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
|
|
@ -687,8 +654,39 @@ describe("hetzner/createServer", () => {
|
|||
),
|
||||
);
|
||||
}
|
||||
if (callCount <= 3) {
|
||||
// Create fails with resource_limit_exceeded
|
||||
// GET /servers (token validation)
|
||||
if (urlStr.includes("/servers")) {
|
||||
return Promise.resolve(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
servers: [],
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
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, ssh_keys, create-fail, list-ips, delete-ip, create-ok
|
||||
expect(hetznerCalls).toBeGreaterThanOrEqual(6);
|
||||
});
|
||||
|
||||
it("throws with guidance when resource limit hit and no orphaned IPs to clean", async () => {
|
||||
process.env.HCLOUD_TOKEN = "test-token";
|
||||
let postServerAttempts = 0;
|
||||
global.fetch = mock((url: string | URL | Request, opts?: RequestInit) => {
|
||||
const urlStr = String(url);
|
||||
// Ignore non-Hetzner calls (e.g. telemetry flush)
|
||||
if (!urlStr.includes("api.hetzner.cloud")) {
|
||||
return Promise.resolve(new Response("ok"));
|
||||
}
|
||||
const method = opts?.method ?? "GET";
|
||||
// POST /servers — always fail with resource_limit_exceeded
|
||||
if (method === "POST" && urlStr.includes("/servers")) {
|
||||
postServerAttempts++;
|
||||
return Promise.resolve(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
|
|
@ -703,20 +701,43 @@ describe("hetzner/createServer", () => {
|
|||
),
|
||||
);
|
||||
}
|
||||
// List primary IPs — all attached (none orphaned)
|
||||
return Promise.resolve(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
primary_ips: [
|
||||
{
|
||||
id: 100,
|
||||
ip: "1.2.3.4",
|
||||
assignee_id: 42,
|
||||
},
|
||||
],
|
||||
}),
|
||||
),
|
||||
);
|
||||
// GET /primary_ips — all attached (none orphaned)
|
||||
if (urlStr.includes("/primary_ips")) {
|
||||
return Promise.resolve(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
primary_ips: [
|
||||
{
|
||||
id: 100,
|
||||
ip: "1.2.3.4",
|
||||
assignee_id: 42,
|
||||
},
|
||||
],
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
// GET /ssh_keys
|
||||
if (urlStr.includes("/ssh_keys")) {
|
||||
return Promise.resolve(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
ssh_keys: [],
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
// GET /servers (token validation)
|
||||
if (urlStr.includes("/servers")) {
|
||||
return Promise.resolve(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
servers: [],
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
return Promise.resolve(new Response(JSON.stringify({})));
|
||||
});
|
||||
const { ensureHcloudToken, createServer } = await import("../hetzner/hetzner");
|
||||
await ensureHcloudToken();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue