| 1 | import { test } from "node:test"; |
| 2 | import assert from "node:assert/strict"; |
| 3 | import { npmCi, retryable } from "./npm-ci.mjs"; |
| 4 | |
| 5 | const quiet = () => {}; |
| 6 | const noSleep = async () => {}; |
| 7 | |
| 8 | /** A fake npm that answers each try in turn. */ |
| 9 | function tries(...answers) { |
| 10 | const seen = []; |
| 11 | const run = async (args) => { |
| 12 | seen.push(args); |
| 13 | return { stalled: false, output: "", ...answers[seen.length - 1] }; |
| 14 | }; |
| 15 | return { seen, run }; |
| 16 | } |
| 17 | |
| 18 | test("a body that broke off is worth another try, a lockfile out of step is not", () => { |
| 19 | assert.equal(retryable("npm error code ECONNRESET\nnpm error network aborted"), true); |
| 20 | assert.equal(retryable("npm error request to https://registry.npmjs.org/x failed, reason: socket hang up"), true); |
| 21 | assert.equal(retryable("npm error code EAI_AGAIN"), true); |
| 22 | assert.equal(retryable("npm error `npm ci` can only install packages when your package.json and package-lock.json are in sync"), false); |
| 23 | assert.equal(retryable("npm error code E404"), false); |
| 24 | }); |
| 25 | |
| 26 | test("an install that ends without saying why, or stalls, is tried again", () => { |
| 27 | assert.equal(retryable(""), true); |
| 28 | assert.equal(retryable("added 12 packages"), true); |
| 29 | assert.equal(retryable("npm error code E404", { stalled: true }), true); |
| 30 | }); |
| 31 | |
| 32 | test("a dropped connection is tried again until the install passes", async () => { |
| 33 | const waits = []; |
| 34 | const { seen, run } = tries({ code: 1, output: "npm error code ECONNRESET" }, { code: 0 }); |
| 35 | const code = await npmCi(["--prefer-offline", "--no-audit"], { run, sleep: async (ms) => waits.push(ms), waits: [5, 15], log: quiet }); |
| 36 | assert.equal(code, 0); |
| 37 | assert.equal(seen.length, 2); |
| 38 | assert.deepEqual(seen[1], ["ci", "--prefer-offline", "--no-audit"]); |
| 39 | assert.deepEqual(waits, [5]); |
| 40 | }); |
| 41 | |
| 42 | test("a real error fails at once", async () => { |
| 43 | const { seen, run } = tries({ code: 1, output: "npm error code ERESOLVE" }); |
| 44 | assert.equal(await npmCi([], { run, sleep: noSleep, log: quiet }), 1); |
| 45 | assert.equal(seen.length, 1); |
| 46 | }); |
| 47 | |
| 48 | test("it gives up after its tries, with npm's exit code", async () => { |
| 49 | const reset = { code: 7, output: "npm error network aborted" }; |
| 50 | const { seen, run } = tries(reset, reset, reset, reset); |
| 51 | assert.equal(await npmCi([], { run, sleep: noSleep, attempts: 3, log: quiet }), 7); |
| 52 | assert.equal(seen.length, 3); |
| 53 | }); |
| 54 | |
| 55 | test("a stalled install is run again", async () => { |
| 56 | const { seen, run } = tries({ code: 1, stalled: true }, { code: 0 }); |
| 57 | assert.equal(await npmCi([], { run, sleep: noSleep, log: quiet }), 0); |
| 58 | assert.equal(seen.length, 2); |
| 59 | }); |