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
69 changes: 69 additions & 0 deletions __tests__/httprobe-plugin.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Quote the CLI path to prevent errors if the directory contains spaces.

The CLI path is directly interpolated into the execSync shell command. If the project is cloned into a directory path that contains spaces (e.g., /Users/dev/my projects/cli), the command will fail to execute.

  • __tests__/httprobe-plugin.test.js#L12-L16: wrap ${CLI} in quotes: execSync(\node "${CLI}" ${args}`, {`
  • __tests__/lazysql-plugin.test.js#L12-L16: wrap ${CLI} in quotes: execSync(\node "${CLI}" ${args}`, {`
  • __tests__/sq-plugin.test.js#L12-L16: wrap ${CLI} in quotes: execSync(\node "${CLI}" ${args}`, {`
🧰 Tools
🪛 OpenGrep (1.25.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)

📍 Affects 3 files
  • __tests__/httprobe-plugin.test.js#L12-L16 (this comment)
  • __tests__/lazysql-plugin.test.js#L12-L16
  • __tests__/sq-plugin.test.js#L12-L16
🤖 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__/httprobe-plugin.test.js` around lines 12 - 16, Quote the
interpolated CLI path in the execSync command so paths containing spaces execute
correctly. Apply this change in __tests__/httprobe-plugin.test.js lines 12-16,
__tests__/lazysql-plugin.test.js lines 12-16, and __tests__/sq-plugin.test.js
lines 12-16; each test’s CLI invocation should preserve the existing arguments
and options.

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 writeFakeHttprobeBinary(dir) {
const bin = path.join(dir, "httprobe")
fs.writeFileSync(bin, [
"#!/usr/bin/env node",
"console.log('https://example.com');"
].join("\n"), "utf-8")
fs.chmodSync(bin, 0o755)
return bin
}

describe("httprobe plugin", () => {
const fakeDir = fs.mkdtempSync(path.join(os.tmpdir(), "dcli-httprobe-"))
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "dcli-home-httprobe-"))
writeFakeHttprobeBinary(fakeDir)
const env = { ...process.env, PATH: `${fakeDir}:${process.env.PATH || ""}`, SUPERCLI_HOME: tempHome }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use path.delimiter to support cross-platform test execution.

Hardcoding : as the PATH separator works on Unix but will break the PATH environment variable if the tests are run on Windows, which uses ;. Use Node's built-in path.delimiter instead.

  • __tests__/httprobe-plugin.test.js#L42-L42: change to PATH: \${fakeDir}${path.delimiter}${process.env.PATH || ""}``
  • __tests__/lazysql-plugin.test.js#L44-L44: change to PATH: \${fakeDir}${path.delimiter}${process.env.PATH || ""}``
  • __tests__/sq-plugin.test.js#L44-L44: change to PATH: \${fakeDir}${path.delimiter}${process.env.PATH || ""}``
📍 Affects 3 files
  • __tests__/httprobe-plugin.test.js#L42-L42 (this comment)
  • __tests__/lazysql-plugin.test.js#L44-L44
  • __tests__/sq-plugin.test.js#L44-L44
🤖 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__/httprobe-plugin.test.js` at line 42, Replace the hardcoded colon
PATH separator with Node’s path.delimiter in the env setup for
__tests__/httprobe-plugin.test.js (42-42), __tests__/lazysql-plugin.test.js
(44-44), and __tests__/sq-plugin.test.js (44-44); ensure each file imports or
reuses the path module so PATH construction works on all platforms.


beforeAll(() => {
runNoServer("plugins install ./plugins/httprobe --on-conflict replace --json", { env })
})

afterAll(() => {
runNoServer("plugins remove httprobe --json", { env })
fs.rmSync(fakeDir, { recursive: true, force: true })
fs.rmSync(tempHome, { recursive: true, force: true })
})

test("routes the domain probe command", () => {
const r = runNoServer("httprobe domain probe --json", { env })
expect(r.ok).toBe(true)
const data = JSON.parse(r.output)
expect(data.command).toBe("httprobe.domain.probe")
expect(data.data.raw).toBe("https://example.com")
})

test("doctor reports httprobe dependency as healthy", () => {
const r = runNoServer("plugins doctor httprobe --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 === "httprobe" && c.ok === true)).toBe(true)
})
})
89 changes: 89 additions & 0 deletions __tests__/lazysql-plugin.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
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 writeFakeLazysqlBinary(dir) {
const bin = path.join(dir, "lazysql")
fs.writeFileSync(bin, [
"#!/usr/bin/env node",
"const args = process.argv.slice(2);",
"if (args[0] === '--version') { console.log('lazysql v0.3.0-test'); process.exit(0); }",
"console.log(JSON.stringify({ ok: true, args }));"
].join("\n"), "utf-8")
fs.chmodSync(bin, 0o755)
return bin
}

describe("lazysql plugin", () => {
const fakeDir = fs.mkdtempSync(path.join(os.tmpdir(), "dcli-lazysql-"))
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "dcli-home-lazysql-"))
writeFakeLazysqlBinary(fakeDir)
const env = { ...process.env, PATH: `${fakeDir}:${process.env.PATH || ""}`, SUPERCLI_HOME: tempHome }

beforeAll(() => {
runNoServer("plugins install ./plugins/lazysql --on-conflict replace --json", { env })
})

afterAll(() => {
runNoServer("plugins remove lazysql --json", { env })
fs.rmSync(fakeDir, { recursive: true, force: true })
fs.rmSync(tempHome, { recursive: true, force: true })
})

test("routes the self version command", () => {
const r = runNoServer("lazysql self version --json", { env })
expect(r.ok).toBe(true)
const data = JSON.parse(r.output)
expect(data.command).toBe("lazysql.self.version")
expect(data.data.raw).toBe("lazysql v0.3.0-test")
})

test("routes connection open with a positional url", () => {
const r = runNoServer("lazysql connection open postgres://x --json", { env })
expect(r.ok).toBe(true)
const data = JSON.parse(r.output)
expect(data.command).toBe("lazysql.connection.open")
const inner = JSON.parse(data.data.raw)
expect(inner.args).toContain("postgres://x")
})

test("supports namespace passthrough for unknown subcommands", () => {
const r = runNoServer("lazysql somecmd --json", { env })
expect(r.ok).toBe(true)
const data = JSON.parse(r.output)
expect(data.command).toBe("lazysql.passthrough")
const inner = JSON.parse(data.data.raw)
expect(inner.args).toContain("somecmd")
})

test("doctor reports lazysql dependency as healthy", () => {
const r = runNoServer("plugins doctor lazysql --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 === "lazysql" && c.ok === true)).toBe(true)
})
})
82 changes: 82 additions & 0 deletions __tests__/sq-plugin.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
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 writeFakeSqBinary(dir) {
const bin = path.join(dir, "sq")
fs.writeFileSync(bin, [
"#!/usr/bin/env node",
"const args = process.argv.slice(2);",
"if (args[0] === 'version') { console.log('sq v0.48.0-test'); process.exit(0); }",
"console.log(JSON.stringify({ ok: true, args }));"
].join("\n"), "utf-8")
fs.chmodSync(bin, 0o755)
return bin
}

describe("sq plugin", () => {
const fakeDir = fs.mkdtempSync(path.join(os.tmpdir(), "dcli-sq-"))
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "dcli-home-sq-"))
writeFakeSqBinary(fakeDir)
const env = { ...process.env, PATH: `${fakeDir}:${process.env.PATH || ""}`, SUPERCLI_HOME: tempHome }

beforeAll(() => {
runNoServer("plugins install ./plugins/sq --on-conflict replace --json", { env })
})

afterAll(() => {
runNoServer("plugins remove sq --json", { env })
fs.rmSync(fakeDir, { recursive: true, force: true })
fs.rmSync(tempHome, { recursive: true, force: true })
})

test("routes a subcommand through namespace passthrough", () => {
const r = runNoServer("sq inspect --json", { env })
expect(r.ok).toBe(true)
const data = JSON.parse(r.output)
expect(data.command).toBe("sq.passthrough")
expect(data.data.args).toContain("inspect")
expect(data.data.args).toContain("--json")
})

test("passes multiple arguments through in order", () => {
const r = runNoServer("sq .data --format json --json", { env })
expect(r.ok).toBe(true)
const data = JSON.parse(r.output)
expect(data.command).toBe("sq.passthrough")
expect(data.data.args).toContain(".data")
expect(data.data.args).toContain("--format")
expect(data.data.args).toContain("json")
})

test("doctor reports sq dependency as healthy", () => {
const r = runNoServer("plugins doctor sq --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 === "sq" && c.ok === true)).toBe(true)
})
})
Loading