test: make [[ ]] assertions effective under bash 3.2 (EGB-677 precursor)

bats on macOS runs under system bash 3.2, where a failing [[ ]] compound
command mid-test does NOT trip the ERR trap — every mid-test
[[ "$output" == *...* ]] assertion in the suite was decorative.
Append '|| false' to all 123 standalone [[ ]] assertion lines so failures
become plain-command failures, which bats catches. Full suite still green
(133/133) — no latent false positives were hiding.
This commit is contained in:
Brian Majewski 2026-06-07 08:21:29 -07:00
parent a3c71f293b
commit 6dbc4e0d01

View file

@ -27,7 +27,7 @@ load test_helper
run "$SECRETS_BIN" init
[ "$status" -eq 1 ]
[[ "$output" == *"Already initialized"* ]]
[[ "$output" == *"Already initialized"* ]] || false
# Key must not be overwritten
local key_after
@ -51,7 +51,7 @@ load test_helper
run env PATH="$fake_path" "$SECRETS_BIN" init
[ "$status" -eq 1 ]
[[ "$output" == *"age"* ]]
[[ "$output" == *"age"* ]] || false
}
# ─── push ──────────────────────────────────────────────────────────────
@ -73,7 +73,7 @@ load test_helper
run "$SECRETS_BIN" push testproj
[ "$status" -eq 1 ]
[[ "$output" == *"No secret files"* ]]
[[ "$output" == *"No secret files"* ]] || false
}
@test "push errors with missing key" {
@ -83,7 +83,7 @@ load test_helper
run "$SECRETS_BIN" push testproj
[ "$status" -eq 1 ]
[[ "$output" == *"Key file"* ]]
[[ "$output" == *"Key file"* ]] || false
}
@test "push derives project name from dirname" {
@ -140,7 +140,7 @@ load test_helper
run git commit -m "should fail"
[ "$status" -eq 1 ]
[[ "$output" == *"Plaintext"* ]]
[[ "$output" == *"Plaintext"* ]] || false
}
# ─── pull ──────────────────────────────────────────────────────────────
@ -168,7 +168,7 @@ load test_helper
run "$SECRETS_BIN" pull nonexistent
[ "$status" -eq 1 ]
[[ "$output" == *"not found"* ]]
[[ "$output" == *"not found"* ]] || false
}
@test "pull errors with missing key" {
@ -183,7 +183,7 @@ load test_helper
run "$SECRETS_BIN" pull testproj
[ "$status" -eq 1 ]
[[ "$output" == *"Key file"* ]]
[[ "$output" == *"Key file"* ]] || false
}
@test "pull overwrites existing files" {
@ -217,7 +217,7 @@ load test_helper
run "$SECRETS_BIN" pull testproj
[ "$status" -eq 0 ]
[ -x "$SECRETS_DIR/.git/hooks/pre-commit" ]
[[ "$output" == *"Reinstalled"* ]]
[[ "$output" == *"Reinstalled"* ]] || false
}
# ─── list ──────────────────────────────────────────────────────────────
@ -231,8 +231,8 @@ load test_helper
run "$SECRETS_BIN" list
[ "$status" -eq 0 ]
[[ "$output" == *"projA"* ]]
[[ "$output" == *"projB"* ]]
[[ "$output" == *"projA"* ]] || false
[[ "$output" == *"projB"* ]] || false
}
@test "list shows empty message" {
@ -240,7 +240,7 @@ load test_helper
run "$SECRETS_BIN" list
[ "$status" -eq 0 ]
[[ "$output" == *"No projects"* ]]
[[ "$output" == *"No projects"* ]] || false
}
# ─── rm ────────────────────────────────────────────────────────────────
@ -261,7 +261,7 @@ load test_helper
run "$SECRETS_BIN" rm nonexistent
[ "$status" -eq 1 ]
[[ "$output" == *"not found"* ]]
[[ "$output" == *"not found"* ]] || false
}
# ─── pre-commit hook ──────────────────────────────────────────────────
@ -275,7 +275,7 @@ load test_helper
run git commit -m "should fail"
[ "$status" -eq 1 ]
[[ "$output" == *"Plaintext"* ]]
[[ "$output" == *"Plaintext"* ]] || false
}
@test "pre-commit allows .age files" {
@ -304,7 +304,7 @@ load test_helper
run "$SECRETS_BIN" clear
[ "$status" -eq 0 ]
[[ "$output" == *"Cleared 3"* ]]
[[ "$output" == *"Cleared 3"* ]] || false
# Files should be gone
[ ! -f "$WORK_DIR/testproj/.env" ]
@ -318,7 +318,7 @@ load test_helper
run "$SECRETS_BIN" clear
[ "$status" -eq 0 ]
[[ "$output" == *"No secret files"* ]]
[[ "$output" == *"No secret files"* ]] || false
}
@test "clear does not remove non-secret files" {
@ -347,7 +347,7 @@ load test_helper
run "$SECRETS_BIN" clear --workspaces
[ "$status" -eq 0 ]
[[ "$output" == *"Cleared"* ]]
[[ "$output" == *"Cleared"* ]] || false
# All should be gone
[ ! -f "$mono/.env" ]
@ -368,7 +368,7 @@ load test_helper
# Run a command that reads the secret
run "$SECRETS_BIN" run cat .env
[ "$status" -eq 0 ]
[[ "$output" == *"SECRET_KEY=abc123"* ]]
[[ "$output" == *"SECRET_KEY=abc123"* ]] || false
# After run completes, plaintext files should be cleared
[ ! -f "$WORK_DIR/testproj/.env" ]
@ -395,7 +395,7 @@ load test_helper
@test "run errors with no command" {
run "$SECRETS_BIN" run
[ "$status" -eq 1 ]
[[ "$output" == *"Usage"* ]]
[[ "$output" == *"Usage"* ]] || false
}
@test "run passes arguments through to command" {
@ -407,7 +407,7 @@ load test_helper
# Run with multiple args
run "$SECRETS_BIN" run ls -la .env
[ "$status" -eq 0 ]
[[ "$output" == *".env"* ]]
[[ "$output" == *".env"* ]] || false
}
@test "run supports -- separator" {
@ -418,7 +418,7 @@ load test_helper
run "$SECRETS_BIN" run -- cat .env
[ "$status" -eq 0 ]
[[ "$output" == *"SECRET_KEY=abc123"* ]]
[[ "$output" == *"SECRET_KEY=abc123"* ]] || false
}
# ─── workspaces ────────────────────────────────────────────────────────
@ -491,7 +491,7 @@ PKGJSON
run "$SECRETS_BIN" push --workspaces
[ "$status" -eq 1 ]
[[ "$output" == *"No package.json"* ]]
[[ "$output" == *"No package.json"* ]] || false
}
@test "push --workspaces errors without workspaces field" {
@ -502,7 +502,7 @@ PKGJSON
run "$SECRETS_BIN" push --workspaces
[ "$status" -eq 1 ]
[[ "$output" == *"No workspaces"* ]]
[[ "$output" == *"No workspaces"* ]] || false
}
@test "push --workspaces errors when no env files anywhere" {
@ -517,7 +517,7 @@ EOF
run "$SECRETS_BIN" push --workspaces
[ "$status" -eq 1 ]
[[ "$output" == *"No secret files"* ]]
[[ "$output" == *"No secret files"* ]] || false
}
# ─── EGB-281: multi-store resolution ──────────────────────────────────
@ -529,8 +529,8 @@ EOF
cd subdir
run "$SECRETS_BIN" which
[ "$status" -eq 0 ]
[[ "$output" == *"$HOME/.secrets"* ]]
[[ "$output" == *"source: default"* ]]
[[ "$output" == *"$HOME/.secrets"* ]] || false
[[ "$output" == *"source: default"* ]] || false
}
@test "which uses .secrets-store file in cwd" {
@ -539,10 +539,10 @@ EOF
create_bound_project_dir myapp "~/.secrets-work"
run "$SECRETS_BIN" which
[ "$status" -eq 0 ]
[[ "$output" == *"$HOME/.secrets-work"* ]]
[[ "$output" == *"$HOME/.secrets-work"* ]] || false
# Source line must include both the rule name AND the resolved file path,
# not the empty parens (".secrets-store file ()") that v0.1.0.0 shipped.
[[ "$output" == *".secrets-store file ("*"$WORK_DIR/myapp/.secrets-store)"* ]]
[[ "$output" == *".secrets-store file ("*"$WORK_DIR/myapp/.secrets-store)"* ]] || false
}
@test "--store flag overrides .secrets-store file and SECRETS_DIR env" {
@ -550,8 +550,8 @@ EOF
create_bound_project_dir myapp "~/.secrets-from-file"
run "$SECRETS_BIN" --store "$HOME/.secrets-from-flag" which
[ "$status" -eq 0 ]
[[ "$output" == *"$HOME/.secrets-from-flag"* ]]
[[ "$output" == *"--store flag"* ]]
[[ "$output" == *"$HOME/.secrets-from-flag"* ]] || false
[[ "$output" == *"--store flag"* ]] || false
}
@test "which walks up to find .secrets-store in ancestor" {
@ -562,7 +562,7 @@ EOF
cd "$WORK_DIR/repo/sub/deep"
run "$SECRETS_BIN" which
[ "$status" -eq 0 ]
[[ "$output" == *"$HOME/.secrets-work"* ]]
[[ "$output" == *"$HOME/.secrets-work"* ]] || false
}
@test "which walk-up stops at HOME boundary, does not read \$HOME/.secrets-store" {
@ -572,9 +572,9 @@ EOF
cd "$WORK_DIR/repo"
run "$SECRETS_BIN" which
[ "$status" -eq 0 ]
[[ "$output" != *"should-not-be-used"* ]]
[[ "$output" == *"$HOME/.secrets"* ]]
[[ "$output" == *"source: default"* ]]
[[ "$output" != *"should-not-be-used"* ]] || false
[[ "$output" == *"$HOME/.secrets"* ]] || false
[[ "$output" == *"source: default"* ]] || false
}
@test "which from outside HOME falls through to default" {
@ -582,8 +582,8 @@ EOF
cd /tmp
run "$SECRETS_BIN" which
[ "$status" -eq 0 ]
[[ "$output" == *"$HOME/.secrets"* ]]
[[ "$output" == *"source: default"* ]]
[[ "$output" == *"$HOME/.secrets"* ]] || false
[[ "$output" == *"source: default"* ]] || false
}
@test "empty .secrets-store falls through to next rule" {
@ -593,7 +593,7 @@ EOF
: > .secrets-store
run "$SECRETS_BIN" which
[ "$status" -eq 0 ]
[[ "$output" == *"source: default"* ]]
[[ "$output" == *"source: default"* ]] || false
}
@test "comment-only .secrets-store falls through" {
@ -603,7 +603,7 @@ EOF
printf '# this is a comment\n \n# another\n' > .secrets-store
run "$SECRETS_BIN" which
[ "$status" -eq 0 ]
[[ "$output" == *"source: default"* ]]
[[ "$output" == *"source: default"* ]] || false
}
@test "bare name 'work' resolves to ~/.secrets-work" {
@ -614,7 +614,7 @@ EOF
cd "$WORK_DIR/repo"
run "$SECRETS_BIN" which
[ "$status" -eq 0 ]
[[ "$output" == *"$HOME/.secrets-work"* ]]
[[ "$output" == *"$HOME/.secrets-work"* ]] || false
}
@test "~/-prefix in .secrets-store expands to HOME" {
@ -625,7 +625,7 @@ EOF
cd "$WORK_DIR/repo"
run "$SECRETS_BIN" which
[ "$status" -eq 0 ]
[[ "$output" == *"$HOME/.secrets-x"* ]]
[[ "$output" == *"$HOME/.secrets-x"* ]] || false
}
@test ".secrets-store with command injection content does not execute" {
@ -671,7 +671,7 @@ EOF
run "$SECRETS_BIN" --store "$SECRETS_DIR" run -- cat .env
[ "$status" -eq 0 ]
[[ "$output" == *"SECRET_KEY=abc123"* ]]
[[ "$output" == *"SECRET_KEY=abc123"* ]] || false
}
@test "uninitialized store referenced by .secrets-store gives directed error" {
@ -683,8 +683,8 @@ EOF
run "$SECRETS_BIN" pull
[ "$status" -eq 1 ]
[[ "$output" == *"git clone"* ]]
[[ "$output" == *"--store"* ]]
[[ "$output" == *"git clone"* ]] || false
[[ "$output" == *"--store"* ]] || false
}
@test "push -w ignores per-workspace .secrets-store, uses monorepo root binding" {
@ -727,7 +727,7 @@ PKG
create_project_dir myapp
run "$SECRETS_BIN" --store "$HOME/.secrets-work" push myapp
[ "$status" -eq 0 ]
[[ "$output" == *"Store: $HOME/.secrets-work"* ]]
[[ "$output" == *"Store: $HOME/.secrets-work"* ]] || false
}
# ─── EGB-281: gap-filler tests (auto-decided during /ship coverage audit) ─
@ -735,7 +735,7 @@ PKG
@test "--store with missing argument errors out" {
run "$SECRETS_BIN" --store
[ "$status" -eq 1 ]
[[ "$output" == *"--store requires"* ]]
[[ "$output" == *"--store requires"* ]] || false
}
@test "--store=value (equals form) is accepted" {
@ -744,7 +744,7 @@ PKG
cd "$HOME"
run "$SECRETS_BIN" --store="$HOME/.secrets-equals" which
[ "$status" -eq 0 ]
[[ "$output" == *"$HOME/.secrets-equals"* ]]
[[ "$output" == *"$HOME/.secrets-equals"* ]] || false
}
@test "where and status are aliases of which" {
@ -754,11 +754,11 @@ PKG
cd subdir
run "$SECRETS_BIN" where
[ "$status" -eq 0 ]
[[ "$output" == *"source:"* ]]
[[ "$output" == *"source:"* ]] || false
run "$SECRETS_BIN" status
[ "$status" -eq 0 ]
[[ "$output" == *"source:"* ]]
[[ "$output" == *"source:"* ]] || false
}
@test "--store default sugar resolves to ~/.secrets" {
@ -766,7 +766,7 @@ PKG
cd "$HOME"
run "$SECRETS_BIN" --store default which
[ "$status" -eq 0 ]
[[ "$output" == *"$HOME/.secrets"* ]]
[[ "$output" == *"$HOME/.secrets"* ]] || false
}
@test "missing key.txt in non-default store gives directed error" {
@ -783,8 +783,8 @@ PKG
run "$SECRETS_BIN" push myapp
[ "$status" -eq 1 ]
[[ "$output" == *"key.txt"* ]]
[[ "$output" == *"teammate"* ]]
[[ "$output" == *"key.txt"* ]] || false
[[ "$output" == *"teammate"* ]] || false
}
@test "CRLF line endings in .secrets-store are tolerated" {
@ -795,7 +795,7 @@ PKG
cd "$WORK_DIR/proj"
run "$SECRETS_BIN" which
[ "$status" -eq 0 ]
[[ "$output" == *"$HOME/.secrets-crlf"* ]]
[[ "$output" == *"$HOME/.secrets-crlf"* ]] || false
}
@test "list hints at 'secrets which' when non-default store is active" {
@ -805,7 +805,7 @@ PKG
run "$SECRETS_BIN" --store "$HOME/.secrets-x" list
[ "$status" -eq 0 ]
[[ "$output" == *"secrets which"* ]]
[[ "$output" == *"secrets which"* ]] || false
}
# ─── EGB-281: adversarial-review regression tests (F1-F5) ─────────────
@ -825,7 +825,7 @@ PKG
# Run a command, then verify .env is cleared by the EXIT trap
run "$SECRETS_BIN" run -- cat .env
[ "$status" -eq 0 ]
[[ "$output" == *"should-not-leak"* ]]
[[ "$output" == *"should-not-leak"* ]] || false
# CRITICAL: the trap must have cleaned up — .env must NOT exist on disk.
# If F1 regressed (string-interpolated trap), the file would still be here.
[ ! -f "$QUOTED_DIR/.env" ]
@ -842,27 +842,27 @@ PKG
run "$SECRETS_BIN" which
[ "$status" -eq 0 ]
# The symlink should be ignored, falling through to default
[[ "$output" != *"/etc/passwd"* ]]
[[ "$output" == *"$HOME/.secrets"* ]]
[[ "$output" == *"source: default"* ]]
[[ "$output" != *"/etc/passwd"* ]] || false
[[ "$output" == *"$HOME/.secrets"* ]] || false
[[ "$output" == *"source: default"* ]] || false
}
@test "F3: --store rejects flag-shaped value" {
run "$SECRETS_BIN" --store --workspaces which
[ "$status" -eq 1 ]
[[ "$output" == *"looks like a flag"* ]]
[[ "$output" == *"looks like a flag"* ]] || false
}
@test "F3: --store rejects literal --" {
run "$SECRETS_BIN" --store -- which
[ "$status" -eq 1 ]
[[ "$output" == *"looks like a flag"* ]]
[[ "$output" == *"looks like a flag"* ]] || false
}
@test "F4: --store= empty value is rejected" {
run "$SECRETS_BIN" --store= which
[ "$status" -eq 1 ]
[[ "$output" == *"requires a value"* ]]
[[ "$output" == *"requires a value"* ]] || false
}
@test "F5: HOME unset gives directed error" {
@ -872,7 +872,7 @@ PKG
run "$SECRETS_BIN" which
export HOME="$SAVED_HOME" # restore before assertions in case bats relies on it
[ "$status" -ne 0 ]
[[ "$output" == *"HOME"* ]]
[[ "$output" == *"HOME"* ]] || false
}
# ─── EGB-282: optional remote URL in .secrets-store ──────────────────
@ -886,7 +886,7 @@ PKG
cd "$WORK_DIR/proj"
run "$SECRETS_BIN" which
[ "$status" -eq 0 ]
[[ "$output" == *"$HOME/.secrets-work"* ]]
[[ "$output" == *"$HOME/.secrets-work"* ]] || false
}
@test "EGB-282: .secrets-store with URL parses both tokens" {
@ -898,9 +898,9 @@ PKG
# that includes the actual URL (not the placeholder).
run "$SECRETS_BIN" pull
[ "$status" -eq 1 ]
[[ "$output" == *"git clone git@github.com:acme/work-secrets.git $HOME/.secrets-work"* ]]
[[ "$output" == *"git clone git@github.com:acme/work-secrets.git $HOME/.secrets-work"* ]] || false
# Placeholder must NOT appear when a real URL was supplied
[[ "$output" != *"<their-store-remote>"* ]]
[[ "$output" != *"<their-store-remote>"* ]] || false
}
@test "EGB-282: missing-store error still works without URL (placeholder)" {
@ -911,7 +911,7 @@ PKG
run "$SECRETS_BIN" pull
[ "$status" -eq 1 ]
# No URL given — placeholder is the right behavior.
[[ "$output" == *"<their-store-remote>"* ]]
[[ "$output" == *"<their-store-remote>"* ]] || false
}
@test "EGB-282: https URL is preserved literally" {
@ -921,7 +921,7 @@ PKG
cd "$WORK_DIR/proj"
run "$SECRETS_BIN" pull
[ "$status" -eq 1 ]
[[ "$output" == *"https://github.com/acme/work-secrets.git"* ]]
[[ "$output" == *"https://github.com/acme/work-secrets.git"* ]] || false
}
@test "EGB-282: ~/-prefixed path with URL works" {
@ -931,7 +931,7 @@ PKG
cd "$WORK_DIR/proj"
run "$SECRETS_BIN" pull
[ "$status" -eq 1 ]
[[ "$output" == *"git clone git@github.com:acme/x.git $HOME/.secrets-x"* ]]
[[ "$output" == *"git clone git@github.com:acme/x.git $HOME/.secrets-x"* ]] || false
}
@test "EGB-282: comments before URL line are still skipped" {
@ -941,7 +941,7 @@ PKG
cd "$WORK_DIR/proj"
run "$SECRETS_BIN" pull
[ "$status" -eq 1 ]
[[ "$output" == *"git clone git@github.com:acme/work-secrets.git"* ]]
[[ "$output" == *"git clone git@github.com:acme/work-secrets.git"* ]] || false
}
# ─── EGB-282 adversarial regressions: URL injection prevention ────────
@ -958,11 +958,11 @@ PKG
run "$SECRETS_BIN" pull
[ "$status" -eq 1 ]
# Must use the placeholder, NOT the attacker URL
[[ "$output" == *"<their-store-remote>"* ]]
[[ "$output" != *"rm -rf"* ]]
[[ "$output" == *"<their-store-remote>"* ]] || false
[[ "$output" != *"rm -rf"* ]] || false
# And must have warned the user that something was dropped
[[ "$output" == *"WARNING"* ]]
[[ "$output" == *"unsafe"* ]]
[[ "$output" == *"WARNING"* ]] || false
[[ "$output" == *"unsafe"* ]] || false
}
@test "EGB-282 SECURITY: URL with backticks is dropped" {
@ -972,7 +972,7 @@ PKG
cd "$WORK_DIR/proj"
run "$SECRETS_BIN" pull
[ "$status" -eq 1 ]
[[ "$output" == *"<their-store-remote>"* ]]
[[ "$output" == *"<their-store-remote>"* ]] || false
}
@test "EGB-282 SECURITY: URL with command substitution \$() is dropped" {
@ -982,7 +982,7 @@ PKG
cd "$WORK_DIR/proj"
run "$SECRETS_BIN" pull
[ "$status" -eq 1 ]
[[ "$output" == *"<their-store-remote>"* ]]
[[ "$output" == *"<their-store-remote>"* ]] || false
}
@test "EGB-282 SECURITY: URL with ANSI escape is dropped (terminal-spoof prevention)" {
@ -993,7 +993,7 @@ PKG
cd "$WORK_DIR/proj"
run "$SECRETS_BIN" pull
[ "$status" -eq 1 ]
[[ "$output" == *"<their-store-remote>"* ]]
[[ "$output" == *"<their-store-remote>"* ]] || false
}
@test "EGB-282 SECURITY: multi-token URL ('work url1 url2') is dropped" {
@ -1005,7 +1005,7 @@ PKG
cd "$WORK_DIR/proj"
run "$SECRETS_BIN" pull
[ "$status" -eq 1 ]
[[ "$output" == *"<their-store-remote>"* ]]
[[ "$output" == *"<their-store-remote>"* ]] || false
}
@test "EGB-282 SECURITY: glob char in URL is dropped (no expansion either way)" {
@ -1018,7 +1018,7 @@ PKG
cd "$WORK_DIR/proj"
run "$SECRETS_BIN" pull
[ "$status" -eq 1 ]
[[ "$output" == *"<their-store-remote>"* ]]
[[ "$output" == *"<their-store-remote>"* ]] || false
}
@test "EGB-282: spec parsing is glob-safe (work * does NOT expand)" {
@ -1032,7 +1032,7 @@ PKG
[ "$status" -eq 0 ]
# Spec is the literal "work" (resolves to ~/.secrets-work). The "*" gets
# rejected as unsafe URL and dropped. Resolution works; no globbing.
[[ "$output" == *"$HOME/.secrets-work"* ]]
[[ "$output" == *"$HOME/.secrets-work"* ]] || false
}
@test "EGB-282: URL with - + _ : / @ . is preserved (positive test)" {
@ -1043,7 +1043,7 @@ PKG
cd "$WORK_DIR/proj"
run "$SECRETS_BIN" pull
[ "$status" -eq 1 ]
[[ "$output" == *"git+ssh://user@host:2222/path/to-repo_v2.git"* ]]
[[ "$output" == *"git+ssh://user@host:2222/path/to-repo_v2.git"* ]] || false
}
# ─── EGB-531: gradle.properties external file support ──────────────────
@ -1069,9 +1069,9 @@ gradle_project() {
gradle_project gproj
run "$SECRETS_BIN" which
[ "$status" -eq 0 ]
[[ "$output" == *"gradle-properties"* ]]
[[ "$output" == *"~/.gradle/gradle.properties"* ]]
[[ "$output" == *"beaconClerkPkTest"* ]]
[[ "$output" == *"gradle-properties"* ]] || false
[[ "$output" == *"~/.gradle/gradle.properties"* ]] || false
[[ "$output" == *"beaconClerkPkTest"* ]] || false
}
@test "EGB-531: push extracts managed keys into external/ blob (no .env needed)" {
@ -1080,7 +1080,7 @@ gradle_project() {
gradle_project gproj
run "$SECRETS_BIN" push gproj
[ "$status" -eq 0 ]
[[ "$output" == *"Extracted 2 key"* ]]
[[ "$output" == *"Extracted 2 key"* ]] || false
run bash -c "ls $SECRETS_DIR/gproj/external/*.gradle-properties.age"
[ "$status" -eq 0 ]
}
@ -1091,7 +1091,7 @@ gradle_project() {
gradle_project gproj
run "$SECRETS_BIN" push gproj
[ "$status" -eq 1 ]
[[ "$output" == *"not found"* ]]
[[ "$output" == *"not found"* ]] || false
}
@test "EGB-531: pull merges managed keys, preserves unrelated entries" {
@ -1103,7 +1103,7 @@ gradle_project() {
gradle_src $'unrelated.key=keepme\norg.gradle.jvmargs=-Xmx2g\n'
run "$SECRETS_BIN" pull gproj
[ "$status" -eq 0 ]
[[ "$output" == *"Merged 2 key"* ]]
[[ "$output" == *"Merged 2 key"* ]] || false
grep -q 'beaconClerkPkTest=pk_test_abc' "$HOME/.gradle/gradle.properties"
grep -q 'beaconClerkPkLive=pk_live_xyz' "$HOME/.gradle/gradle.properties"
grep -q 'unrelated.key=keepme' "$HOME/.gradle/gradle.properties"
@ -1225,7 +1225,7 @@ gradle_project() {
printf 'gradle-properties ~/.gradle/custom.properties beaconClerkPkTest\n' > .secrets-files
run "$SECRETS_BIN" push gproj
[ "$status" -eq 1 ]
[[ "$output" == *"gradle.properties"* ]]
[[ "$output" == *"gradle.properties"* ]] || false
}
@test "EGB-531: target outside HOME is refused" {
@ -1239,7 +1239,7 @@ gradle_project() {
run "$SECRETS_BIN" push gproj
rm -rf "$outside"
[ "$status" -eq 1 ]
[[ "$output" == *"HOME"* ]]
[[ "$output" == *"HOME"* ]] || false
}
@test "EGB-531: symlinked target is refused" {
@ -1250,7 +1250,7 @@ gradle_project() {
gradle_project gproj beaconClerkPkTest
run "$SECRETS_BIN" push gproj
[ "$status" -eq 1 ]
[[ "$output" == *"symlink"* ]]
[[ "$output" == *"symlink"* ]] || false
}
@test "EGB-531: unknown type in manifest warns and skips" {
@ -1259,7 +1259,7 @@ gradle_project() {
printf 'gradle-props ~/.gradle/gradle.properties beaconClerkPkTest\n' > .secrets-files
echo "X=1" > .env
run "$SECRETS_BIN" push gproj
[[ "$output" == *"unknown type"* ]]
[[ "$output" == *"unknown type"* ]] || false
[ ! -d "$SECRETS_DIR/gproj/external" ]
}
@ -1269,7 +1269,7 @@ gradle_project() {
printf 'gradle-properties\n' > .secrets-files
run "$SECRETS_BIN" which
[ "$status" -eq 0 ]
[[ "$output" == *"WARNING"* ]]
[[ "$output" == *"WARNING"* ]] || false
}
@test "EGB-531: manifest path with command-substitution chars is rejected" {
@ -1280,7 +1280,7 @@ gradle_project() {
printf 'gradle-properties ~/.gradle/gradle.properties$(touch %s) beaconClerkPkTest\n' "$pwn" > .secrets-files
run "$SECRETS_BIN" which
[ ! -f "$pwn" ]
[[ "$output" == *"WARNING"* ]]
[[ "$output" == *"WARNING"* ]] || false
}
@test "EGB-531: symlinked .secrets-files is ignored" {
@ -1290,7 +1290,7 @@ gradle_project() {
ln -s "$HOME/realmanifest" .secrets-files
run "$SECRETS_BIN" which
[ "$status" -eq 0 ]
[[ "$output" != *"beaconClerkPkTest"* ]]
[[ "$output" != *"beaconClerkPkTest"* ]] || false
}
@test "EGB-531: rekey re-encrypts the external blob (still decryptable after)" {
@ -1329,7 +1329,7 @@ gradle_project() {
"$SECRETS_BIN" push gproj >/dev/null 2>&1
run "$SECRETS_BIN" list
[ "$status" -eq 0 ]
[[ "$output" == *"external"* ]]
[[ "$output" == *"external"* ]] || false
}
@test "EGB-531: no .secrets-files behaves exactly as before (backward compat)" {
@ -1347,7 +1347,7 @@ gradle_project() {
git add -f gradle.properties
run git commit -m "should fail"
[ "$status" -eq 1 ]
[[ "$output" == *"Plaintext"* ]]
[[ "$output" == *"Plaintext"* ]] || false
}
# ── EGB-531: coverage for warning/error branches, workspaces, multi-entry ──
@ -1395,9 +1395,9 @@ gradle_project() {
gradle_project gproj
run "$SECRETS_BIN" push gproj
[ "$status" -eq 0 ]
[[ "$output" == *"beaconClerkPkLive"* ]]
[[ "$output" == *"not found"* ]]
[[ "$output" == *"Extracted 1 key"* ]]
[[ "$output" == *"beaconClerkPkLive"* ]] || false
[[ "$output" == *"not found"* ]] || false
[[ "$output" == *"Extracted 1 key"* ]] || false
}
@test "EGB-531: pull warns when manifest entry has no blob in store" {
@ -1408,7 +1408,7 @@ gradle_project() {
cd "$WORK_DIR/gproj"
run "$SECRETS_BIN" pull gproj
[ "$status" -eq 0 ]
[[ "$output" == *"no encrypted data exists"* ]]
[[ "$output" == *"no encrypted data exists"* ]] || false
}
@test "EGB-531: multi-entry manifest syncs each target" {
@ -1436,8 +1436,8 @@ gradle_project() {
printf 'gradle-properties ~/.gradle/gradle.properties bad=key\n' > .secrets-files
run "$SECRETS_BIN" which
[ "$status" -eq 0 ]
[[ "$output" == *"WARNING"* ]]
[[ "$output" != *"bad=key"* ]]
[[ "$output" == *"WARNING"* ]] || false
[[ "$output" != *"bad=key"* ]] || false
}
@test "EGB-531: symlinked parent dir of target is refused" {
@ -1448,7 +1448,7 @@ gradle_project() {
gradle_project gproj beaconClerkPkTest
run "$SECRETS_BIN" push gproj
[ "$status" -eq 1 ]
[[ "$output" == *"symlink"* ]]
[[ "$output" == *"symlink"* ]] || false
}
@test "EGB-531: push skips a multi-line (continuation) managed value with a warning" {
@ -1459,8 +1459,8 @@ gradle_project() {
gradle_project gproj
run "$SECRETS_BIN" push gproj
[ "$status" -eq 0 ]
[[ "$output" == *"multi-line"* ]]
[[ "$output" == *"Extracted 1 key"* ]]
[[ "$output" == *"multi-line"* ]] || false
[[ "$output" == *"Extracted 1 key"* ]] || false
}
@test "EGB-531: push skips comment and continuation lines in source" {
@ -1491,7 +1491,7 @@ gradle_project() {
run "$SECRETS_BIN" init
[ "$status" -eq 1 ]
[[ "$output" == *"git clone"* ]]
[[ "$output" == *"git clone"* ]] || false
# Must not leave a half-initialized store behind
[ ! -d "$SECRETS_DIR/.git" ]
# Key untouched
@ -1505,12 +1505,12 @@ gradle_project() {
run "$SECRETS_BIN" push
[ "$status" -eq 0 ]
[[ "$output" == *"Restored store .gitignore"* ]]
[[ "$output" == *"Restored store .gitignore"* ]] || false
[ -f "$SECRETS_DIR/.gitignore" ]
grep -q "key.txt" "$SECRETS_DIR/.gitignore"
# key.txt must never be tracked (push does `git add -A` in the store)
run git -C "$SECRETS_DIR" ls-files
[[ "$output" != *"key.txt"* ]]
[[ "$output" != *"key.txt"* ]] || false
}
@test "pull restores missing store .gitignore" {
@ -1535,7 +1535,7 @@ gradle_project() {
[ "$status" -eq 0 ]
[ -f "$SECRETS_DIR/.gitignore" ]
run git -C "$SECRETS_DIR" ls-files
[[ "$output" != *"key.txt"* ]]
[[ "$output" != *"key.txt"* ]] || false
}
@test "rekey re-encrypts dotenv blobs (round-trip survives key rotation)" {
@ -1563,7 +1563,7 @@ gradle_project() {
run "$SECRETS_BIN" push
[ "$status" -eq 0 ]
[[ "$output" == *"Reinstalled pre-commit hook"* ]]
[[ "$output" == *"Reinstalled pre-commit hook"* ]] || false
[ -x "$SECRETS_DIR/.git/hooks/pre-commit" ]
}
@ -1584,8 +1584,8 @@ gradle_project() {
run "$SECRETS_BIN" push
[ "$status" -eq 0 ]
[[ "$output" != *"Restored store .gitignore"* ]]
[[ "$output" != *"Reinstalled pre-commit hook"* ]]
[[ "$output" != *"Restored store .gitignore"* ]] || false
[[ "$output" != *"Reinstalled pre-commit hook"* ]] || false
}
@test "restored store .gitignore carries the full block/allow globs" {
@ -1617,7 +1617,7 @@ gradle_project() {
[ "$status" -eq 0 ]
[ -f "$SECRETS_DIR/.gitignore" ]
run git -C "$SECRETS_DIR" ls-files
[[ "$output" != *"key.txt"* ]]
[[ "$output" != *"key.txt"* ]] || false
}
@test "push untracks a previously committed key.txt with a warning" {
@ -1629,9 +1629,9 @@ gradle_project() {
run "$SECRETS_BIN" push
[ "$status" -eq 0 ]
[[ "$output" == *"key.txt was tracked"* ]]
[[ "$output" == *"key.txt was tracked"* ]] || false
run git -C "$SECRETS_DIR" ls-files
[[ "$output" != *"key.txt"* ]]
[[ "$output" != *"key.txt"* ]] || false
}
@test "push rewrites a store .gitignore that is missing the key.txt line" {
@ -1643,7 +1643,7 @@ gradle_project() {
[ "$status" -eq 0 ]
grep -qx 'key.txt' "$SECRETS_DIR/.gitignore"
run git -C "$SECRETS_DIR" ls-files
[[ "$output" != *"key.txt"* ]]
[[ "$output" != *"key.txt"* ]] || false
}
@test "init guard renders the real clone URL when .secrets-store carries a remote" {
@ -1655,7 +1655,7 @@ gradle_project() {
run "$SECRETS_BIN" init
[ "$status" -eq 1 ]
[[ "$output" == *"git clone git@example.com:me/secrets-work.git"* ]]
[[ "$output" == *"git clone git@example.com:me/secrets-work.git"* ]] || false
}
# ─── EGB-652: `file` external type (whole-file sync, e.g. Android keystore) ──
@ -1680,8 +1680,8 @@ file_project() {
file_project fproj
run "$SECRETS_BIN" which
[ "$status" -eq 0 ]
[[ "$output" == *"file"* ]]
[[ "$output" == *"~/keystores/upload.keystore"* ]]
[[ "$output" == *"file"* ]] || false
[[ "$output" == *"~/keystores/upload.keystore"* ]] || false
}
@test "EGB-652: push encrypts a file-type entry into external/ blob" {
@ -1690,7 +1690,7 @@ file_project() {
file_project fproj
run "$SECRETS_BIN" push fproj
[ "$status" -eq 0 ]
[[ "$output" == *"Encrypted file"* ]]
[[ "$output" == *"Encrypted file"* ]] || false
run bash -c "ls $SECRETS_DIR/fproj/external/*.file.age"
[ "$status" -eq 0 ]
}
@ -1704,7 +1704,7 @@ file_project() {
rm -rf "$HOME/keystores"
run "$SECRETS_BIN" pull fproj
[ "$status" -eq 0 ]
[[ "$output" == *"Restored file"* ]]
[[ "$output" == *"Restored file"* ]] || false
cmp "$HOME/keystores/upload.keystore" "$TEST_TMPDIR/reference"
mode=$(stat -f '%Lp' "$HOME/keystores/upload.keystore" 2>/dev/null || stat -c '%a' "$HOME/keystores/upload.keystore")
[ "$mode" = "600" ]
@ -1730,10 +1730,10 @@ file_project() {
cd "$dir"
run "$SECRETS_BIN" which
[ "$status" -eq 0 ]
[[ "$output" == *"take no keys"* ]]
[[ "$output" == *"take no keys"* ]] || false
# The rejected entry must not be listed as parsed (header only prints
# when at least one entry parses).
[[ "$output" != *"external files ("* ]]
[[ "$output" != *"external files ("* ]] || false
}
@test "EGB-652: file target outside HOME is refused on push" {
@ -1743,7 +1743,7 @@ file_project() {
cd "$dir"
run "$SECRETS_BIN" push fout
[ "$status" -ne 0 ]
[[ "$output" == *"inside \$HOME"* ]] || [[ "$output" == *"Refusing"* ]]
[[ "$output" == *"inside \$HOME"* ]] || [[ "$output" == *"Refusing"* ]] || false
}
@test "EGB-652: gradle-properties entries still work alongside a file entry" {
@ -1755,6 +1755,6 @@ file_project() {
cd "$dir"
run "$SECRETS_BIN" push fmix
[ "$status" -eq 0 ]
[[ "$output" == *"Extracted 1 key"* ]]
[[ "$output" == *"Encrypted file"* ]]
[[ "$output" == *"Extracted 1 key"* ]] || false
[[ "$output" == *"Encrypted file"* ]] || false
}