Skip to content
Draft
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
81 changes: 81 additions & 0 deletions __tests__/chainloop-plugin.test.js
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 })
})
Comment on lines +46 to +48

Copy link
Copy Markdown

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

beforeAll does not assert plugin install success across all three test files. None of the three suites check the return value of runNoServer("plugins install ..."); if installation fails, every downstream test fails with cryptic JSON.parse errors 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 add expect(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
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@__tests__/chainloop-plugin.test.js` around lines 46 - 48, Unchecked plugin
installation results obscure setup failures. In the beforeAll install setup,
capture the result of runNoServer and assert r.ok is true in
__tests__/chainloop-plugin.test.js lines 46-48,
__tests__/overmind-plugin.test.js lines 46-48, and
__tests__/resvg-plugin.test.js lines 46-48; apply the same change in all three
suites.


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)
})
})
81 changes: 81 additions & 0 deletions __tests__/overmind-plugin.test.js
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

Copy link
Copy Markdown

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

execSync with string interpolation — same issue as chainloop-plugin.test.js.

Use execFileSync with an argument array to avoid shell interpretation of the CLI path.

🧰 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 Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@__tests__/overmind-plugin.test.js` around lines 12 - 16, Replace the
interpolated execSync invocation in the test helper with execFileSync, passing
CLI and the command arguments as separate array entries while preserving
encoding, timeout, and merged environment options.

Source: 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)
})
})
81 changes: 81 additions & 0 deletions __tests__/resvg-plugin.test.js
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

Copy link
Copy Markdown

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

execSync with string interpolation — same issue as chainloop-plugin.test.js.

Use execFileSync with an argument array to avoid shell interpretation of the CLI path.

🧰 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 Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@__tests__/resvg-plugin.test.js` around lines 12 - 16, Replace the
interpolated command invocation in the test helper with execFileSync, passing
CLI as the executable and args as an argument array. Preserve the existing
encoding, timeout, and merged environment options while avoiding shell
interpretation of the CLI path.

Source: 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)
})
})
Loading