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