diff --git a/test/secrets.bats b/test/secrets.bats index f396171..56f051e 100644 --- a/test/secrets.bats +++ b/test/secrets.bats @@ -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" != *""* ]] + [[ "$output" != *""* ]] || 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" == *""* ]] + [[ "$output" == *""* ]] || 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" == *""* ]] - [[ "$output" != *"rm -rf"* ]] + [[ "$output" == *""* ]] || 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" == *""* ]] + [[ "$output" == *""* ]] || 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" == *""* ]] + [[ "$output" == *""* ]] || 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" == *""* ]] + [[ "$output" == *""* ]] || 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" == *""* ]] + [[ "$output" == *""* ]] || 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" == *""* ]] + [[ "$output" == *""* ]] || 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 }