-
Notifications
You must be signed in to change notification settings - Fork 3
test: add plugin routing tests for bundled chainloop plugin #349
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| const fs = require("fs") | ||
| const os = require("os") | ||
| const path = require("path") | ||
| const { execSync } = require("child_process") | ||
|
|
||
| const CLI = path.join(__dirname, "..", "cli", "supercli.js") | ||
|
|
||
| function runNoServer(args, options = {}) { | ||
| try { | ||
| const env = { ...process.env } | ||
| delete env.SUPERCLI_SERVER | ||
| const out = execSync(`node ${CLI} ${args}`, { | ||
| encoding: "utf-8", | ||
| timeout: 15000, | ||
| env: { ...env, ...(options.env || {}) } | ||
| }) | ||
| return { ok: true, output: out.trim(), code: 0 } | ||
| } catch (err) { | ||
| return { | ||
| ok: false, | ||
| output: (err.stdout || "").trim(), | ||
| stderr: (err.stderr || "").trim(), | ||
| code: err.status | ||
| } | ||
| } | ||
| } | ||
|
|
||
| function writeFakeChainloopBinary(dir) { | ||
| const bin = path.join(dir, "chainloop") | ||
| fs.writeFileSync(bin, [ | ||
| "#!/usr/bin/env node", | ||
| "const args = process.argv.slice(2);", | ||
| "if (args[0] === 'version') { console.log('chainloop 0.1.0-test'); process.exit(0); }", | ||
| "console.log(JSON.stringify({ ok: true, args }));" | ||
| ].join("\n"), "utf-8") | ||
| fs.chmodSync(bin, 0o755) | ||
| return bin | ||
| } | ||
|
|
||
| describe("chainloop plugin", () => { | ||
| const fakeDir = fs.mkdtempSync(path.join(os.tmpdir(), "dcli-chainloop-")) | ||
| const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "dcli-home-chainloop-")) | ||
| writeFakeChainloopBinary(fakeDir) | ||
| const env = { ...process.env, PATH: `${fakeDir}:${process.env.PATH || ""}`, SUPERCLI_HOME: tempHome } | ||
|
|
||
| beforeAll(() => { | ||
| runNoServer("plugins install ./plugins/chainloop --on-conflict replace --json", { env }) | ||
| }) | ||
|
|
||
| afterAll(() => { | ||
| runNoServer("plugins remove chainloop --json", { env }) | ||
| fs.rmSync(fakeDir, { recursive: true, force: true }) | ||
| fs.rmSync(tempHome, { recursive: true, force: true }) | ||
| }) | ||
|
|
||
| test("routes namespace passthrough to the chainloop binary", () => { | ||
| const r = runNoServer("chainloop version --json", { env }) | ||
| expect(r.ok).toBe(true) | ||
| const data = JSON.parse(r.output) | ||
| expect(data.command).toBe("chainloop.passthrough") | ||
| expect(data.data.raw).toBe("chainloop 0.1.0-test") | ||
| }) | ||
|
|
||
| test("forwards arbitrary flags through passthrough", () => { | ||
| const r = runNoServer("chainloop workflow list --output json", { env }) | ||
| expect(r.ok).toBe(true) | ||
| const data = JSON.parse(r.output) | ||
| expect(data.command).toBe("chainloop.passthrough") | ||
| expect(data.data.args).toContain("workflow") | ||
| expect(data.data.args).toContain("list") | ||
| expect(data.data.args).toContain("--output") | ||
| }) | ||
|
|
||
| test("doctor reports chainloop dependency as healthy", () => { | ||
| const r = runNoServer("plugins doctor chainloop --json", { env }) | ||
| expect(r.ok).toBe(true) | ||
| const data = JSON.parse(r.output) | ||
| expect(data.ok).toBe(true) | ||
| expect(data.checks.some(c => c.type === "binary" && c.binary === "chainloop" && c.ok === true)).toBe(true) | ||
| }) | ||
| }) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| const fs = require("fs") | ||
| const os = require("os") | ||
| const path = require("path") | ||
| const { execSync } = require("child_process") | ||
|
|
||
| const CLI = path.join(__dirname, "..", "cli", "supercli.js") | ||
|
|
||
| function runNoServer(args, options = {}) { | ||
| try { | ||
| const env = { ...process.env } | ||
| delete env.SUPERCLI_SERVER | ||
| const out = execSync(`node ${CLI} ${args}`, { | ||
| encoding: "utf-8", | ||
| timeout: 15000, | ||
| env: { ...env, ...(options.env || {}) } | ||
| }) | ||
|
Comment on lines
+12
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Use 🧰 Tools🪛 OpenGrep (1.23.0)[ERROR] 12-16: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead. (coderabbit.command-injection.exec-js) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
| return { ok: true, output: out.trim(), code: 0 } | ||
| } catch (err) { | ||
| return { | ||
| ok: false, | ||
| output: (err.stdout || "").trim(), | ||
| stderr: (err.stderr || "").trim(), | ||
| code: err.status | ||
| } | ||
| } | ||
| } | ||
|
|
||
| function writeFakeOvermindBinary(dir) { | ||
| const bin = path.join(dir, "overmind") | ||
| fs.writeFileSync(bin, [ | ||
| "#!/usr/bin/env node", | ||
| "const args = process.argv.slice(2);", | ||
| "if (args[0] === 'version') { console.log('overmind 2.5.1-test'); process.exit(0); }", | ||
| "console.log(JSON.stringify({ ok: true, args }));" | ||
| ].join("\n"), "utf-8") | ||
| fs.chmodSync(bin, 0o755) | ||
| return bin | ||
| } | ||
|
|
||
| describe("overmind plugin", () => { | ||
| const fakeDir = fs.mkdtempSync(path.join(os.tmpdir(), "dcli-overmind-")) | ||
| const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "dcli-home-overmind-")) | ||
| writeFakeOvermindBinary(fakeDir) | ||
| const env = { ...process.env, PATH: `${fakeDir}:${process.env.PATH || ""}`, SUPERCLI_HOME: tempHome } | ||
|
|
||
| beforeAll(() => { | ||
| runNoServer("plugins install ./plugins/overmind --on-conflict replace --json", { env }) | ||
| }) | ||
|
|
||
| afterAll(() => { | ||
| runNoServer("plugins remove overmind --json", { env }) | ||
| fs.rmSync(fakeDir, { recursive: true, force: true }) | ||
| fs.rmSync(tempHome, { recursive: true, force: true }) | ||
| }) | ||
|
|
||
| test("routes namespace passthrough to the overmind binary", () => { | ||
| const r = runNoServer("overmind version --json", { env }) | ||
| expect(r.ok).toBe(true) | ||
| const data = JSON.parse(r.output) | ||
| expect(data.command).toBe("overmind.passthrough") | ||
| expect(data.data.raw).toBe("overmind 2.5.1-test") | ||
| }) | ||
|
|
||
| test("forwards subcommands and flags through passthrough", () => { | ||
| const r = runNoServer("overmind start -f ./Procfile.dev", { env }) | ||
| expect(r.ok).toBe(true) | ||
| const data = JSON.parse(r.output) | ||
| expect(data.command).toBe("overmind.passthrough") | ||
| expect(data.data.args).toContain("start") | ||
| expect(data.data.args).toContain("-f") | ||
| expect(data.data.args).toContain("./Procfile.dev") | ||
| }) | ||
|
|
||
| test("doctor reports overmind dependency as healthy", () => { | ||
| const r = runNoServer("plugins doctor overmind --json", { env }) | ||
| expect(r.ok).toBe(true) | ||
| const data = JSON.parse(r.output) | ||
| expect(data.ok).toBe(true) | ||
| expect(data.checks.some(c => c.type === "binary" && c.binary === "overmind" && c.ok === true)).toBe(true) | ||
| }) | ||
| }) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| const fs = require("fs") | ||
| const os = require("os") | ||
| const path = require("path") | ||
| const { execSync } = require("child_process") | ||
|
|
||
| const CLI = path.join(__dirname, "..", "cli", "supercli.js") | ||
|
|
||
| function runNoServer(args, options = {}) { | ||
| try { | ||
| const env = { ...process.env } | ||
| delete env.SUPERCLI_SERVER | ||
| const out = execSync(`node ${CLI} ${args}`, { | ||
| encoding: "utf-8", | ||
| timeout: 15000, | ||
| env: { ...env, ...(options.env || {}) } | ||
| }) | ||
|
Comment on lines
+12
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Use 🧰 Tools🪛 OpenGrep (1.23.0)[ERROR] 12-16: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead. (coderabbit.command-injection.exec-js) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
| return { ok: true, output: out.trim(), code: 0 } | ||
| } catch (err) { | ||
| return { | ||
| ok: false, | ||
| output: (err.stdout || "").trim(), | ||
| stderr: (err.stderr || "").trim(), | ||
| code: err.status | ||
| } | ||
| } | ||
| } | ||
|
|
||
| function writeFakeResvgBinary(dir) { | ||
| const bin = path.join(dir, "resvg") | ||
| fs.writeFileSync(bin, [ | ||
| "#!/usr/bin/env node", | ||
| "const args = process.argv.slice(2);", | ||
| "if (args[0] === 'version') { console.log('resvg 0.44.0-test'); process.exit(0); }", | ||
| "console.log(JSON.stringify({ ok: true, args }));" | ||
| ].join("\n"), "utf-8") | ||
| fs.chmodSync(bin, 0o755) | ||
| return bin | ||
| } | ||
|
|
||
| describe("resvg plugin", () => { | ||
| const fakeDir = fs.mkdtempSync(path.join(os.tmpdir(), "dcli-resvg-")) | ||
| const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "dcli-home-resvg-")) | ||
| writeFakeResvgBinary(fakeDir) | ||
| const env = { ...process.env, PATH: `${fakeDir}:${process.env.PATH || ""}`, SUPERCLI_HOME: tempHome } | ||
|
|
||
| beforeAll(() => { | ||
| runNoServer("plugins install ./plugins/resvg --on-conflict replace --json", { env }) | ||
| }) | ||
|
|
||
| afterAll(() => { | ||
| runNoServer("plugins remove resvg --json", { env }) | ||
| fs.rmSync(fakeDir, { recursive: true, force: true }) | ||
| fs.rmSync(tempHome, { recursive: true, force: true }) | ||
| }) | ||
|
|
||
| test("routes namespace passthrough to the resvg binary", () => { | ||
| const r = runNoServer("resvg version --json", { env }) | ||
| expect(r.ok).toBe(true) | ||
| const data = JSON.parse(r.output) | ||
| expect(data.command).toBe("resvg.passthrough") | ||
| expect(data.data.raw).toBe("resvg 0.44.0-test") | ||
| }) | ||
|
|
||
| test("forwards conversion arguments through passthrough", () => { | ||
| const r = runNoServer("resvg input.svg output.png --width 512", { env }) | ||
| expect(r.ok).toBe(true) | ||
| const data = JSON.parse(r.output) | ||
| expect(data.command).toBe("resvg.passthrough") | ||
| expect(data.data.args).toContain("input.svg") | ||
| expect(data.data.args).toContain("output.png") | ||
| expect(data.data.args).toContain("--width") | ||
| }) | ||
|
|
||
| test("doctor reports resvg dependency as healthy", () => { | ||
| const r = runNoServer("plugins doctor resvg --json", { env }) | ||
| expect(r.ok).toBe(true) | ||
| const data = JSON.parse(r.output) | ||
| expect(data.ok).toBe(true) | ||
| expect(data.checks.some(c => c.type === "binary" && c.binary === "resvg" && c.ok === true)).toBe(true) | ||
| }) | ||
| }) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
beforeAlldoes not assert plugin install success across all three test files. None of the three suites check the return value ofrunNoServer("plugins install ..."); if installation fails, every downstream test fails with crypticJSON.parseerrors instead of a clear failure message. The shared root cause is the same unchecked install call in each file.__tests__/chainloop-plugin.test.js#L46-L48: capture the result and addexpect(r.ok).toBe(true).__tests__/overmind-plugin.test.js#L46-L48: same change.__tests__/resvg-plugin.test.js#L46-L48: same change.📍 Affects 3 files
__tests__/chainloop-plugin.test.js#L46-L48(this comment)__tests__/overmind-plugin.test.js#L46-L48__tests__/resvg-plugin.test.js#L46-L48🤖 Prompt for AI Agents