@desplega.ai/agent-swarm 2.7.5 → 2.7.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{acp-adapter-g3ads55s.js → acp-adapter-bp91d37f.js} +4 -4
- package/dist/{actions-zwfykqqk.js → actions-yj0eeaqh.js} +18 -17
- package/dist/{amp-adapter-z3s0ssdt.js → amp-adapter-4pg9h34q.js} +1 -1
- package/dist/{app-vmp4fgep.js → app-909e97cr.js} +3 -3
- package/dist/{assistant-vwde653f.js → assistant-qdyb5fyy.js} +24 -22
- package/dist/{boot-reembed-375t7tbk.js → boot-reembed-fk0s9z18.js} +1 -1
- package/dist/{boot-reembed-gq9rcpnj.js → boot-reembed-gs1rzrnh.js} +1 -1
- package/dist/{boot-scrub-sweep-nd938kyf.js → boot-scrub-sweep-4qfd4kfx.js} +2 -2
- package/dist/{boot-seeds-aykx51v3.js → boot-seeds-yr00k13d.js} +4 -4
- package/dist/{boxd-k1j26g3h.js → boxd-vdmfssd9.js} +4 -4
- package/dist/{claude-adapter-8j4hm099.js → claude-adapter-qta1f1xn.js} +3 -3
- package/dist/{claude-managed-setup-0wyynpn1.js → claude-managed-setup-sc6j4dm0.js} +1 -1
- package/dist/{claude-sdk-session-q6wj5hmy.js → claude-sdk-session-zgrz6yvc.js} +9169 -9070
- package/dist/{cli-gjr2y2xk.js → cli-0agcv1v5.js} +4 -1
- package/dist/{cli-vqypxzh4.js → cli-0at8w34w.js} +7 -7
- package/dist/{cli-7a42nfy0.js → cli-0qatpy8q.js} +3 -3
- package/dist/{cli-p599f1qm.js → cli-0yv5z2pg.js} +1 -1
- package/dist/{cli-0gey5fzy.js → cli-386tyxxg.js} +2 -2
- package/dist/{cli-5pag6rmn.js → cli-3ypk9vk1.js} +1 -1
- package/dist/{cli-qxnf8z2z.js → cli-436532ck.js} +2 -2
- package/dist/{cli-z8eh3ex8.js → cli-4necwtsv.js} +4 -4
- package/dist/{cli-zh3fp8v2.js → cli-54t00gt8.js} +1 -1
- package/dist/{cli-hf6zt5m6.js → cli-69szn7w7.js} +17 -1
- package/dist/{cli-nn4d8dne.js → cli-89knwags.js} +2 -2
- package/dist/{cli-spknvxbq.js → cli-8bc1w0tw.js} +10 -10
- package/dist/{cli-rqkxrmgw.js → cli-8snvgs5r.js} +38 -13
- package/dist/{cli-rhw40jgb.js → cli-bjb3z51z.js} +2 -2
- package/dist/{cli-xtjhfmpc.js → cli-c60vetmm.js} +1 -1
- package/dist/{cli-0731hvbw.js → cli-decfb6v8.js} +2 -2
- package/dist/{cli-bhjt02p8.js → cli-drr5v6av.js} +1 -1
- package/dist/{cli-fqzx7gwa.js → cli-dxae2yg7.js} +2 -2
- package/dist/{cli-tqgg40ek.js → cli-e3r2neck.js} +8 -7
- package/dist/{cli-zppwg9p9.js → cli-ekwks2vs.js} +1 -1
- package/dist/{cli-nzxr556s.js → cli-es6va8jk.js} +26 -26
- package/dist/{cli-a47xy8eq.js → cli-faxe6thg.js} +1 -1
- package/dist/cli-gdhpcbhf.js +55 -0
- package/dist/{cli-82jg7fp9.js → cli-he5nxaa8.js} +1 -1
- package/dist/{cli-8veytvbh.js → cli-he9wchme.js} +1 -1
- package/dist/{cli-pywf3eh5.js → cli-hgaazk69.js} +1 -1
- package/dist/{cli-0bxqnvq9.js → cli-hr2e2fft.js} +28 -6
- package/dist/{cli-1ayk1eas.js → cli-k2gzwsj2.js} +135 -26
- package/dist/{cli-xbkdm710.js → cli-kqdg1bsb.js} +3 -3
- package/dist/cli-m2z8jagn.js +12078 -0
- package/dist/{cli-g61qxmge.js → cli-m48g6p95.js} +3 -3
- package/dist/{cli-7wspm5gm.js → cli-nhrvpaj7.js} +20 -4
- package/dist/{cli-afrrppsw.js → cli-nwj0ee2w.js} +2 -2
- package/dist/{cli-3h4nhjn8.js → cli-p5gp8pcx.js} +9 -7
- package/dist/{cli-rm6hqj3z.js → cli-pd7vagab.js} +33 -139
- package/dist/{cli-mjzynrse.js → cli-q0k1w5zd.js} +1 -1
- package/dist/{cli-8hs41zy2.js → cli-rmmmrjkd.js} +1 -1
- package/dist/{cli-kg8hchyg.js → cli-sa02jsqt.js} +1 -1
- package/dist/{cli-1338796d.js → cli-shpbx5ee.js} +142 -98
- package/dist/{cli-82ae98pz.js → cli-svj5090z.js} +2 -2
- package/dist/{cli-bnz7a7qf.js → cli-vvpx3gsx.js} +1 -1
- package/dist/{cli-mmwm7zbf.js → cli-w6dt9ged.js} +1 -1
- package/dist/{cli-wvaawacd.js → cli-waqbxg08.js} +5230 -66
- package/dist/{cli-j3jvnpe8.js → cli-wp9sp1qb.js} +1 -1
- package/dist/{cli-nmbmy64z.js → cli-xbbndvjy.js} +3 -3
- package/dist/{cli-vbyfwh92.js → cli-xds5f77d.js} +2 -2
- package/dist/{cli-7wcyk2s5.js → cli-xnqgpshy.js} +1 -1
- package/dist/{cli-rpm2sgv0.js → cli-xnrwazav.js} +19 -2
- package/dist/{cli-gf5hbmdr.js → cli-yjdgea32.js} +3 -3
- package/dist/{cli-mry0qzym.js → cli-yn4kxs06.js} +123 -10
- package/dist/{cli-hn9hxmz9.js → cli-yx4kfngt.js} +1 -1
- package/dist/{cli-y24sc29k.js → cli-yy3rhsdw.js} +30 -8
- package/dist/{cli-6s7vdcta.js → cli-zbbwez0r.js} +4 -4
- package/dist/{cli-z6jkx23q.js → cli-zm72anhg.js} +9 -9
- package/dist/cli.js +22 -21
- package/dist/{codex-adapter-q6k3gnyg.js → codex-adapter-ayd1fb6h.js} +2 -2
- package/dist/{codex-session-runner-3mwmk3d9.js → codex-session-runner-jesa32xc.js} +2 -2
- package/dist/{commands-evrenjn9.js → commands-hanewgf2.js} +9 -3
- package/dist/{controller-xjp8vswh.js → controller-dt1n3c1q.js} +8 -3
- package/dist/{controllers-connect-pp5jnxvc.js → controllers-connect-xx8x64n5.js} +20 -19
- package/dist/{cursor-adapter-yf47w4t0.js → cursor-adapter-9s4dwwdw.js} +1 -1
- package/dist/{db-zxpw6aht.js → db-3vttcbv4.js} +3 -3
- package/dist/{db-rgfsh91f.js → db-9dv0g1ab.js} +11 -3
- package/dist/{devtools-82fzqra8.js → devtools-qxfgbsqz.js} +1 -1
- package/dist/{dispatcher-smasgws7.js → dispatcher-tx9hd1sv.js} +17 -16
- package/dist/{docker-rrjhaqt9.js → docker-c1a3gqmf.js} +89 -22
- package/dist/{dsh-adapter-ftyy8dnp.js → dsh-adapter-007y4ejs.js} +1 -1
- package/dist/{e2b-qcaj675y.js → e2b-a19d28rz.js} +2 -2
- package/dist/{e2b-w1p0bnpm.js → e2b-we6hs6q5.js} +4 -4
- package/dist/{easter-egg-3d-yyts7p16.js → easter-egg-3d-rgbrqb8s.js} +1 -1
- package/dist/{embedding-space-1z0mbg95.js → embedding-space-ny1e2940.js} +6 -6
- package/dist/{embeddings-ygeg7tm7.js → embeddings-2t386t13.js} +1 -1
- package/dist/{execute-14qhqp4d.js → execute-pksh0ceh.js} +1 -1
- package/dist/{external-channel-ttgrs3sx.js → external-channel-phgmhmd7.js} +2 -2
- package/dist/{fx-adapter-gt6pncj2.js → fx-adapter-fy8rr921.js} +5 -5
- package/dist/{google-generative-ai-yvmdxyxa.js → google-generative-ai-8y3m3k4b.js} +1 -1
- package/dist/{google-vertex-jrtdh9gs.js → google-vertex-w3v98n7b.js} +1 -1
- package/dist/{grok-adapter-sm4m5k4g.js → grok-adapter-q0abvpaj.js} +5 -5
- package/dist/handlers-by3sfxyx.js +32 -0
- package/dist/{heartbeat-qahz4p62.js → heartbeat-kadyek8p.js} +20 -19
- package/dist/{hook-kj3d398m.js → hook-5zs3msf6.js} +2 -2
- package/dist/{hook-4fd5wq5w.js → hook-9k551xnj.js} +2 -2
- package/dist/{http-1ted0j1k.js → http-mhz9vw1f.js} +213 -80
- package/dist/index-4f2pvxae.js +37 -0
- package/dist/index-98gz7tmp.js +30 -0
- package/dist/{index-5rk6rfzr.js → index-fvzke5kx.js} +1 -1
- package/dist/index-k08z2cak.js +33 -0
- package/dist/{index-yqhz88yy.js → index-pxn30mse.js} +8 -8
- package/dist/{index-6rq3r5zb.js → index-q361cg2f.js} +1 -1
- package/dist/{index-7v51awbn.js → index-rvr00sg7.js} +20 -13
- package/dist/{index-q4b917zs.js → index-vdygbq10.js} +1 -1
- package/dist/{keepalive-6cf311t5.js → keepalive-w56f32cg.js} +3 -3
- package/dist/{lead-fjy418cw.js → lead-enq35nqp.js} +31 -30
- package/dist/{maintenance-a4bm1bmt.js → maintenance-7mdgxcea.js} +1 -1
- package/dist/{oauth-refresh-sweep-3h0dfw1k.js → oauth-refresh-sweep-29txy9vk.js} +2 -2
- package/dist/{onboard-myn5hraa.js → onboard-kpsrkarj.js} +4 -4
- package/dist/{opencode-adapter-p2nkb5am.js → opencode-adapter-j25vm3rr.js} +3 -3
- package/dist/{otel-impl-a1a0ef6b.js → otel-impl-6kkct7a8.js} +1 -1
- package/dist/{pi-mono-adapter-rzmpb72a.js → pi-mono-adapter-exhkzhn2.js} +4 -4
- package/dist/{pricing-refresh-83drrnxj.js → pricing-refresh-n2x7jh6k.js} +3 -3
- package/dist/{rbac-roles-s8y6ecb5.js → rbac-roles-81ypx6es.js} +1 -1
- package/dist/{rbac-roles-0fb6e7pj.js → rbac-roles-b5htkjby.js} +2 -2
- package/dist/{render-v2-twmbgrz4.js → render-v2-qn6jmpk4.js} +3 -3
- package/dist/{resume-zfqc36xj.js → resume-wd30wx23.js} +6 -6
- package/dist/{runtime-qwggdpqr.js → runtime-4hsg7h60.js} +1 -1
- package/dist/{session-access-sc9pn9da.js → session-access-5qvfevhk.js} +4 -4
- package/dist/{session-source-backfill-qh07xr8g.js → session-source-backfill-n1czavbr.js} +2 -2
- package/dist/{setup-4fr0m7a1.js → setup-dnb56qy5.js} +3 -3
- package/dist/{virtual-modules-fh4ha15b.js → virtual-modules-76xxv9fs.js} +2 -2
- package/dist/{work-objects-0wr8zxct.js → work-objects-4n21tfkm.js} +20 -19
- package/dist/{worker-follow-up-ktyyazyp.js → worker-follow-up-77hvzxww.js} +17 -16
- package/dist/{worker-0c5tyzqe.js → worker-gg1576j3.js} +31 -30
- package/openapi.json +93 -1
- package/package.json +8 -7
- package/src/be/db/script-runs.ts +23 -0
- package/src/be/db/tasks/read.ts +122 -3
- package/src/be/db-client.ts +8 -5
- package/src/be/db.ts +15 -4
- package/src/be/memory/providers/sqlite-store.ts +49 -12
- package/src/be/migrations/260_runtime_pool_cache.sql +20 -0
- package/src/be/migrations/262_session_visibility_indexes.sql +18 -0
- package/src/be/migrations/263_task_list_filter_index.sql +11 -0
- package/src/be/migrations/264_slack_thread_cover_index.sql +9 -0
- package/src/be/migrations/266_script_runs_source_task.sql +11 -0
- package/src/be/runtime-pools/cache-cleanup.ts +109 -0
- package/src/be/runtime-pools/db.ts +65 -6
- package/src/be/runtime-pools/remote-controller.ts +5 -0
- package/src/be/runtime-pools/supervisor.ts +33 -2
- package/src/be/scripts/typecheck.ts +22 -0
- package/src/be/seed-scripts/catalog/task-failure-audit.ts +5 -1
- package/src/commands/controller.ts +4 -0
- package/src/commands/entrypoint-api-readiness.ts +74 -0
- package/src/http/index.ts +1 -1
- package/src/http/list-total-cache.ts +14 -7
- package/src/http/memory.ts +38 -9
- package/src/http/script-runs.ts +174 -5
- package/src/http/status.ts +10 -2
- package/src/http/tasks.ts +4 -2
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/fx-adapter.ts +1 -1
- package/src/runtime-providers/controller-protocol.ts +14 -1
- package/src/runtime-providers/controller.ts +5 -0
- package/src/runtime-providers/docker.ts +108 -23
- package/src/runtime-providers/types.ts +16 -0
- package/src/script-workflows/workflow-ctx.ts +8 -0
- package/src/scripts-runtime/types/stdlib.d.ts +22 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +22 -0
- package/src/server-user.ts +1 -0
- package/src/slack/commands.ts +5 -1
- package/src/slack/handlers.ts +37 -2
- package/src/slack/left-threads.ts +27 -4
- package/src/tasks/follow-up-redelegation-guard.ts +155 -0
- package/src/tests/claude-artifacts-read-script.test.ts +209 -0
- package/src/tests/db-client.test.ts +21 -0
- package/src/tests/db-task-read-contract.test.ts +8 -2
- package/src/tests/db-task-write-contract.test.ts +5 -1
- package/src/tests/entrypoint-api-readiness.test.ts +416 -210
- package/src/tests/explicit-model-validation.test.ts +14 -8
- package/src/tests/follow-up-redelivery-guard.test.ts +432 -202
- package/src/tests/list-total-cache.test.ts +27 -2
- package/src/tests/memory-fts-populate.test.ts +11 -9
- package/src/tests/memory-retrieval-purge.test.ts +80 -0
- package/src/tests/migration-260-runtime-pool-cache.test.ts +118 -0
- package/src/tests/query-audit-plans.test.ts +27 -0
- package/src/tests/runtime-cache-cleanup.test.ts +367 -0
- package/src/tests/runtime-e2b-pools.test.ts +41 -0
- package/src/tests/runtime-pools-db.test.ts +98 -0
- package/src/tests/runtime-pools-http.test.ts +29 -0
- package/src/tests/runtime-providers-docker.test.ts +253 -0
- package/src/tests/runtime-remote-controller.test.ts +66 -2
- package/src/tests/runtime-supervisor.test.ts +66 -2
- package/src/tests/script-connections.test.ts +11 -0
- package/src/tests/script-runs-http.test.ts +684 -2
- package/src/tests/seed-scripts.test.ts +26 -0
- package/src/tests/slack-leave-thread.test.ts +145 -5
- package/src/tests/slack-render-v2-query-plans.test.ts +24 -2
- package/src/tests/status.test.ts +11 -0
- package/src/tests/tasks-readable-row.test.ts +155 -0
- package/src/tools/send-task.ts +35 -47
- package/src/tools/slack-leave-thread.ts +1 -1
- package/src/tools/task-dedup.ts +45 -21
- package/src/types.ts +38 -0
- package/templates/skills/composio-google-docs/content.md +10 -0
- package/templates/skills/script-workflows/content.md +19 -1
- package/templates/skills/swarm-docs/content.md +1 -0
- package/templates/skills/swarm-scripts/SKILL.md +2 -0
- package/templates/skills/swarm-scripts/content.md +2 -0
- package/dist/cli-dznrgaep.js +0 -30
- package/dist/cli-w3hcrxht.js +0 -6985
- package/dist/handlers-vwmba1m2.js +0 -31
- package/dist/index-5r71rf98.js +0 -32
- package/dist/index-anmwxs87.js +0 -29
- package/dist/index-spp0ra5c.js +0 -36
|
@@ -1,24 +1,39 @@
|
|
|
1
|
-
import { afterEach, describe, expect, test } from "bun:test";
|
|
1
|
+
import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
|
+
import { chmod, mkdtemp, rm } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { waitForApiReady } from "../commands/entrypoint-api-readiness";
|
|
7
|
+
import { CHILD_PROCESS_TEST_BUDGET_MS, runChild } from "./test-proc";
|
|
3
8
|
|
|
4
9
|
/**
|
|
5
10
|
* Regression tests for the `wait_for_api_ready` bounded /health poll in
|
|
6
|
-
* docker-entrypoint.sh
|
|
7
|
-
* "Gate Worker Boot on API Readiness").
|
|
11
|
+
* docker-entrypoint.sh before provider configuration reads.
|
|
8
12
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* The subprocess MUST be spawned asynchronously (`Bun.spawn`, not
|
|
15
|
-
* `Bun.spawnSync`): the mock `Bun.serve` server used below runs its `fetch`
|
|
16
|
-
* handler on this same process's event loop, and `Bun.spawnSync` blocks that
|
|
17
|
-
* event loop for its entire duration — the mock server would never be able
|
|
18
|
-
* to answer a request from the very curl subprocess we're waiting on.
|
|
13
|
+
* Run the deployed shell shim with its Bun helper against a real HTTP server.
|
|
14
|
+
* Image paths for Bun and the helper are relocated to the test runtime and an isolated copy; the shell
|
|
15
|
+
* call, argument handling, exit status and TypeScript logic are the real ones.
|
|
16
|
+
* Injected clocks and probes also cover policy without wall-clock waits.
|
|
19
17
|
*/
|
|
20
18
|
|
|
19
|
+
const TEST_TIMEOUT = CHILD_PROCESS_TEST_BUDGET_MS;
|
|
21
20
|
const entrypointPath = `${import.meta.dir}/../../docker-entrypoint.sh`;
|
|
21
|
+
let helperDir: string;
|
|
22
|
+
let helperPath: string;
|
|
23
|
+
|
|
24
|
+
beforeAll(async () => {
|
|
25
|
+
helperDir = await mkdtemp(join(tmpdir(), "swarm-readiness-"));
|
|
26
|
+
helperPath = join(helperDir, "entrypoint-api-readiness.ts");
|
|
27
|
+
// Copy only the file shipped by each image: no repo imports or node_modules.
|
|
28
|
+
await Bun.write(
|
|
29
|
+
helperPath,
|
|
30
|
+
Bun.file(`${import.meta.dir}/../commands/entrypoint-api-readiness.ts`),
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
afterAll(async () => {
|
|
35
|
+
await rm(helperDir, { recursive: true, force: true });
|
|
36
|
+
});
|
|
22
37
|
|
|
23
38
|
function extractWaitForApiReady(): string {
|
|
24
39
|
const script = readFileSync(entrypointPath, "utf8");
|
|
@@ -42,38 +57,23 @@ function extractWaitForApiReady(): string {
|
|
|
42
57
|
throw new Error("Could not locate `# END wait_for_api_ready` marker in docker-entrypoint.sh.");
|
|
43
58
|
}
|
|
44
59
|
|
|
45
|
-
return script
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
exitCode: number | null;
|
|
50
|
-
stdout: string;
|
|
51
|
-
stderr: string;
|
|
52
|
-
durationMs: number;
|
|
60
|
+
return script
|
|
61
|
+
.slice(funcStart, endIndex)
|
|
62
|
+
.replace("/usr/local/bin/bun", '"$3"')
|
|
63
|
+
.replace("/usr/local/lib/agent-swarm/entrypoint-api-readiness.ts", '"$2"');
|
|
53
64
|
}
|
|
54
65
|
|
|
55
66
|
/** Run the real extracted helper against `url` in a fresh bash subprocess. */
|
|
56
67
|
async function runWaitForApiReady(
|
|
57
68
|
url: string,
|
|
58
69
|
env: Record<string, string> = {},
|
|
59
|
-
): Promise<
|
|
70
|
+
): Promise<Awaited<ReturnType<typeof runChild>>> {
|
|
60
71
|
const funcSrc = extractWaitForApiReady();
|
|
61
|
-
const script = `set -
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const proc = Bun.spawn(["bash", "-c", script, "bash", url], {
|
|
72
|
+
const script = `set -eu\n${funcSrc}\nwait_for_api_ready "$1" "$2" "$3"\necho 'provider setup reached'\n`;
|
|
73
|
+
return runChild(["bash", "-c", script, "bash", url, helperPath, process.execPath], {
|
|
74
|
+
cwd: helperDir,
|
|
65
75
|
env: { PATH: process.env.PATH ?? "", ...env },
|
|
66
|
-
stdout: "pipe",
|
|
67
|
-
stderr: "pipe",
|
|
68
76
|
});
|
|
69
|
-
const [stdout, stderr, exitCode] = await Promise.all([
|
|
70
|
-
new Response(proc.stdout).text(),
|
|
71
|
-
new Response(proc.stderr).text(),
|
|
72
|
-
proc.exited,
|
|
73
|
-
]);
|
|
74
|
-
const durationMs = performance.now() - start;
|
|
75
|
-
|
|
76
|
-
return { exitCode, stdout, stderr, durationMs };
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
let server: ReturnType<typeof Bun.serve> | undefined;
|
|
@@ -98,12 +98,6 @@ describe("wait_for_api_ready: source contract", () => {
|
|
|
98
98
|
expect(firstProviderReadIndex).toBeGreaterThan(gateCallIndex);
|
|
99
99
|
});
|
|
100
100
|
|
|
101
|
-
test("uses bounded per-attempt curl flags, not an unbounded request", () => {
|
|
102
|
-
const funcSrc = extractWaitForApiReady();
|
|
103
|
-
expect(funcSrc).toContain("--connect-timeout");
|
|
104
|
-
expect(funcSrc).toContain("--max-time");
|
|
105
|
-
});
|
|
106
|
-
|
|
107
101
|
test("never references API_KEY or an Authorization header", () => {
|
|
108
102
|
const funcSrc = extractWaitForApiReady();
|
|
109
103
|
expect(funcSrc).not.toContain("API_KEY");
|
|
@@ -112,187 +106,399 @@ describe("wait_for_api_ready: source contract", () => {
|
|
|
112
106
|
});
|
|
113
107
|
|
|
114
108
|
describe("wait_for_api_ready: invalid timeout values", () => {
|
|
115
|
-
test.each(["0", "-5", "abc"])(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
109
|
+
test.each(["0", "-5", "abc"])(
|
|
110
|
+
"rejects %s with a stable fatal message",
|
|
111
|
+
async (raw) => {
|
|
112
|
+
const result = await runWaitForApiReady("http://unused.invalid", {
|
|
113
|
+
WORKER_API_READY_TIMEOUT_SECONDS: raw,
|
|
114
|
+
});
|
|
115
|
+
expect(result.exitCode).not.toBe(0);
|
|
116
|
+
expect(result.stdout).toContain(
|
|
117
|
+
`WORKER_API_READY_TIMEOUT_SECONDS must be a positive integer, got '${raw}'; exiting.`,
|
|
118
|
+
);
|
|
119
|
+
// Fails fast on the config check — must not attempt any network call.
|
|
120
|
+
expect(result.durationMs).toBeLessThan(5000);
|
|
121
|
+
expect(result.stdout).not.toContain("provider setup reached");
|
|
122
|
+
},
|
|
123
|
+
TEST_TIMEOUT,
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
test(
|
|
127
|
+
"an explicitly-empty value falls back to the 90s default (bash :- semantics)",
|
|
128
|
+
async () => {
|
|
129
|
+
server = Bun.serve({ port: 0, fetch: () => new Response("ok") });
|
|
130
|
+
const result = await runWaitForApiReady(server.url.toString(), {
|
|
137
131
|
WORKER_API_READY_TIMEOUT_SECONDS: "",
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const firstLine = new TextDecoder().decode(value);
|
|
146
|
-
reader.releaseLock();
|
|
147
|
-
proc.kill();
|
|
148
|
-
await proc.exited;
|
|
149
|
-
|
|
150
|
-
expect(firstLine).toContain("(timeout 90s)");
|
|
151
|
-
expect(firstLine).not.toContain("must be a positive integer");
|
|
152
|
-
}, 10000);
|
|
132
|
+
});
|
|
133
|
+
expect(result.exitCode).toBe(0);
|
|
134
|
+
expect(result.stdout).toContain("(timeout 90s)");
|
|
135
|
+
expect(result.stdout).not.toContain("must be a positive integer");
|
|
136
|
+
},
|
|
137
|
+
TEST_TIMEOUT,
|
|
138
|
+
);
|
|
153
139
|
});
|
|
154
140
|
|
|
155
141
|
describe("wait_for_api_ready: immediate success", () => {
|
|
156
|
-
test(
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
142
|
+
test(
|
|
143
|
+
"succeeds on the first attempt and logs waiting + ready lines",
|
|
144
|
+
async () => {
|
|
145
|
+
server = Bun.serve({
|
|
146
|
+
port: 0,
|
|
147
|
+
hostname: "127.0.0.1",
|
|
148
|
+
fetch(req) {
|
|
149
|
+
const url = new URL(req.url);
|
|
150
|
+
if (url.pathname === "/health") return new Response("ok", { status: 200 });
|
|
151
|
+
return new Response("not found", { status: 404 });
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const result = await runWaitForApiReady(server.url.toString(), {
|
|
156
|
+
WORKER_API_READY_TIMEOUT_SECONDS: "5",
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
expect(result.exitCode).toBe(0);
|
|
160
|
+
expect(result.stdout).toContain("Waiting for API readiness at");
|
|
161
|
+
expect(result.stdout).toContain("API ready at");
|
|
162
|
+
expect(result.stdout).toContain("provider setup reached");
|
|
163
|
+
expect(result.durationMs).toBeLessThan(5000);
|
|
164
|
+
},
|
|
165
|
+
TEST_TIMEOUT,
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
test(
|
|
169
|
+
"normalizes a trailing slash so it requests exactly /health",
|
|
170
|
+
async () => {
|
|
171
|
+
let requestedPath = "";
|
|
172
|
+
server = Bun.serve({
|
|
173
|
+
port: 0,
|
|
174
|
+
hostname: "127.0.0.1",
|
|
175
|
+
fetch(req) {
|
|
176
|
+
requestedPath = new URL(req.url).pathname;
|
|
177
|
+
return new Response("ok", { status: 200 });
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const trailingSlashUrl = server.url.toString(); // Bun.serve URLs already end in "/"
|
|
182
|
+
expect(trailingSlashUrl.endsWith("/")).toBe(true);
|
|
183
|
+
|
|
184
|
+
const result = await runWaitForApiReady(trailingSlashUrl, {
|
|
185
|
+
WORKER_API_READY_TIMEOUT_SECONDS: "5",
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
expect(result.exitCode).toBe(0);
|
|
189
|
+
expect(requestedPath).toBe("/health");
|
|
190
|
+
},
|
|
191
|
+
TEST_TIMEOUT,
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
test(
|
|
195
|
+
"never leaks a secret placed in the process environment",
|
|
196
|
+
async () => {
|
|
197
|
+
let authorization: string | null | undefined;
|
|
198
|
+
server = Bun.serve({
|
|
199
|
+
port: 0,
|
|
200
|
+
hostname: "127.0.0.1",
|
|
201
|
+
fetch(req) {
|
|
202
|
+
authorization = req.headers.get("authorization");
|
|
203
|
+
const url = new URL(req.url);
|
|
204
|
+
if (url.pathname === "/health") return new Response("ok", { status: 200 });
|
|
205
|
+
return new Response("not found", { status: 404 });
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const secret = "example-api-ready-secret-do-not-leak";
|
|
210
|
+
const result = await runWaitForApiReady(server.url.toString(), {
|
|
211
|
+
WORKER_API_READY_TIMEOUT_SECONDS: "5",
|
|
212
|
+
API_KEY: secret,
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
expect(result.exitCode).toBe(0);
|
|
216
|
+
expect(result.stdout).not.toContain(secret);
|
|
217
|
+
expect(result.stderr).not.toContain(secret);
|
|
218
|
+
expect(authorization).toBeNull();
|
|
219
|
+
},
|
|
220
|
+
TEST_TIMEOUT,
|
|
221
|
+
);
|
|
222
|
+
});
|
|
170
223
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
224
|
+
describe("wait_for_api_ready: transient failure then success", () => {
|
|
225
|
+
test(
|
|
226
|
+
"retries roughly once per second until the server recovers",
|
|
227
|
+
async () => {
|
|
228
|
+
// A per-test path prefix: an ephemeral port can be reused from another test
|
|
229
|
+
// process whose poller still hits /health, and those stray requests used
|
|
230
|
+
// to count as retries (3 requests in 1033 ms on CI).
|
|
231
|
+
const prefix = `/${crypto.randomUUID()}`;
|
|
232
|
+
const requestTimes: number[] = [];
|
|
233
|
+
server = Bun.serve({
|
|
234
|
+
port: 0,
|
|
235
|
+
hostname: "127.0.0.1",
|
|
236
|
+
fetch(req) {
|
|
237
|
+
const url = new URL(req.url);
|
|
238
|
+
if (url.pathname !== `${prefix}/health`)
|
|
239
|
+
return new Response("not found", { status: 404 });
|
|
240
|
+
requestTimes.push(performance.now());
|
|
241
|
+
if (requestTimes.length < 3) return new Response("unavailable", { status: 503 });
|
|
242
|
+
return new Response("ok", { status: 200 });
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
const result = await runWaitForApiReady(`${server.url.origin}${prefix}/`, {
|
|
247
|
+
WORKER_API_READY_TIMEOUT_SECONDS: "10",
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
expect(result.exitCode).toBe(0);
|
|
251
|
+
expect(requestTimes).toHaveLength(3);
|
|
252
|
+
expect(result.stdout).toContain("API ready at");
|
|
253
|
+
// Each retry waits out `sleep 1` after the previous response, so the
|
|
254
|
+
// server-side gap cannot shrink under load: paced, not a hot loop.
|
|
255
|
+
expect(requestTimes[1]! - requestTimes[0]!).toBeGreaterThanOrEqual(900);
|
|
256
|
+
expect(requestTimes[2]! - requestTimes[1]!).toBeGreaterThanOrEqual(900);
|
|
257
|
+
expect(result.durationMs).toBeLessThan(15000);
|
|
258
|
+
},
|
|
259
|
+
TEST_TIMEOUT,
|
|
260
|
+
);
|
|
261
|
+
});
|
|
187
262
|
|
|
188
|
-
|
|
189
|
-
|
|
263
|
+
describe("wait_for_api_ready: unreachable / timeout", () => {
|
|
264
|
+
test(
|
|
265
|
+
"exits non-zero with a stable fatal line after the bounded window",
|
|
266
|
+
async () => {
|
|
267
|
+
server = Bun.serve({ port: 0, fetch: () => new Response("unused") });
|
|
268
|
+
const url = `${server.url.origin}/${crypto.randomUUID()}`;
|
|
269
|
+
await server.stop(true);
|
|
270
|
+
server = undefined;
|
|
271
|
+
const result = await runWaitForApiReady(url, { WORKER_API_READY_TIMEOUT_SECONDS: "2" });
|
|
272
|
+
|
|
273
|
+
expect(result.exitCode).not.toBe(0);
|
|
274
|
+
expect(result.stdout).toContain(
|
|
275
|
+
`FATAL: API readiness timed out after 2s waiting for ${url}/health; exiting.`,
|
|
276
|
+
);
|
|
277
|
+
expect(result.stdout).not.toContain("provider setup reached");
|
|
278
|
+
expect(result.durationMs).toBeGreaterThanOrEqual(900);
|
|
279
|
+
expect(result.durationMs).toBeLessThan(15000);
|
|
280
|
+
},
|
|
281
|
+
TEST_TIMEOUT,
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
test(
|
|
285
|
+
"bounds total wall-clock time even against a server that never responds",
|
|
286
|
+
async () => {
|
|
287
|
+
server = Bun.serve({
|
|
288
|
+
port: 0,
|
|
289
|
+
hostname: "127.0.0.1",
|
|
290
|
+
fetch() {
|
|
291
|
+
// No response headers: the request deadline must abort the fetch.
|
|
292
|
+
return new Promise<Response>(() => {});
|
|
293
|
+
},
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
const result = await runWaitForApiReady(server.url.toString(), {
|
|
297
|
+
WORKER_API_READY_TIMEOUT_SECONDS: "2",
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
expect(result.exitCode).not.toBe(0);
|
|
301
|
+
expect(result.stdout).toContain("FATAL: API readiness timed out after 2s waiting for");
|
|
302
|
+
expect(result.durationMs).toBeLessThan(15000);
|
|
303
|
+
},
|
|
304
|
+
TEST_TIMEOUT,
|
|
305
|
+
);
|
|
306
|
+
});
|
|
190
307
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
308
|
+
describe("API readiness step with injected dependencies", () => {
|
|
309
|
+
test.each([
|
|
310
|
+
"0",
|
|
311
|
+
"-1",
|
|
312
|
+
"abc",
|
|
313
|
+
"1.5",
|
|
314
|
+
"Infinity",
|
|
315
|
+
"9007199254740992",
|
|
316
|
+
])("rejects %s before probing or sleeping", async (value) => {
|
|
317
|
+
let probes = 0;
|
|
318
|
+
let sleeps = 0;
|
|
319
|
+
const logs: string[] = [];
|
|
320
|
+
expect(
|
|
321
|
+
await waitForApiReady("http://api", value, {
|
|
322
|
+
fetch: async () => {
|
|
323
|
+
probes++;
|
|
324
|
+
return new Response("ok");
|
|
325
|
+
},
|
|
326
|
+
sleep: async () => {
|
|
327
|
+
sleeps++;
|
|
328
|
+
},
|
|
329
|
+
log: (message) => logs.push(message),
|
|
330
|
+
}),
|
|
331
|
+
).toBe(false);
|
|
332
|
+
expect(probes).toBe(0);
|
|
333
|
+
expect(sleeps).toBe(0);
|
|
334
|
+
expect(logs).toHaveLength(1);
|
|
335
|
+
expect(logs[0]).toContain("must be a positive integer");
|
|
336
|
+
});
|
|
194
337
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
return new Response("
|
|
338
|
+
test("paces HTTP and connection failures until the API recovers", async () => {
|
|
339
|
+
let now = 0;
|
|
340
|
+
let probes = 0;
|
|
341
|
+
const sleeps: number[] = [];
|
|
342
|
+
const logs: string[] = [];
|
|
343
|
+
const ready = await waitForApiReady("http://api/prefix/", "5", {
|
|
344
|
+
now: () => now,
|
|
345
|
+
fetch: async (url, init) => {
|
|
346
|
+
expect(url).toBe("http://api/prefix/health");
|
|
347
|
+
expect(init.redirect).toBe("manual");
|
|
348
|
+
expect(init.signal).toBeInstanceOf(AbortSignal);
|
|
349
|
+
if (++probes === 1) return new Response("booting", { status: 503 });
|
|
350
|
+
if (probes === 2) throw new Error("connection reset");
|
|
351
|
+
return new Response("ok");
|
|
207
352
|
},
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const result = await runWaitForApiReady(server.url.toString(), {
|
|
212
|
-
WORKER_API_READY_TIMEOUT_SECONDS: "5",
|
|
213
|
-
API_KEY: secret,
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
expect(result.exitCode).toBe(0);
|
|
217
|
-
expect(result.stdout).not.toContain(secret);
|
|
218
|
-
expect(result.stderr).not.toContain(secret);
|
|
219
|
-
}, 15000);
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
describe("wait_for_api_ready: transient failure then success", () => {
|
|
223
|
-
test("retries roughly once per second until the server recovers", async () => {
|
|
224
|
-
// A per-test path prefix: an ephemeral port can be reused from another test
|
|
225
|
-
// process whose poller still hits /health, and those stray requests used
|
|
226
|
-
// to count as retries (3 requests in 1033 ms on CI).
|
|
227
|
-
const prefix = `/${crypto.randomUUID()}`;
|
|
228
|
-
const requestTimes: number[] = [];
|
|
229
|
-
server = Bun.serve({
|
|
230
|
-
port: 0,
|
|
231
|
-
hostname: "127.0.0.1",
|
|
232
|
-
fetch(req) {
|
|
233
|
-
const url = new URL(req.url);
|
|
234
|
-
if (url.pathname !== `${prefix}/health`) return new Response("not found", { status: 404 });
|
|
235
|
-
requestTimes.push(performance.now());
|
|
236
|
-
if (requestTimes.length < 3) return new Response("unavailable", { status: 503 });
|
|
237
|
-
return new Response("ok", { status: 200 });
|
|
353
|
+
sleep: async (ms) => {
|
|
354
|
+
sleeps.push(ms);
|
|
355
|
+
now += ms;
|
|
238
356
|
},
|
|
357
|
+
log: (message) => logs.push(message),
|
|
239
358
|
});
|
|
359
|
+
expect(ready).toBe(true);
|
|
360
|
+
expect(probes).toBe(3);
|
|
361
|
+
expect(sleeps).toEqual([1000, 1000]);
|
|
362
|
+
expect(logs.at(-1)).toContain("API ready at");
|
|
363
|
+
});
|
|
240
364
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
test("exits non-zero with a stable fatal line after the bounded window", async () => {
|
|
258
|
-
// Port 1 is a well-known privileged port nothing is listening on inside
|
|
259
|
-
// this sandbox; the connection is refused immediately and deterministically.
|
|
260
|
-
const url = "http://127.0.0.1:1";
|
|
261
|
-
const result = await runWaitForApiReady(url, { WORKER_API_READY_TIMEOUT_SECONDS: "2" });
|
|
262
|
-
|
|
263
|
-
expect(result.exitCode).not.toBe(0);
|
|
264
|
-
expect(result.stdout).toContain(
|
|
265
|
-
`FATAL: API readiness timed out after 2s waiting for ${url}/health; exiting.`,
|
|
365
|
+
test("stops at the overall deadline without an unbounded retry loop", async () => {
|
|
366
|
+
let now = 0;
|
|
367
|
+
const logs: string[] = [];
|
|
368
|
+
expect(
|
|
369
|
+
await waitForApiReady("http://api", "2", {
|
|
370
|
+
now: () => now,
|
|
371
|
+
fetch: async () => new Response("unavailable", { status: 503 }),
|
|
372
|
+
sleep: async (ms) => {
|
|
373
|
+
now += ms;
|
|
374
|
+
},
|
|
375
|
+
log: (message) => logs.push(message),
|
|
376
|
+
}),
|
|
377
|
+
).toBe(false);
|
|
378
|
+
expect(now).toBe(2000);
|
|
379
|
+
expect(logs.at(-1)).toBe(
|
|
380
|
+
"[entrypoint] FATAL: API readiness timed out after 2s waiting for http://api/health; exiting.",
|
|
266
381
|
);
|
|
267
|
-
|
|
268
|
-
// substantially longer than the configured timeout. The lower bound is
|
|
269
|
-
// deliberately loose: the deadline is computed from `date +%s`, which has
|
|
270
|
-
// 1-second granularity, so a 2s timeout can legitimately resolve in ~1-3
|
|
271
|
-
// real seconds depending on where "now" lands within the current second.
|
|
272
|
-
// That slop is proportionally invisible at the real 90s production default.
|
|
273
|
-
expect(result.durationMs).toBeGreaterThanOrEqual(900);
|
|
274
|
-
expect(result.durationMs).toBeLessThan(15000);
|
|
275
|
-
}, 20000);
|
|
276
|
-
|
|
277
|
-
test("bounds total wall-clock time even against a server that never responds", async () => {
|
|
278
|
-
server = Bun.serve({
|
|
279
|
-
port: 0,
|
|
280
|
-
hostname: "127.0.0.1",
|
|
281
|
-
fetch() {
|
|
282
|
-
// Never resolves — simulates a hung/unresponsive API. curl's
|
|
283
|
-
// --max-time must cut this off rather than hanging indefinitely.
|
|
284
|
-
return new Promise<Response>(() => {});
|
|
285
|
-
},
|
|
286
|
-
});
|
|
382
|
+
});
|
|
287
383
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
384
|
+
test("a successful response arriving after the deadline does not continue boot", async () => {
|
|
385
|
+
let now = 0;
|
|
386
|
+
const logs: string[] = [];
|
|
387
|
+
expect(
|
|
388
|
+
await waitForApiReady("http://api", "1", {
|
|
389
|
+
now: () => now,
|
|
390
|
+
fetch: async () => {
|
|
391
|
+
now = 1001;
|
|
392
|
+
return new Response("ok");
|
|
393
|
+
},
|
|
394
|
+
log: (message) => logs.push(message),
|
|
395
|
+
}),
|
|
396
|
+
).toBe(false);
|
|
397
|
+
expect(logs.at(-1)).toContain("FATAL: API readiness timed out");
|
|
398
|
+
expect(logs.some((message) => message.includes("API ready at"))).toBe(false);
|
|
399
|
+
});
|
|
400
|
+
});
|
|
291
401
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
402
|
+
describe("API readiness HTTP compatibility", () => {
|
|
403
|
+
test.each(["headers", "body"])(
|
|
404
|
+
"retries a stalled %s request after the three-second request cap",
|
|
405
|
+
async (phase) => {
|
|
406
|
+
const prefix = `/${crypto.randomUUID()}`;
|
|
407
|
+
const requestTimes: number[] = [];
|
|
408
|
+
server = Bun.serve({
|
|
409
|
+
port: 0,
|
|
410
|
+
hostname: "127.0.0.1",
|
|
411
|
+
fetch(req) {
|
|
412
|
+
if (new URL(req.url).pathname !== `${prefix}/health`)
|
|
413
|
+
return new Response("not found", { status: 404 });
|
|
414
|
+
requestTimes.push(performance.now());
|
|
415
|
+
if (requestTimes.length > 1) return new Response("ok");
|
|
416
|
+
if (phase === "headers") return new Promise<Response>(() => {});
|
|
417
|
+
return new Response(
|
|
418
|
+
new ReadableStream({
|
|
419
|
+
start(controller) {
|
|
420
|
+
controller.enqueue(new TextEncoder().encode("partial"));
|
|
421
|
+
},
|
|
422
|
+
}),
|
|
423
|
+
);
|
|
424
|
+
},
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
const result = await runWaitForApiReady(`${server.url.origin}${prefix}`, {
|
|
428
|
+
WORKER_API_READY_TIMEOUT_SECONDS: "8",
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
expect(result.exitCode).toBe(0);
|
|
432
|
+
expect(requestTimes).toHaveLength(2);
|
|
433
|
+
// Three seconds to abort, then the existing one-second retry spacing.
|
|
434
|
+
expect(requestTimes[1]! - requestTimes[0]!).toBeGreaterThanOrEqual(3500);
|
|
435
|
+
expect(requestTimes[1]! - requestTimes[0]!).toBeLessThan(7000);
|
|
436
|
+
expect(result.stdout).toContain("API ready at");
|
|
437
|
+
expect(result.stdout).toContain("provider setup reached");
|
|
438
|
+
},
|
|
439
|
+
TEST_TIMEOUT,
|
|
440
|
+
);
|
|
441
|
+
|
|
442
|
+
test(
|
|
443
|
+
"uses the image's Bun even when a worker-writable PATH shadows bun",
|
|
444
|
+
async () => {
|
|
445
|
+
const shadow = join(helperDir, "bun");
|
|
446
|
+
await Bun.write(shadow, "#!/bin/sh\necho 'untrusted bun was executed'\nexit 99\n");
|
|
447
|
+
await chmod(shadow, 0o755);
|
|
448
|
+
server = Bun.serve({ port: 0, fetch: () => new Response("ok") });
|
|
449
|
+
const result = await runWaitForApiReady(server.url.origin, {
|
|
450
|
+
WORKER_API_READY_TIMEOUT_SECONDS: "2",
|
|
451
|
+
PATH: `${helperDir}:${process.env.PATH ?? ""}`,
|
|
452
|
+
});
|
|
453
|
+
expect(result.exitCode).toBe(0);
|
|
454
|
+
expect(result.stdout).not.toContain("untrusted bun was executed");
|
|
455
|
+
expect(result.stdout).toContain("provider setup reached");
|
|
456
|
+
},
|
|
457
|
+
TEST_TIMEOUT,
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
test(
|
|
461
|
+
"accepts a redirect without following it, like curl -f without -L",
|
|
462
|
+
async () => {
|
|
463
|
+
const paths: string[] = [];
|
|
464
|
+
server = Bun.serve({
|
|
465
|
+
port: 0,
|
|
466
|
+
fetch(req) {
|
|
467
|
+
paths.push(new URL(req.url).pathname);
|
|
468
|
+
return Response.redirect(`${server!.url.origin}/should-not-be-requested`, 302);
|
|
469
|
+
},
|
|
470
|
+
});
|
|
471
|
+
const result = await runWaitForApiReady(server.url.origin, {
|
|
472
|
+
WORKER_API_READY_TIMEOUT_SECONDS: "2",
|
|
473
|
+
});
|
|
474
|
+
expect(result.exitCode).toBe(0);
|
|
475
|
+
expect(paths).toEqual(["/health"]);
|
|
476
|
+
},
|
|
477
|
+
TEST_TIMEOUT,
|
|
478
|
+
);
|
|
479
|
+
|
|
480
|
+
test(
|
|
481
|
+
"bounds a response body that never finishes after successful headers",
|
|
482
|
+
async () => {
|
|
483
|
+
server = Bun.serve({
|
|
484
|
+
port: 0,
|
|
485
|
+
fetch: () =>
|
|
486
|
+
new Response(
|
|
487
|
+
new ReadableStream({
|
|
488
|
+
start(controller) {
|
|
489
|
+
controller.enqueue(new TextEncoder().encode("partial"));
|
|
490
|
+
},
|
|
491
|
+
}),
|
|
492
|
+
),
|
|
493
|
+
});
|
|
494
|
+
const result = await runWaitForApiReady(server.url.origin, {
|
|
495
|
+
WORKER_API_READY_TIMEOUT_SECONDS: "1",
|
|
496
|
+
});
|
|
497
|
+
expect(result.exitCode).not.toBe(0);
|
|
498
|
+
expect(result.stdout).toContain("FATAL: API readiness timed out after 1s");
|
|
499
|
+
expect(result.stdout).not.toContain("provider setup reached");
|
|
500
|
+
expect(result.durationMs).toBeLessThan(15000);
|
|
501
|
+
},
|
|
502
|
+
TEST_TIMEOUT,
|
|
503
|
+
);
|
|
298
504
|
});
|