From 1ec9e7fbd58e63b78fece38a83db7329b8f040ef Mon Sep 17 00:00:00 2001 From: Jeremy Sfez Date: Thu, 9 Apr 2026 15:41:48 +0200 Subject: [PATCH] test(cli): use vitest --- packages/cli/e2e/build.js | 186 -------------------------------- packages/cli/e2e/build.test.js | 155 ++++++++++++++++++++++++++ packages/cli/e2e/skip.js | 11 -- packages/cli/e2e/skip.test.js | 12 +++ packages/cli/e2e/upload.js | 18 ---- packages/cli/e2e/upload.test.js | 20 ++++ packages/cli/e2e/utils.js | 12 ++- packages/cli/package.json | 5 +- packages/cli/vitest.config.ts | 8 ++ pnpm-lock.yaml | 4 + 10 files changed, 210 insertions(+), 221 deletions(-) delete mode 100644 packages/cli/e2e/build.js create mode 100644 packages/cli/e2e/build.test.js delete mode 100644 packages/cli/e2e/skip.js create mode 100644 packages/cli/e2e/skip.test.js delete mode 100644 packages/cli/e2e/upload.js create mode 100644 packages/cli/e2e/upload.test.js create mode 100644 packages/cli/vitest.config.ts diff --git a/packages/cli/e2e/build.js b/packages/cli/e2e/build.js deleted file mode 100644 index 1086705e..00000000 --- a/packages/cli/e2e/build.js +++ /dev/null @@ -1,186 +0,0 @@ -/** - * E2E tests for `argos build` commands. - * Requires ARGOS_TOKEN env var. - * Optional: ARGOS_API_BASE_URL env var. - * - * Usage: - * ARGOS_TOKEN=xxx node e2e/build.js - * ARGOS_TOKEN=xxx ARGOS_API_BASE_URL=https://api.argos-ci.dev:4001/v2 NODE_OPTIONS=--use-system-ca pnpm -C packages/cli exec node e2e/build.js - */ - -import { assert, run } from "./utils.js"; - -const token = process.env.ARGOS_TOKEN; -const apiBaseURL = process.env.ARGOS_API_BASE_URL; -const buildNumber = process.env.ARGOS_BUILD_NUMBER || "28022"; - -if (!token) { - console.error( - "Usage: ARGOS_TOKEN=xxx [ARGOS_API_BASE_URL=] node e2e/build.js", - ); - process.exit(1); -} - -function envWith(overrides = {}) { - return { ...process.env, ...overrides }; -} - -const baseEnv = apiBaseURL - ? envWith({ ARGOS_API_BASE_URL: apiBaseURL }) - : process.env; - -console.log("\n`build get` failing commands:"); - -try { - run(["build", "get", "1"], { ...baseEnv, ARGOS_TOKEN: "" }); - assert(false, "Missing token with build number should exit with code 1"); -} catch (err) { - assert(err.status !== 0, "Exit code 1 when no token for build number"); - assert( - err.stderr.includes("No Argos token found"), - "Error message includes 'No Argos token found' for build number", - ); -} - -try { - run( - [ - "build", - "get", - "https://app.argos-ci.com/argos-ci/argos-javascript/builds/1", - ], - { ...baseEnv, ARGOS_TOKEN: "" }, - ); - assert(false, "Missing token with build URL should exit with code 1"); -} catch (err) { - assert(err.status !== 0, "Exit code 1 when no token for build URL"); - assert( - err.stderr.includes("No Argos token found"), - "Error message includes 'No Argos token found' for build URL", - ); -} - -try { - run(["build", "get", "999999"], { - ...baseEnv, - ARGOS_TOKEN: token, - }); - assert(false, "Unknown build number should exit with code 1"); -} catch (err) { - assert(err.status !== 0, "Unknown build number: exit code 1"); - assert( - err.stderr.includes("Error:"), - "Unknown build number: human-readable error message", - ); -} - -try { - run(["build", "get", "not-a-number"], { - ...baseEnv, - ARGOS_TOKEN: token, - }); - assert(false, "Invalid build number should exit with code 1"); -} catch (err) { - assert(err.status !== 0, "Invalid build number: exit code 1"); - assert( - err.stderr.includes("valid build number or Argos build URL"), - "Invalid build reference: human-readable error message", - ); -} - -console.log("\n`build get` successful commands:"); -const buildByNumberJsonOutput = run(["build", "get", buildNumber, "--json"], { - ...baseEnv, - ARGOS_TOKEN: token, -}); -const buildByNumberJson = JSON.parse(buildByNumberJsonOutput.stdout); -const buildUrl = buildByNumberJson.url; - -const buildByNumberHumanOutput = run(["build", "get", buildNumber], { - ...baseEnv, - ARGOS_TOKEN: token, -}); -assert( - buildByNumberHumanOutput.stdout.includes(`Build #${buildNumber}`), - "Prints the build number in human-readable mode", -); -assert( - buildByNumberHumanOutput.stdout.includes("Snapshots:"), - "Prints snapshot stats in human-readable mode", -); -assert( - buildByNumberHumanOutput.stdout.includes(`URL: ${buildUrl}`), - "Prints the build URL in human-readable mode", -); -assert(buildByNumberJson.id !== undefined, "Returns build id"); -assert(buildByNumberJson.url !== undefined, "Returns build url"); -assert( - buildByNumberJson.number === Number(buildNumber), - "Returns the requested build number", -); - -const buildByUrlJsonOutput = run(["build", "get", "--json", buildUrl], { - ...baseEnv, - ARGOS_TOKEN: token, -}); -const buildByUrlJson = JSON.parse(buildByUrlJsonOutput.stdout); -assert( - buildByUrlJson.number === Number(buildNumber), - "accepts an Argos build URL", -); - -console.log("\n`build snapshots` failing commands:"); - -try { - run(["build", "snapshots", "1"], { ...baseEnv, ARGOS_TOKEN: "" }); - assert( - false, - "Missing token for snapshots with build number should exit with code 1", - ); -} catch (err) { - assert( - err.status !== 0, - "Exit code 1 when no token for snapshots with build number", - ); - assert( - err.stderr.includes("No Argos token found"), - "Error message includes 'No Argos token found' for snapshots with build number", - ); -} - -console.log("\n`build snapshots` successful commands:"); -const buildSnapshots = run(["build", "snapshots", buildNumber], { - ...baseEnv, - ARGOS_TOKEN: token, -}); -assert( - buildSnapshots.stdout.includes("Snapshots for build #"), - "Prints the build id", -); -assert(buildSnapshots.stdout.includes("Summary:"), "Prints the build Summary"); - -const buildSnapshotsJsonOutput = run( - ["build", "snapshots", buildNumber, "--json"], - { - ...baseEnv, - ARGOS_TOKEN: token, - }, -); -const buildSnapshotsJson = JSON.parse(buildSnapshotsJsonOutput.stdout); -assert(Array.isArray(buildSnapshotsJson), "Returns an array in JSON mode"); -assert(Boolean(buildSnapshotsJson[0].id), "Returns structured snapshot data"); - -const snapshotsNeedsReviewJsonOutput = run( - ["build", "snapshots", buildNumber, "--needs-review", "--json"], - { - ...baseEnv, - ARGOS_TOKEN: token, - }, -); -const snapshotsNeedingReview = JSON.parse( - snapshotsNeedsReviewJsonOutput.stdout, -); -assert( - Array.isArray(snapshotsNeedingReview), - "Returns an array in JSON mode for needs-review filter", -); diff --git a/packages/cli/e2e/build.test.js b/packages/cli/e2e/build.test.js new file mode 100644 index 00000000..bf4b24a6 --- /dev/null +++ b/packages/cli/e2e/build.test.js @@ -0,0 +1,155 @@ +import { beforeAll, describe, expect, test } from "vitest"; + +import { getRequiredEnv, run } from "./utils.js"; + +const token = getRequiredEnv("ARGOS_TOKEN"); +const apiBaseURL = process.env.ARGOS_API_BASE_URL; +const buildNumber = process.env.ARGOS_BUILD_NUMBER || "28022"; + +const baseEnv = { + ...process.env, + ...(apiBaseURL ? { ARGOS_API_BASE_URL: apiBaseURL } : {}), + ARGOS_TOKEN: token, +}; + +function expectRunToFail(args, overrideEnv) { + try { + run(args, { ...baseEnv, ...overrideEnv }); + } catch (error) { + return error; + } + + throw new Error( + `Expected command to fail: node bin/argos-cli.js ${args.join(" ")}`, + ); +} + +let build; +let buildUrl; + +beforeAll(() => { + build = JSON.parse( + run(["build", "get", buildNumber, "--json"], baseEnv).stdout, + ); + buildUrl = build.url; +}); + +describe("argos build get", () => { + test("fails when token is missing for a build number", () => { + const error = expectRunToFail(["build", "get", "1"], { ARGOS_TOKEN: "" }); + + expect(error.status).not.toBe(0); + expect(error.stderr).toContain("No Argos token found"); + }); + + test("fails when token is missing for a build URL", () => { + const error = expectRunToFail( + [ + "build", + "get", + "https://app.argos-ci.com/argos-ci/argos-javascript/builds/1", + ], + { ARGOS_TOKEN: "" }, + ); + + expect(error.status).not.toBe(0); + expect(error.stderr).toContain("No Argos token found"); + }); + + test("fails for an unknown build number", () => { + const error = expectRunToFail(["build", "get", "999999"]); + + expect(error.status).not.toBe(0); + expect(error.stderr).toMatch("Unauthorized"); + }); + + test("fails for an invalid build reference", () => { + const error = expectRunToFail(["build", "get", "not-a-number"]); + + expect(error.status).not.toBe(0); + expect(error.stderr).toContain("valid build number or Argos build URL"); + }); + + test("returns build details for a build number in JSON mode", () => { + expect(build.id).toBeDefined(); + expect(build.url).toBeDefined(); + expect(build.number).toBe(Number(buildNumber)); + }); + + test("returns build details for a build number in human-readable mode", () => { + const buildByNumberHumanOutput = run(["build", "get", buildNumber], { + ...baseEnv, + }); + + expect(buildByNumberHumanOutput.stdout).toContain(`Build #${buildNumber}`); + expect(buildByNumberHumanOutput.stdout).toContain( + `Status: ${build.status}`, + ); + expect(buildByNumberHumanOutput.stdout).toContain("Snapshots:"); + expect(buildByNumberHumanOutput.stdout).toContain(`URL: ${buildUrl}`); + }); + + test("accepts an Argos build URL", () => { + const buildByUrlJsonOutput = run( + ["build", "get", "--json", buildUrl], + baseEnv, + ); + const buildByUrlJson = JSON.parse(buildByUrlJsonOutput.stdout); + + expect(buildByUrlJson.id).toBe(build.id); + expect(buildByUrlJson.number).toBe(Number(buildNumber)); + }); +}); + +describe("argos build snapshots", () => { + test("fails when token is missing", () => { + const error = expectRunToFail(["build", "snapshots", "1"], { + ARGOS_TOKEN: "", + }); + + expect(error.status).not.toBe(0); + expect(error.stderr).toContain("No Argos token found"); + }); + + test("prints human-readable snapshot data", () => { + const buildSnapshots = run(["build", "snapshots", buildNumber], { + ...baseEnv, + }); + + expect(buildSnapshots.stdout).toContain("Snapshots for build #"); + expect(buildSnapshots.stdout).toContain("Summary:"); + }); + + test("returns structured snapshot data in JSON mode", () => { + const buildSnapshotsJsonOutput = run( + ["build", "snapshots", buildNumber, "--json"], + baseEnv, + ); + const buildSnapshotsJson = JSON.parse(buildSnapshotsJsonOutput.stdout); + + expect(Array.isArray(buildSnapshotsJson)).toBe(true); + expect(buildSnapshotsJson[0]?.id).toBeTruthy(); + }); + + test("accepts an Argos build URL", () => { + const buildSnapshotsJsonOutput = run( + ["build", "snapshots", buildUrl, "--json"], + baseEnv, + ); + const buildSnapshotsJson = JSON.parse(buildSnapshotsJsonOutput.stdout); + + expect(Array.isArray(buildSnapshotsJson)).toBe(true); + }); + + test("supports filtering snapshots needing review", () => { + const snapshotsNeedsReviewJsonOutput = run( + ["build", "snapshots", buildNumber, "--needs-review", "--json"], + baseEnv, + ); + const snapshotsNeedingReview = JSON.parse( + snapshotsNeedsReviewJsonOutput.stdout, + ); + + expect(Array.isArray(snapshotsNeedingReview)).toBe(true); + }); +}); diff --git a/packages/cli/e2e/skip.js b/packages/cli/e2e/skip.js deleted file mode 100644 index 2ea01ab6..00000000 --- a/packages/cli/e2e/skip.js +++ /dev/null @@ -1,11 +0,0 @@ -import { assert, run } from "./utils.js"; - -const buildName = `argos-cli-e2e-skipped-node-${process.env.NODE_VERSION}-${process.env.OS}`; - -const skipResult = run(["skip", "--build-name", buildName]); - -console.log(skipResult.stdout); -console.error(skipResult.stderr); - -const buildNumberMatch = skipResult.combined.match(/\/builds\/(\d+)/); -assert(buildNumberMatch, "skip returns a build URL"); diff --git a/packages/cli/e2e/skip.test.js b/packages/cli/e2e/skip.test.js new file mode 100644 index 00000000..4150b19a --- /dev/null +++ b/packages/cli/e2e/skip.test.js @@ -0,0 +1,12 @@ +import { expect, test } from "vitest"; + +import { getRequiredEnv, run } from "./utils.js"; + +getRequiredEnv("ARGOS_TOKEN"); + +test("skip returns a build URL", () => { + const buildName = `argos-cli-e2e-skipped-node-${process.env.NODE_VERSION}-${process.env.OS}`; + const skipResult = run(["skip", "--build-name", buildName]); + + expect(skipResult.combined).toMatch(/\/builds\/(\d+)/); +}); diff --git a/packages/cli/e2e/upload.js b/packages/cli/e2e/upload.js deleted file mode 100644 index 28360f04..00000000 --- a/packages/cli/e2e/upload.js +++ /dev/null @@ -1,18 +0,0 @@ -import { assert, run } from "./utils.js"; - -const buildName = `argos-cli-e2e-node-${process.env.NODE_VERSION}-${process.env.OS}`; - -const uploadResult = run([ - "upload", - "../../__fixtures__", - "--build-name", - buildName, -]); - -console.log(uploadResult.stdout); -console.error(uploadResult.stderr); - -const buildUrlMatch = uploadResult.combined.match( - /https?:\/\/\S+\/builds\/\d+/, -); -assert(buildUrlMatch, "upload returns a full build URL"); diff --git a/packages/cli/e2e/upload.test.js b/packages/cli/e2e/upload.test.js new file mode 100644 index 00000000..0b323b42 --- /dev/null +++ b/packages/cli/e2e/upload.test.js @@ -0,0 +1,20 @@ +import { expect, test } from "vitest"; + +import { getRequiredEnv, run } from "./utils.js"; + +getRequiredEnv("ARGOS_TOKEN"); + +test("upload returns a full build URL", () => { + const buildName = `argos-cli-e2e-node-${process.env.NODE_VERSION}-${process.env.OS}`; + const uploadResult = run([ + "upload", + "../../__fixtures__", + "--build-name", + buildName, + ]); + + console.log(uploadResult.stdout); + console.error(uploadResult.stderr); + + expect(uploadResult.combined).toMatch(/https?:\/\/\S+\/builds\/\d+/); +}); diff --git a/packages/cli/e2e/utils.js b/packages/cli/e2e/utils.js index 23de87f0..e73b41ae 100644 --- a/packages/cli/e2e/utils.js +++ b/packages/cli/e2e/utils.js @@ -28,10 +28,12 @@ export function run(args, env = process.env) { }; } -export function assert(condition, message) { - if (!condition) { - console.error(`✘ FAIL: ${message}`); - process.exit(1); +export function getRequiredEnv(name) { + const value = process.env[name]; + + if (!value) { + throw new Error(`Missing required environment variable: ${name}`); } - console.log(`✔ PASS: ${message}`); + + return value; } diff --git a/packages/cli/package.json b/packages/cli/package.json index a701c113..614f45de 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -40,9 +40,12 @@ "ora": "^9.3.0", "update-notifier": "^7.3.1" }, + "devDependencies": { + "vitest": "catalog:" + }, "scripts": { "build": "tsdown", - "e2e": "node e2e/upload.js && node e2e/skip.js && node e2e/build.js", + "e2e": "vitest", "check-types": "tsc", "check-format": "prettier --check --ignore-unknown --ignore-path=../../.gitignore --ignore-path=../../.prettierignore .", "lint": "eslint ." diff --git a/packages/cli/vitest.config.ts b/packages/cli/vitest.config.ts new file mode 100644 index 00000000..3e387b35 --- /dev/null +++ b/packages/cli/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "node", + include: ["e2e/**/*.test.js"], + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 833062e6..feeb9ce3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -108,6 +108,10 @@ importers: update-notifier: specifier: ^7.3.1 version: 7.3.1 + devDependencies: + vitest: + specifier: 'catalog:' + version: 4.1.2(@types/node@25.5.0)(@vitest/browser-playwright@4.1.2)(@vitest/ui@4.1.2)(msw@2.12.14(@types/node@25.5.0)(typescript@6.0.2))(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) packages/core: dependencies: