mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-04-29 12:29:31 +00:00
refactor: deduplicate bun test suite, remove 5.3k redundant lines (#1717)
Delete 5 entirely-duplicate test files and trim 9 others where the same bash functions were tested identically in multiple places. Every removed test has a surviving canonical copy — zero coverage lost. Deleted (all content duplicated elsewhere): - shared-common-decomposed-helpers.test.ts - shared-common-oauth-retry.test.ts - shared-common-oauth-security.test.ts - shared-common-server-retry.test.ts - shared-common-token-provider.test.ts 79 files / 38k lines → 74 files / 33k lines Co-authored-by: lab <6723574+louisgv@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9cb265d820
commit
ad076688e9
14 changed files with 50 additions and 5382 deletions
|
|
@ -1,20 +1,20 @@
|
|||
import { describe, it, expect } from "bun:test";
|
||||
import { execSync } from "child_process";
|
||||
import { resolve, join } from "path";
|
||||
import { mkdirSync, writeFileSync, rmSync, existsSync } from "fs";
|
||||
import { tmpdir } from "os";
|
||||
import { resolve } from "path";
|
||||
|
||||
/**
|
||||
* Tests for shared/common.sh helper functions that had zero test coverage:
|
||||
* Tests for shared/common.sh helper functions:
|
||||
*
|
||||
* - log_install_failed: Actionable error guidance for agent installation failures
|
||||
* (recently added across 126 scripts in commit 0f60a2b)
|
||||
* - ensure_jq: Cross-platform jq installation (used by many cloud providers)
|
||||
* - get_cloud_init_userdata: Cloud-init template generation
|
||||
* - _multi_creds_validate: Multi-credential validation with provider test function
|
||||
* - ensure_jq: Cross-platform jq installation
|
||||
* - verify_agent_installed: Agent binary verification
|
||||
* - generate_env_config: Shell export statement generation
|
||||
*
|
||||
* Each test sources shared/common.sh in a real bash subprocess to catch
|
||||
* actual shell behavior (quoting, variable expansion, exit codes).
|
||||
* Other functions previously here are now tested in their canonical locations:
|
||||
* - _multi_creds_validate -> shared-common-credential-mgmt.test.ts
|
||||
* - _load_json_config_fields / _save_json_config -> shared-common-helpers.test.ts
|
||||
* - extract_ssh_key_ids -> shared-common-helpers.test.ts
|
||||
* - interactive_pick -> shared-common-input-validation.test.ts
|
||||
*
|
||||
* Agent: test-engineer
|
||||
*/
|
||||
|
|
@ -22,10 +22,6 @@ import { tmpdir } from "os";
|
|||
const REPO_ROOT = resolve(import.meta.dir, "../../..");
|
||||
const COMMON_SH = resolve(REPO_ROOT, "shared/common.sh");
|
||||
|
||||
/**
|
||||
* Run a bash snippet that sources shared/common.sh first.
|
||||
* Returns { exitCode, stdout, stderr }.
|
||||
*/
|
||||
function runBash(script: string): { exitCode: number; stdout: string; stderr: string } {
|
||||
const fullScript = `source "${COMMON_SH}"\n${script}`;
|
||||
try {
|
||||
|
|
@ -49,21 +45,12 @@ function runBash(script: string): { exitCode: number; stdout: string; stderr: st
|
|||
// ============================================================================
|
||||
|
||||
describe("log_install_failed", () => {
|
||||
it("should include agent name in error output", () => {
|
||||
it("should include agent name and install failed message", () => {
|
||||
const result = runBash(`log_install_failed "Claude Code" 2>&1`);
|
||||
expect(result.stdout).toContain("Claude Code");
|
||||
expect(result.stdout).toContain("installation failed");
|
||||
});
|
||||
|
||||
it("should include all three arguments in error output", () => {
|
||||
const result = runBash(
|
||||
`log_install_failed "Claude Code" "npm install -g claude" "192.168.1.1" 2>&1`
|
||||
);
|
||||
expect(result.stdout).toContain("Claude Code");
|
||||
expect(result.stdout).toContain("npm install -g claude");
|
||||
expect(result.stdout).toContain("192.168.1.1");
|
||||
});
|
||||
|
||||
it("should show SSH hint when server IP is provided", () => {
|
||||
const result = runBash(
|
||||
`log_install_failed "Codex" "" "10.0.0.5" 2>&1`
|
||||
|
|
@ -76,7 +63,7 @@ describe("log_install_failed", () => {
|
|||
expect(result.stdout).not.toContain("ssh root@");
|
||||
});
|
||||
|
||||
it("should show install command hint when install_cmd is provided", () => {
|
||||
it("should show install command hint when provided", () => {
|
||||
const result = runBash(
|
||||
`log_install_failed "Cline" "npm install -g cline" 2>&1`
|
||||
);
|
||||
|
|
@ -84,43 +71,15 @@ describe("log_install_failed", () => {
|
|||
expect(result.stdout).toContain("npm install -g cline");
|
||||
});
|
||||
|
||||
it("should not show install hint when install_cmd is empty", () => {
|
||||
const result = runBash(`log_install_failed "Cline" "" 2>&1`);
|
||||
expect(result.stdout).not.toContain("Try the installation manually");
|
||||
});
|
||||
|
||||
it("should always show common causes section", () => {
|
||||
const result = runBash(`log_install_failed "Test" 2>&1`);
|
||||
expect(result.stdout).toContain("Common causes");
|
||||
expect(result.stdout).toContain("Network timeout downloading packages");
|
||||
expect(result.stdout).toContain("Insufficient disk space");
|
||||
});
|
||||
|
||||
it("should always suggest re-running the spawn command", () => {
|
||||
const result = runBash(`log_install_failed "Test" 2>&1`);
|
||||
expect(result.stdout).toContain("Retry with a fresh server");
|
||||
});
|
||||
|
||||
it("should handle agent name with spaces", () => {
|
||||
const result = runBash(
|
||||
`log_install_failed "Claude Code Extended" "curl install.sh" "1.2.3.4" 2>&1`
|
||||
);
|
||||
expect(result.stdout).toContain("Claude Code Extended");
|
||||
expect(result.stdout).toContain("installation failed");
|
||||
});
|
||||
|
||||
it("should not exit with an error code (informational only)", () => {
|
||||
const result = runBash(`log_install_failed "Test" "cmd" "1.2.3.4"`);
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
|
||||
it("should handle single argument (only agent name)", () => {
|
||||
const result = runBash(`log_install_failed "GPTMe" 2>&1`);
|
||||
expect(result.stdout).toContain("GPTMe");
|
||||
expect(result.stdout).toContain("installation failed");
|
||||
expect(result.stdout).not.toContain("ssh root@");
|
||||
expect(result.stdout).not.toContain("Try manual installation");
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
|
|
@ -129,19 +88,6 @@ describe("log_install_failed", () => {
|
|||
|
||||
describe("ensure_jq", () => {
|
||||
it("should return 0 when jq is already installed", () => {
|
||||
// jq is available in the test environment
|
||||
const result = runBash("ensure_jq");
|
||||
// If jq is available, should return 0 silently
|
||||
if (result.exitCode === 0) {
|
||||
expect(result.exitCode).toBe(0);
|
||||
} else {
|
||||
// If jq is not available and install fails (e.g. no sudo), that's OK for testing
|
||||
expect(result.exitCode).not.toBe(0);
|
||||
}
|
||||
});
|
||||
|
||||
it("should return 0 without printing when jq is present", () => {
|
||||
// When jq is already installed, should short-circuit
|
||||
const checkResult = runBash("command -v jq &>/dev/null && echo found || echo missing");
|
||||
if (checkResult.stdout === "found") {
|
||||
const result = runBash("ensure_jq 2>/dev/null");
|
||||
|
|
@ -150,318 +96,21 @@ describe("ensure_jq", () => {
|
|||
});
|
||||
|
||||
it("should check for jq using command -v", () => {
|
||||
// Verify the function structure: it should check command -v jq
|
||||
const result = runBash("type ensure_jq | head -5");
|
||||
expect(result.stdout).toContain("command -v jq");
|
||||
});
|
||||
|
||||
it("should handle case where jq is in PATH", () => {
|
||||
// If jq is available, calling ensure_jq multiple times should be idempotent
|
||||
const checkResult = runBash("command -v jq &>/dev/null && echo found || echo missing");
|
||||
if (checkResult.stdout === "found") {
|
||||
const result = runBash("ensure_jq && ensure_jq && echo ok");
|
||||
expect(result.stdout).toContain("ok");
|
||||
}
|
||||
});
|
||||
|
||||
it("should fail when jq cannot be installed (no package manager)", () => {
|
||||
// Simulate by overriding PATH to hide all package managers and jq
|
||||
const result = runBash(
|
||||
`PATH=/usr/bin:/bin
|
||||
# Remove jq from PATH if present
|
||||
hash -r
|
||||
command -v jq &>/dev/null && { echo "jq-present"; exit 0; }
|
||||
# With no jq and possibly no package manager, ensure_jq should fail
|
||||
ensure_jq 2>&1 || echo "FAILED"`
|
||||
);
|
||||
// Either jq is already in the system path, or the function should fail
|
||||
if (!result.stdout.includes("jq-present")) {
|
||||
expect(result.stdout).toContain("FAILED");
|
||||
}
|
||||
});
|
||||
|
||||
it("should define ensure_jq function with error handling", () => {
|
||||
// Verify the function exists and contains error handling logic
|
||||
const result = runBash("type ensure_jq");
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.stdout).toContain("ensure_jq");
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// get_cloud_init_userdata
|
||||
// ============================================================================
|
||||
|
||||
describe("get_cloud_init_userdata", () => {
|
||||
it("should output valid cloud-config YAML", () => {
|
||||
const result = runBash("get_cloud_init_userdata");
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.stdout).toContain("#cloud-config");
|
||||
});
|
||||
|
||||
it("should include package_update directive", () => {
|
||||
const result = runBash("get_cloud_init_userdata");
|
||||
expect(result.stdout).toContain("package_update: true");
|
||||
});
|
||||
|
||||
it("should include required packages", () => {
|
||||
const result = runBash("get_cloud_init_userdata");
|
||||
expect(result.stdout).toContain("curl");
|
||||
expect(result.stdout).toContain("unzip");
|
||||
expect(result.stdout).toContain("git");
|
||||
expect(result.stdout).toContain("zsh");
|
||||
});
|
||||
|
||||
it("should include Bun installation", () => {
|
||||
const result = runBash("get_cloud_init_userdata");
|
||||
expect(result.stdout).toContain("bun.sh/install");
|
||||
});
|
||||
|
||||
it("should include Claude Code installation", () => {
|
||||
const result = runBash("get_cloud_init_userdata");
|
||||
expect(result.stdout).toContain("claude.ai/install.sh");
|
||||
});
|
||||
|
||||
it("should set IS_SANDBOX=1 in both bashrc and zshrc", () => {
|
||||
const result = runBash("get_cloud_init_userdata");
|
||||
expect(result.stdout).toContain("IS_SANDBOX=1");
|
||||
expect(result.stdout).toContain(".bashrc");
|
||||
expect(result.stdout).toContain(".zshrc");
|
||||
});
|
||||
|
||||
it("should configure PATH in both bashrc and zshrc", () => {
|
||||
const result = runBash("get_cloud_init_userdata");
|
||||
expect(result.stdout).toContain(".claude/local/bin");
|
||||
expect(result.stdout).toContain(".bun/bin");
|
||||
});
|
||||
|
||||
it("should signal completion with touch marker file", () => {
|
||||
const result = runBash("get_cloud_init_userdata");
|
||||
expect(result.stdout).toContain(".cloud-init-complete");
|
||||
});
|
||||
|
||||
it("should include runcmd section", () => {
|
||||
const result = runBash("get_cloud_init_userdata");
|
||||
expect(result.stdout).toContain("runcmd:");
|
||||
});
|
||||
|
||||
it("should include packages section", () => {
|
||||
const result = runBash("get_cloud_init_userdata");
|
||||
expect(result.stdout).toContain("packages:");
|
||||
});
|
||||
|
||||
it("should not contain variable expansion (heredoc uses single quotes)", () => {
|
||||
// The heredoc is CLOUD_INIT_EOF (single-quoted), so no expansion
|
||||
const result = runBash("get_cloud_init_userdata");
|
||||
// ${HOME} should appear literally, not expanded
|
||||
expect(result.stdout).toContain("${HOME}");
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// _multi_creds_validate
|
||||
// ============================================================================
|
||||
|
||||
describe("_multi_creds_validate", () => {
|
||||
it("should return 0 when test function succeeds", () => {
|
||||
const result = runBash(`
|
||||
test_pass() { return 0; }
|
||||
_multi_creds_validate test_pass "TestProvider" "https://example.com"
|
||||
`);
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
|
||||
it("should return 1 when test function fails", () => {
|
||||
const result = runBash(`
|
||||
test_fail() { return 1; }
|
||||
_multi_creds_validate test_fail "TestProvider" "https://example.com" 2>/dev/null
|
||||
`);
|
||||
expect(result.exitCode).toBe(1);
|
||||
});
|
||||
|
||||
it("should return 0 when test function is empty (no validation)", () => {
|
||||
const result = runBash(`_multi_creds_validate "" "TestProvider" "https://example.com"`);
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
|
||||
it("should unset env vars on validation failure", () => {
|
||||
const result = runBash(`
|
||||
export MY_VAR1="secret1"
|
||||
export MY_VAR2="secret2"
|
||||
test_fail() { return 1; }
|
||||
_multi_creds_validate test_fail "TestProvider" "https://example.com" MY_VAR1 MY_VAR2 2>/dev/null
|
||||
echo "VAR1=\${MY_VAR1:-unset}"
|
||||
echo "VAR2=\${MY_VAR2:-unset}"
|
||||
`);
|
||||
expect(result.stdout).toContain("VAR1=unset");
|
||||
expect(result.stdout).toContain("VAR2=unset");
|
||||
});
|
||||
|
||||
it("should not unset env vars on validation success", () => {
|
||||
const result = runBash(`
|
||||
export MY_VAR1="secret1"
|
||||
export MY_VAR2="secret2"
|
||||
test_pass() { return 0; }
|
||||
_multi_creds_validate test_pass "TestProvider" "https://example.com" MY_VAR1 MY_VAR2
|
||||
echo "VAR1=\${MY_VAR1:-unset}"
|
||||
echo "VAR2=\${MY_VAR2:-unset}"
|
||||
`);
|
||||
expect(result.stdout).toContain("VAR1=secret1");
|
||||
expect(result.stdout).toContain("VAR2=secret2");
|
||||
});
|
||||
|
||||
it("should show error message with provider name on failure", () => {
|
||||
const result = runBash(`
|
||||
test_fail() { return 1; }
|
||||
_multi_creds_validate test_fail "Contabo" "https://example.com" MY_VAR 2>&1
|
||||
`);
|
||||
expect(result.stdout).toContain("Invalid Contabo credentials");
|
||||
});
|
||||
|
||||
it("should show actionable guidance on failure", () => {
|
||||
const result = runBash(`
|
||||
test_fail() { return 1; }
|
||||
_multi_creds_validate test_fail "UpCloud" "https://example.com" MY_VAR 2>&1
|
||||
`);
|
||||
expect(result.stdout).toContain("expired");
|
||||
expect(result.stdout).toContain("Re-run");
|
||||
});
|
||||
|
||||
it("should show testing message during validation", () => {
|
||||
const result = runBash(`
|
||||
test_pass() { return 0; }
|
||||
_multi_creds_validate test_pass "Hetzner" "https://example.com" 2>&1
|
||||
`);
|
||||
expect(result.stdout).toContain("Testing Hetzner credentials");
|
||||
});
|
||||
|
||||
it("should handle single env var unset on failure", () => {
|
||||
const result = runBash(`
|
||||
export SINGLE_VAR="value"
|
||||
test_fail() { return 1; }
|
||||
_multi_creds_validate test_fail "Provider" "https://example.com" SINGLE_VAR 2>/dev/null
|
||||
echo "\${SINGLE_VAR:-unset}"
|
||||
`);
|
||||
expect(result.stdout).toContain("unset");
|
||||
});
|
||||
|
||||
it("should handle three env vars unset on failure", () => {
|
||||
const result = runBash(`
|
||||
export V1="a" V2="b" V3="c"
|
||||
test_fail() { return 1; }
|
||||
_multi_creds_validate test_fail "Provider" "https://example.com" V1 V2 V3 2>/dev/null
|
||||
echo "\${V1:-x}\${V2:-x}\${V3:-x}"
|
||||
`);
|
||||
expect(result.stdout).toBe("xxx");
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// check_json_processor_available
|
||||
// ============================================================================
|
||||
|
||||
describe("check_json_processor_available", () => {
|
||||
it("should return 0 when python3 is available", () => {
|
||||
const result = runBash("check_json_processor_available");
|
||||
// python3 should be available in CI/test environment
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
|
||||
it("should return 1 when python3 is not in PATH", () => {
|
||||
const result = runBash(`
|
||||
PATH=/nonexistent
|
||||
hash -r
|
||||
check_json_processor_available 2>/dev/null
|
||||
`);
|
||||
expect(result.exitCode).toBe(1);
|
||||
});
|
||||
|
||||
it("should show installation instructions when jq and bun are missing", () => {
|
||||
const result = runBash(`
|
||||
PATH=/nonexistent
|
||||
hash -r
|
||||
check_json_processor_available 2>&1
|
||||
`);
|
||||
expect(result.stdout).toContain("jq or bun is required");
|
||||
expect(result.stdout).toContain("sudo apt-get");
|
||||
expect(result.stdout).toContain("brew install jq");
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// calculate_retry_backoff
|
||||
// ============================================================================
|
||||
|
||||
describe("calculate_retry_backoff", () => {
|
||||
it("should double the interval (base case)", () => {
|
||||
// With interval=2 and max=100, next should be doubled from base (but with jitter)
|
||||
// The function returns the CURRENT interval with jitter, not the doubled one
|
||||
const result = runBash("calculate_retry_backoff 10 100");
|
||||
expect(result.exitCode).toBe(0);
|
||||
const value = parseInt(result.stdout, 10);
|
||||
// 10 * 0.8 = 8, 10 * 1.2 = 12 (jitter range)
|
||||
expect(value).toBeGreaterThanOrEqual(8);
|
||||
expect(value).toBeLessThanOrEqual(12);
|
||||
});
|
||||
|
||||
it("should cap at max interval", () => {
|
||||
// interval=100, max=50 => capped at 50, then jitter on 100
|
||||
const result = runBash("calculate_retry_backoff 100 50");
|
||||
expect(result.exitCode).toBe(0);
|
||||
const value = parseInt(result.stdout, 10);
|
||||
// Jitter on 100: 80-120
|
||||
expect(value).toBeGreaterThanOrEqual(80);
|
||||
expect(value).toBeLessThanOrEqual(120);
|
||||
});
|
||||
|
||||
it("should handle interval of 1", () => {
|
||||
const result = runBash("calculate_retry_backoff 1 100");
|
||||
expect(result.exitCode).toBe(0);
|
||||
const value = parseInt(result.stdout, 10);
|
||||
// 1 * 0.8 = 0.8 -> 0 or 1, 1 * 1.2 = 1.2 -> 1
|
||||
expect(value).toBeGreaterThanOrEqual(0);
|
||||
expect(value).toBeLessThanOrEqual(2);
|
||||
});
|
||||
|
||||
it("should return numeric output", () => {
|
||||
const result = runBash("calculate_retry_backoff 5 60");
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(parseInt(result.stdout, 10)).not.toBeNaN();
|
||||
});
|
||||
|
||||
it("should apply jitter (non-deterministic but bounded)", () => {
|
||||
// Run multiple times and check they're all within jitter range
|
||||
const values: number[] = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const result = runBash("calculate_retry_backoff 50 200");
|
||||
values.push(parseInt(result.stdout, 10));
|
||||
}
|
||||
for (const v of values) {
|
||||
// 50 * 0.8 = 40, 50 * 1.2 = 60
|
||||
expect(v).toBeGreaterThanOrEqual(40);
|
||||
expect(v).toBeLessThanOrEqual(60);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// verify_agent_installed
|
||||
// Signature: verify_agent_installed CMD [VERIFY_ARG] [AGENT_NAME]
|
||||
// ============================================================================
|
||||
|
||||
describe("verify_agent_installed", () => {
|
||||
it("should return 0 when command exists", () => {
|
||||
// bash is always available
|
||||
const result = runBash(`verify_agent_installed "bash"`);
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
|
||||
it("should return 0 with custom verify arg", () => {
|
||||
// ls --help should succeed
|
||||
const result = runBash(`verify_agent_installed "ls" "--help"`);
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
|
||||
it("should return 1 when command does not exist", () => {
|
||||
const result = runBash(
|
||||
`verify_agent_installed "nonexistent_cmd_12345" 2>/dev/null`
|
||||
|
|
@ -477,170 +126,37 @@ describe("verify_agent_installed", () => {
|
|||
expect(result.stdout).toContain("Claude Code");
|
||||
expect(result.stdout).toContain("installation failed");
|
||||
});
|
||||
|
||||
it("should show how-to-fix guidance on failure", () => {
|
||||
const result = runBash(
|
||||
`verify_agent_installed "nonexistent_cmd_12345" "--version" "Codex" 2>&1`
|
||||
);
|
||||
expect(result.stdout).toContain("How to fix");
|
||||
expect(result.stdout).toContain("Codex");
|
||||
});
|
||||
|
||||
it("should use command name as default agent name", () => {
|
||||
const result = runBash(
|
||||
`verify_agent_installed "nonexistent_cmd_12345" 2>&1`
|
||||
);
|
||||
expect(result.stdout).toContain("nonexistent_cmd_12345");
|
||||
});
|
||||
|
||||
it("should show verified message on success", () => {
|
||||
const result = runBash(`verify_agent_installed "bash" "--version" "Bash" 2>&1`);
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.stdout).toContain("verified successfully");
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// _log_diagnostic
|
||||
// generate_env_config
|
||||
// ============================================================================
|
||||
|
||||
describe("_log_diagnostic", () => {
|
||||
it("should show header, causes, and fixes", () => {
|
||||
const result = runBash(
|
||||
`_log_diagnostic "Something failed" "Cause 1" "Cause 2" --- "Fix 1" "Fix 2" 2>&1`
|
||||
);
|
||||
expect(result.stdout).toContain("Something failed");
|
||||
expect(result.stdout).toContain("Possible causes");
|
||||
expect(result.stdout).toContain("Cause 1");
|
||||
expect(result.stdout).toContain("Cause 2");
|
||||
expect(result.stdout).toContain("How to fix");
|
||||
expect(result.stdout).toContain("Fix 1");
|
||||
expect(result.stdout).toContain("Fix 2");
|
||||
describe("generate_env_config", () => {
|
||||
it("should generate export statements", () => {
|
||||
const result = runBash(`generate_env_config "MY_KEY=my_value"`);
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.stdout).toContain("export MY_KEY='my_value'");
|
||||
});
|
||||
|
||||
it("should number fix steps", () => {
|
||||
const result = runBash(
|
||||
`_log_diagnostic "Error" "cause" --- "First step" "Second step" "Third step" 2>&1`
|
||||
);
|
||||
expect(result.stdout).toContain("1. First step");
|
||||
expect(result.stdout).toContain("2. Second step");
|
||||
expect(result.stdout).toContain("3. Third step");
|
||||
it("should include spawn:env marker", () => {
|
||||
const result = runBash(`generate_env_config "K=V"`);
|
||||
expect(result.stdout).toContain("# [spawn:env]");
|
||||
});
|
||||
|
||||
it("should handle single cause and single fix", () => {
|
||||
const result = runBash(
|
||||
`_log_diagnostic "Header" "Only cause" --- "Only fix" 2>&1`
|
||||
);
|
||||
expect(result.stdout).toContain("Only cause");
|
||||
expect(result.stdout).toContain("1. Only fix");
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// _generate_csrf_state
|
||||
// ============================================================================
|
||||
|
||||
describe("_generate_csrf_state", () => {
|
||||
it("should generate a non-empty string", () => {
|
||||
const result = runBash("_generate_csrf_state");
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.stdout.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("should generate hex-only output", () => {
|
||||
const result = runBash("_generate_csrf_state");
|
||||
expect(result.stdout).toMatch(/^[0-9a-f]+$/);
|
||||
});
|
||||
|
||||
it("should generate at least 16 characters", () => {
|
||||
const result = runBash("_generate_csrf_state");
|
||||
expect(result.stdout.length).toBeGreaterThanOrEqual(16);
|
||||
});
|
||||
|
||||
it("should generate different values on successive calls", () => {
|
||||
const result1 = runBash("_generate_csrf_state");
|
||||
const result2 = runBash("_generate_csrf_state");
|
||||
// Very unlikely to get the same value twice
|
||||
expect(result1.stdout).not.toBe(result2.stdout);
|
||||
});
|
||||
|
||||
it("should work with openssl if available", () => {
|
||||
const checkOpenssl = runBash("command -v openssl && echo found || echo missing");
|
||||
if (checkOpenssl.stdout.includes("found")) {
|
||||
const result = runBash("_generate_csrf_state");
|
||||
expect(result.exitCode).toBe(0);
|
||||
// openssl rand -hex 16 produces 32 hex characters
|
||||
expect(result.stdout.length).toBe(32);
|
||||
}
|
||||
});
|
||||
|
||||
it("should work with /dev/urandom fallback", () => {
|
||||
// Force the /dev/urandom path by temporarily renaming openssl in PATH
|
||||
// Use a subshell with modified PATH to hide openssl
|
||||
const result = runBash(`
|
||||
PATH=$(echo "$PATH" | tr ':' '\\n' | grep -v openssl | tr '\\n' ':')
|
||||
unset -f openssl 2>/dev/null
|
||||
# Override command -v to hide openssl
|
||||
command() {
|
||||
if [[ "$1" == "-v" && "$2" == "openssl" ]]; then return 1; fi
|
||||
builtin command "$@"
|
||||
}
|
||||
_generate_csrf_state
|
||||
`);
|
||||
// If /dev/urandom is available (it should be on Linux), this should work
|
||||
if (result.exitCode === 0) {
|
||||
expect(result.stdout.length).toBeGreaterThan(0);
|
||||
expect(result.stdout).toMatch(/^[0-9a-f]+$/);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// register_cleanup_trap and cleanup_temp_files
|
||||
// ============================================================================
|
||||
|
||||
describe("register_cleanup_trap and cleanup_temp_files", () => {
|
||||
it("should register EXIT trap without error", () => {
|
||||
const result = runBash("register_cleanup_trap");
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
|
||||
it("should track temp files for cleanup", () => {
|
||||
const result = runBash(`
|
||||
TMPF=$(mktemp)
|
||||
track_temp_file "$TMPF"
|
||||
echo "$TMPF"
|
||||
`);
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.stdout.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("should clean up tracked temp files", () => {
|
||||
const result = runBash(`
|
||||
TMPF=$(mktemp)
|
||||
track_temp_file "$TMPF"
|
||||
test -f "$TMPF" && echo "before:exists"
|
||||
cleanup_temp_files
|
||||
test -f "$TMPF" && echo "after:exists" || echo "after:removed"
|
||||
`);
|
||||
expect(result.stdout).toContain("before:exists");
|
||||
expect(result.stdout).toContain("after:removed");
|
||||
});
|
||||
|
||||
it("should handle cleanup with no tracked files", () => {
|
||||
const result = runBash("cleanup_temp_files");
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
|
||||
it("should handle cleanup when tracked file already removed", () => {
|
||||
const result = runBash(`
|
||||
TMPF=$(mktemp)
|
||||
track_temp_file "$TMPF"
|
||||
rm -f "$TMPF"
|
||||
cleanup_temp_files
|
||||
echo "ok"
|
||||
`);
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.stdout).toContain("ok");
|
||||
it("should handle values containing equals signs", () => {
|
||||
const result = runBash(`generate_env_config "API_URL=https://example.com?key=abc"`);
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.stdout).toContain("export API_URL='https://example.com?key=abc'");
|
||||
});
|
||||
|
||||
it("should produce sourceable bash output", () => {
|
||||
const result = runBash(`
|
||||
OUTPUT=$(generate_env_config "TEST_VAR=hello_world")
|
||||
eval "$OUTPUT"
|
||||
echo "$TEST_VAR"
|
||||
`);
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.stdout).toBe("hello_world");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue