Add gen-secret: generate bash/YAML/JSON-safe random strings

Outputs cryptographically random strings using only characters safe
for unquoted use in bash, YAML, and JSON: [A-Za-z0-9._+\-:@^~]

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Paul O'Reilly
2026-03-27 11:32:55 +13:00
parent 161c633b42
commit 8bd4c7253b
3 changed files with 265 additions and 0 deletions

120
tests/test-gen-secret.sh Executable file
View File

@@ -0,0 +1,120 @@
#!/usr/bin/env bash
set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
GEN_SECRET="$SCRIPT_DIR/scripts/gen-secret"
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m'
pass=0
fail=0
assert_eq() {
local desc="$1" expected="$2" actual="$3"
if [[ "$expected" == "$actual" ]]; then
echo -e "${GREEN}PASS${NC}: $desc"
((pass++))
else
echo -e "${RED}FAIL${NC}: $desc"
echo " expected: $expected"
echo " actual: $actual"
((fail++))
fi
}
assert_match() {
local desc="$1" pattern="$2" actual="$3"
if [[ "$actual" =~ $pattern ]]; then
echo -e "${GREEN}PASS${NC}: $desc"
((pass++))
else
echo -e "${RED}FAIL${NC}: $desc"
echo " pattern: $pattern"
echo " actual: $actual"
((fail++))
fi
}
assert_exit() {
local desc="$1" expected="$2" actual="$3"
if [[ "$expected" -eq "$actual" ]]; then
echo -e "${GREEN}PASS${NC}: $desc"
((pass++))
else
echo -e "${RED}FAIL${NC}: $desc"
echo " expected exit: $expected"
echo " actual exit: $actual"
((fail++))
fi
}
echo "=== gen-secret tests ==="
echo
# --- Dryrun tests ---
out=$("$GEN_SECRET" --dryrun 2>&1)
assert_eq "dryrun default length" \
'[dryrun] Would generate a 32-character secret from charset: [A-Za-z0-9._+\-:@^~]' \
"$out"
out=$("$GEN_SECRET" -n 16 2>&1)
assert_eq "dryrun custom length" \
'[dryrun] Would generate a 16-character secret from charset: [A-Za-z0-9._+\-:@^~]' \
"$out"
# --- Help ---
out=$("$GEN_SECRET" --help 2>&1)
rc=$?
assert_exit "help exits 0" 0 "$rc"
assert_match "help mentions LENGTH" "LENGTH" "$out"
# --- Default generation ---
out=$("$GEN_SECRET" 2>&1)
rc=$?
assert_exit "default exits 0" 0 "$rc"
assert_eq "default length is 32" 32 "${#out}"
assert_match "default uses safe charset" '^[A-Za-z0-9._+:@^~-]+$' "$out"
# --- Custom length ---
out=$("$GEN_SECRET" 64 2>&1)
assert_eq "custom length 64" 64 "${#out}"
out=$("$GEN_SECRET" 1 2>&1)
assert_eq "minimum length 1" 1 "${#out}"
# --- Error cases ---
out=$("$GEN_SECRET" 0 2>&1)
rc=$?
assert_exit "length 0 exits 1" 1 "$rc"
assert_match "length 0 error message" "positive integer" "$out"
out=$("$GEN_SECRET" -5 2>&1)
rc=$?
assert_exit "negative length exits 1" 1 "$rc"
out=$("$GEN_SECRET" abc 2>&1)
rc=$?
assert_exit "non-numeric exits 1" 1 "$rc"
assert_match "non-numeric error message" "positive integer" "$out"
out=$("$GEN_SECRET" 10 20 2>&1)
rc=$?
assert_exit "too many args exits 1" 1 "$rc"
assert_match "too many args error message" "Too many arguments" "$out"
# --- Summary ---
echo
total=$((pass + fail))
echo -e "Results: ${GREEN}${pass}${NC}/${total} passed"
if [[ $fail -gt 0 ]]; then
echo -e "${RED}${fail} test(s) failed${NC}"
exit 1
fi