Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions packages/api-client/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,22 @@ export interface paths {
patch?: never;
trace?: never;
};
"/project/builds/{buildNumber}": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get: operations["getAuthBuildByNumber"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
}
export type webhooks = Record<string, never>;
export interface components {
Expand Down Expand Up @@ -1097,4 +1113,63 @@ export interface operations {
};
};
};
getAuthBuildByNumber: {
parameters: {
query?: never;
header?: never;
path: {
/** @description The build number */
buildNumber: number;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Build */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["Build"];
};
};
/** @description Invalid parameters */
400: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["Error"];
};
};
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["Error"];
};
};
/** @description Not found */
404: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["Error"];
};
};
/** @description Server error */
500: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["Error"];
};
};
};
}
}
190 changes: 190 additions & 0 deletions packages/cli/e2e/builds.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/**
* E2E tests for `argos builds` commands.
* Requires ARGOS_TOKEN env var.
* Optional: ARGOS_API_BASE_URL env var.
*
* Usage:
* ARGOS_TOKEN=xxx node e2e/builds.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/builds.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=<url>] node e2e/builds.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`builds get` failing commands:");

try {
run(["builds", "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(
[
"builds",
"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(["builds", "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(["builds", "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`builds get` successful commands:");
const buildByNumberJsonOutput = run(["builds", "get", buildNumber, "--json"], {
...baseEnv,
ARGOS_TOKEN: token,
});
const buildByNumberJson = JSON.parse(buildByNumberJsonOutput.stdout);
const buildUrl = buildByNumberJson.url;

const buildByNumberHumanOutput = run(["builds", "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(["builds", "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`builds snapshots` failing commands:");

try {
run(["builds", "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`builds snapshots` successful commands:");
const buildSnapshots = run(["builds", "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(
["builds", "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].base.id),
"Returns structured snapshot data",
);

const snapshotsNeedsReviewJsonOutput = run(
["builds", "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");
assert(
snapshotsNeedingReview.length === 0,
"Returns an empty array when there are no snapshots to review",
);
25 changes: 11 additions & 14 deletions packages/cli/e2e/skip.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import { exec } from "node:child_process";

exec(
`node bin/argos-cli.js skip --build-name "argos-cli-e2e-skipped-node-${process.env.NODE_VERSION}-${process.env.OS}"`,
(err, stdout, stderr) => {
if (err) {
console.error(err);
process.exit(1);
}

console.log(stdout);
console.error(stderr);
},
);
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");
30 changes: 17 additions & 13 deletions packages/cli/e2e/upload.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { exec } from "node:child_process";

exec(
`node bin/argos-cli.js upload ../../__fixtures__ --build-name "argos-cli-e2e-node-${process.env.NODE_VERSION}-${process.env.OS}"`,
(err, stdout, stderr) => {
if (err) {
console.error(err);
process.exit(1);
}

console.log(stdout);
console.error(stderr);
},
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");
37 changes: 37 additions & 0 deletions packages/cli/e2e/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { spawnSync } from "node:child_process";

const cliPath = "bin/argos-cli.js";

export function run(args, env = process.env) {
const result = spawnSync("node", [cliPath, ...args], {
encoding: "utf8",
env,
});

const stdout = result.stdout ?? "";
const stderr = result.stderr ?? "";

if (result.status !== 0) {
const error = new Error(
`Command failed: node ${cliPath} ${args.join(" ")}`,
);
error.status = result.status;
error.stdout = stdout;
error.stderr = stderr;
throw error;
}

return {
stdout,
stderr,
combined: `${stdout}${stderr}`,
};
}

export function assert(condition, message) {
if (!condition) {
console.error(`✘ FAIL: ${message}`);
process.exit(1);
}
console.log(`✔ PASS: ${message}`);
}
3 changes: 2 additions & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,15 @@
"access": "public"
},
"dependencies": {
"@argos-ci/api-client": "workspace:*",
"@argos-ci/core": "workspace:*",
"commander": "^14.0.3",
"ora": "^9.3.0",
"update-notifier": "^7.3.1"
},
"scripts": {
"build": "tsdown",
"e2e": "node e2e/upload.js && node e2e/skip.js",
"e2e": "node e2e/upload.js && node e2e/skip.js && node e2e/builds.js",
"check-types": "tsc",
"check-format": "prettier --check --ignore-unknown --ignore-path=../../.gitignore --ignore-path=../../.prettierignore .",
"lint": "eslint ."
Expand Down
Loading
Loading