Skip to content

Commit 439fa8a

Browse files
test(selfhost): cover env_get and require_cmd in deploy-common lib (#7769)
Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 2ac97ad commit 439fa8a

1 file changed

Lines changed: 83 additions & 0 deletions

File tree

test/unit/selfhost-deploy-common.test.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,3 +227,86 @@ printf 'REACHED_END args=[%s]\\n' "\${compose_args[*]}"
227227
expect(result.stderr).toContain("compose file not found: missing.yml");
228228
});
229229
});
230+
231+
// Generic seam for the remaining library functions (#7769): source the lib in a scratch dir, invoke one
232+
// function with the given args, and return its status/stdout/stderr -- the same spawn-a-real-bash approach as
233+
// createHarness above, but parameterized so env_get/require_cmd can each be driven directly.
234+
// (compose_file_args is already covered by the #7765 suite above.)
235+
function runLibFn(call: string, options: { env?: Record<string, string>; files?: Record<string, string> } = {}) {
236+
const dir = mkdtempSync(join(tmpdir(), "loopover-selfhost-deploy-common-fn-"));
237+
try {
238+
for (const [name, contents] of Object.entries(options.files ?? {})) {
239+
writeFileSync(join(dir, name), contents);
240+
}
241+
const scriptPath = join(dir, "run.sh");
242+
// set -u is deliberately NOT enabled: env_get reads optional ENV_FILE with `${VAR:-default}` guards,
243+
// exactly as the real deploy scripts do.
244+
writeFileSync(scriptPath, `#!/usr/bin/env bash\nset -eo pipefail\n. "${libPath.replace(/\\/g, "/")}"\n${call}\n`);
245+
chmodSync(scriptPath, 0o755);
246+
return spawnSync("bash", [scriptPath], {
247+
cwd: dir,
248+
encoding: "utf8",
249+
env: { ...process.env, ...(options.env ?? {}) },
250+
});
251+
} finally {
252+
rmSync(dir, { recursive: true, force: true });
253+
}
254+
}
255+
256+
describe("require_cmd (#7769)", () => {
257+
it("succeeds silently when the command exists", () => {
258+
const result = runLibFn("require_cmd bash");
259+
expect(result.status, result.stderr).toBe(0);
260+
expect(result.stderr).toBe("");
261+
});
262+
263+
it("fails with exit 1 and a clear error when the command is missing", () => {
264+
const result = runLibFn("require_cmd loopover-definitely-not-a-real-command");
265+
expect(result.status).toBe(1);
266+
expect(result.stderr).toContain("required command not found: loopover-definitely-not-a-real-command");
267+
});
268+
});
269+
270+
describe("env_get (#7769)", () => {
271+
it("returns a plain unquoted value for a matching key", () => {
272+
const result = runLibFn('env_get FOO "$PWD/.env"', { files: { ".env": "FOO=bar\n" } });
273+
expect(result.status, result.stderr).toBe(0);
274+
expect(result.stdout).toBe("bar\n");
275+
});
276+
277+
it("strips surrounding double and single quotes from the value", () => {
278+
const dq = runLibFn('env_get FOO "$PWD/.env"', { files: { ".env": 'FOO="quoted value"\n' } });
279+
expect(dq.status, dq.stderr).toBe(0);
280+
expect(dq.stdout).toBe("quoted value\n");
281+
282+
const sq = runLibFn('env_get FOO "$PWD/.env"', { files: { ".env": "FOO='single quoted'\n" } });
283+
expect(sq.status, sq.stderr).toBe(0);
284+
expect(sq.stdout).toBe("single quoted\n");
285+
});
286+
287+
it("skips comment and blank lines and returns the first matching key", () => {
288+
const result = runLibFn('env_get FOO "$PWD/.env"', { files: { ".env": "# a comment\n\n FOO = spaced\nFOO=second\n" } });
289+
expect(result.status, result.stderr).toBe(0);
290+
expect(result.stdout).toBe("spaced\n");
291+
});
292+
293+
it("returns exit 1 when the key is absent from the file", () => {
294+
const result = runLibFn('env_get MISSING "$PWD/.env"', { files: { ".env": "FOO=bar\n" } });
295+
expect(result.status).toBe(1);
296+
expect(result.stdout).toBe("");
297+
});
298+
299+
it("returns exit 1 when the file does not exist", () => {
300+
const result = runLibFn('env_get FOO "$PWD/does-not-exist.env"');
301+
expect(result.status).toBe(1);
302+
});
303+
304+
it("falls back to $ENV_FILE when no file argument is given", () => {
305+
const result = runLibFn("env_get FOO", {
306+
files: { ".env": "FOO=from-env-file\n" },
307+
env: { ENV_FILE: ".env" },
308+
});
309+
expect(result.status, result.stderr).toBe(0);
310+
expect(result.stdout).toBe("from-env-file\n");
311+
});
312+
});

0 commit comments

Comments
 (0)