返回 CodeWhale
postinstall.test.js
根目录 / npm / codewhale / test / postinstall.test.js
1 const assert = require("node:assert/strict");
2 const test = require("node:test");
3
4 const pkg = require("../package.json");
5 const { _internal } = require("../scripts/install");
6
7 test("postinstall opts into optional install mode", () => {
8 assert.equal(pkg.scripts.postinstall, "node scripts/install.js --optional");
9 });
10
11 test("optional install can be enabled by command-line flag or env", () => {
12 assert.equal(_internal.isOptionalInstall(["--optional"], {}), true);
13 assert.equal(_internal.isOptionalInstall([], {}), false);
14 assert.equal(_internal.isOptionalInstall([], { DEEPSEEK_TUI_OPTIONAL_INSTALL: "1" }), true);
15 assert.equal(_internal.isOptionalInstall([], { DEEPSEEK_OPTIONAL_INSTALL: "1" }), true);
16 });
17
18 test("optional mode only changes install-time defaults", () => {
19 assert.equal(_internal.maxAttempts("install", { DEEPSEEK_TUI_OPTIONAL_INSTALL: "1" }), 1);
20 assert.equal(_internal.maxAttempts("runtime", { DEEPSEEK_TUI_OPTIONAL_INSTALL: "1" }), 5);
21 assert.equal(_internal.defaultTimeoutMs("install", { DEEPSEEK_TUI_OPTIONAL_INSTALL: "1" }), 15_000);
22 assert.equal(_internal.defaultTimeoutMs("runtime", { DEEPSEEK_TUI_OPTIONAL_INSTALL: "1" }), 300_000);
23 assert.equal(_internal.defaultStallMs("install", { DEEPSEEK_TUI_OPTIONAL_INSTALL: "1" }), 5_000);
24 assert.equal(_internal.defaultStallMs("runtime", { DEEPSEEK_TUI_OPTIONAL_INSTALL: "1" }), 30_000);
25 });
26
27 test("pnpm optional postinstall skips install-time download", () => {
28 assert.equal(
29 _internal.shouldSkipOptionalPostinstall("install", ["--optional"], {
30 npm_config_user_agent: "pnpm/10.11.0 npm/? node/v22.15.0 win32 x64",
31 }),
32 true,
33 );
34 assert.equal(
35 _internal.shouldSkipOptionalPostinstall("runtime", ["--optional"], {
36 npm_config_user_agent: "pnpm/10.11.0 npm/? node/v22.15.0 win32 x64",
37 }),
38 false,
39 );
40 assert.equal(
41 _internal.shouldSkipOptionalPostinstall("install", [], {
42 npm_config_user_agent: "pnpm/10.11.0 npm/? node/v22.15.0 win32 x64",
43 }),
44 false,
45 );
46 assert.equal(
47 _internal.shouldSkipOptionalPostinstall("install", ["--optional"], {
48 npm_config_user_agent: "npm/11.3.0 node/v22.15.0 win32 x64",
49 }),
50 false,
51 );
52 });
53
54 test("optional install only swallows retryable download failures", () => {
55 const socketHangUp = new Error("socket hang up");
56 assert.equal(
57 _internal.shouldIgnoreInstallFailure("install", socketHangUp, ["--optional"], {}),
58 true,
59 );
60
61 const timedOut = new Error("download exceeded total timeout of 15000 ms");
62 timedOut.code = "EDOWNLOADTIMEOUT";
63 assert.equal(
64 _internal.shouldIgnoreInstallFailure("install", timedOut, ["--optional"], {}),
65 true,
66 );
67
68 const unsupported = new Error("Unsupported platform: freebsd");
69 assert.equal(
70 _internal.shouldIgnoreInstallFailure("install", unsupported, ["--optional"], {}),
71 false,
72 );
73
74 const badChecksum = new Error("Checksum mismatch for codewhale-linux-x64");
75 badChecksum.nonRetryable = true;
76 assert.equal(
77 _internal.shouldIgnoreInstallFailure("install", badChecksum, ["--optional"], {}),
78 false,
79 );
80
81 const glibc = new Error("requires glibc 2.34 or newer");
82 glibc.nonRetryable = true;
83 assert.equal(
84 _internal.shouldIgnoreInstallFailure("install", glibc, ["--optional"], {}),
85 false,
86 );
87 });
88
89 test("optional install still swallows wrapped http 5xx failures", async () => {
90 const previous = process.env.DEEPSEEK_TUI_OPTIONAL_INSTALL;
91 process.env.DEEPSEEK_TUI_OPTIONAL_INSTALL = "1";
92 const http5xx = new Error("Request failed with status 502: https://example.invalid");
93 http5xx.name = "HttpStatusError";
94 http5xx.status = 502;
95
96 try {
97 await assert.rejects(
98 _internal.withRetry("fetch https://example.invalid", async () => {
99 throw http5xx;
100 }, "install"),
101 (wrapped) => {
102 assert.equal(wrapped.name, "HttpStatusError");
103 assert.equal(wrapped.status, 502);
104 assert.equal(
105 _internal.shouldIgnoreInstallFailure("install", wrapped, ["--optional"], {}),
106 true,
107 );
108 return true;
109 },
110 );
111 } finally {
112 if (previous === undefined) {
113 delete process.env.DEEPSEEK_TUI_OPTIONAL_INSTALL;
114 } else {
115 process.env.DEEPSEEK_TUI_OPTIONAL_INSTALL = previous;
116 }
117 }
118 });
119
120 test("withRetry prints install hint on first retryable failure", async () => {
121 const previousWrite = process.stderr.write;
122 const previousSetTimeout = global.setTimeout;
123 let stderr = "";
124 let attempts = 0;
125 process.stderr.write = (chunk) => {
126 stderr += String(chunk);
127 return true;
128 };
129 global.setTimeout = (callback) => {
130 callback();
131 return 0;
132 };
133
134 try {
135 const result = await _internal.withRetry(
136 "fetch https://github.com/example",
137 async () => {
138 attempts += 1;
139 if (attempts === 1) {
140 const err = new Error("connect ETIMEDOUT 20.205.243.166:443");
141 err.code = "ETIMEDOUT";
142 throw err;
143 }
144 return "ok";
145 },
146 "runtime",
147 );
148
149 assert.equal(result, "ok");
150 assert.equal(attempts, 2);
151 assert.match(stderr, /codewhale install hint:/);
152 assert.match(stderr, /#npm-binary-download-times-out/);
153 } finally {
154 process.stderr.write = previousWrite;
155 global.setTimeout = previousSetTimeout;
156 }
157 });
158
158 lines JAVASCRIPT