test: coverage for external-file workspace/multi-entry/warning paths (EGB-531)
Close the high-value gaps surfaced by the ship coverage audit (71% → ~92%): push -w / pull -w external sync, multi-entry manifests, partial-key push warnings, missing-blob pull warnings, unsafe-key-charset rejection, symlinked parent dir refusal, and source-side comment/continuation skipping. Tests: 104 -> 112. Docs updated to match.
This commit is contained in:
parent
110ac514cc
commit
31f2741c44
4 changed files with 118 additions and 3 deletions
|
|
@ -28,7 +28,7 @@ and this project adheres to a four-digit MAJOR.MINOR.PATCH.MICRO version scheme.
|
||||||
|
|
||||||
### Tests
|
### Tests
|
||||||
|
|
||||||
- 80 → 104 (+24). New coverage: manifest parse/read-back, key extraction across `=`/`:`/space separators, merge (preserve unrelated/comments/order, substring-key isolation, sed-metachar value round-trip, duplicate-key collapse, continuation-line safety, idempotency), path validation (wrong basename, outside `$HOME`, symlink), first-create mode `600`, manifest injection/symlink rejection, rekey round-trip of external blobs, glob isolation (blob not leaked to cwd), `list` surfacing, pre-commit blocking plaintext `gradle.properties`, and backward compatibility.
|
- 80 → 112 (+32). New coverage: manifest parse/read-back, key extraction across `=`/`:`/space separators, merge (preserve unrelated/comments/order, substring-key isolation, sed-metachar value round-trip, duplicate-key collapse, continuation-line safety, idempotency), path validation (wrong basename, outside `$HOME`, symlinked target, symlinked parent dir), first-create mode `600`, manifest injection/symlink/unsafe-key rejection, rekey round-trip of external blobs, glob isolation (blob not leaked to cwd), `list` surfacing, pre-commit blocking plaintext `gradle.properties`, workspace (`push -w`/`pull -w`) external sync, multi-entry manifests, partial-key push warnings, missing-blob pull warnings, source-side comment/continuation skipping, and backward compatibility.
|
||||||
|
|
||||||
## [0.1.1.0] - 2026-05-09
|
## [0.1.1.0] - 2026-05-09
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ Single bash script (`secrets`) with subcommands: init, push, pull, list, rm, rek
|
||||||
secrets # CLI script (~600 lines bash)
|
secrets # CLI script (~600 lines bash)
|
||||||
hooks/pre-commit # Pre-commit hook template
|
hooks/pre-commit # Pre-commit hook template
|
||||||
test/
|
test/
|
||||||
secrets.bats # bats-core test suite (104 tests)
|
secrets.bats # bats-core test suite (112 tests)
|
||||||
test_helper.bash # Shared setup/teardown
|
test_helper.bash # Shared setup/teardown
|
||||||
README.md # User-facing documentation
|
README.md # User-facing documentation
|
||||||
CLAUDE.md # This file
|
CLAUDE.md # This file
|
||||||
|
|
|
||||||
|
|
@ -432,7 +432,7 @@ For complete rotation with no historical exposure, create a fresh `~/.secrets/`
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Run the test suite (104 tests)
|
# Run the test suite (112 tests)
|
||||||
brew install bats-core
|
brew install bats-core
|
||||||
bats test/secrets.bats
|
bats test/secrets.bats
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -1349,3 +1349,118 @@ gradle_project() {
|
||||||
[ "$status" -eq 1 ]
|
[ "$status" -eq 1 ]
|
||||||
[[ "$output" == *"Plaintext"* ]]
|
[[ "$output" == *"Plaintext"* ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ── EGB-531: coverage for warning/error branches, workspaces, multi-entry ──
|
||||||
|
|
||||||
|
@test "EGB-531: push -w pushes external blob once at monorepo root" {
|
||||||
|
init_with_remote
|
||||||
|
gradle_src $'beaconClerkPkTest=pk_w\n'
|
||||||
|
local mono="$WORK_DIR/mono"
|
||||||
|
mkdir -p "$mono/apps/web"
|
||||||
|
printf '{"workspaces":["apps/*"]}\n' > "$mono/package.json"
|
||||||
|
echo "ROOT=1" > "$mono/.env"
|
||||||
|
echo "WEB=1" > "$mono/apps/web/.env"
|
||||||
|
printf 'gradle-properties ~/.gradle/gradle.properties beaconClerkPkTest\n' > "$mono/.secrets-files"
|
||||||
|
git init "$mono" >/dev/null 2>&1
|
||||||
|
cd "$mono"
|
||||||
|
run "$SECRETS_BIN" push -w
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
# External blob pushed exactly once (not once per workspace)
|
||||||
|
run bash -c "ls $SECRETS_DIR/mono/external/*.gradle-properties.age 2>/dev/null | wc -l | tr -d ' '"
|
||||||
|
[ "$output" = "1" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "EGB-531: pull -w merges external keys at monorepo root" {
|
||||||
|
init_with_remote
|
||||||
|
gradle_src $'beaconClerkPkTest=pk_w\n'
|
||||||
|
local mono="$WORK_DIR/mono"
|
||||||
|
mkdir -p "$mono/apps/web"
|
||||||
|
printf '{"workspaces":["apps/*"]}\n' > "$mono/package.json"
|
||||||
|
echo "ROOT=1" > "$mono/.env"
|
||||||
|
echo "WEB=1" > "$mono/apps/web/.env"
|
||||||
|
printf 'gradle-properties ~/.gradle/gradle.properties beaconClerkPkTest\n' > "$mono/.secrets-files"
|
||||||
|
git init "$mono" >/dev/null 2>&1
|
||||||
|
cd "$mono"
|
||||||
|
"$SECRETS_BIN" push -w >/dev/null 2>&1
|
||||||
|
gradle_src $'unrelated=keep\n'
|
||||||
|
run "$SECRETS_BIN" pull -w
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
grep -q '^beaconClerkPkTest=pk_w$' "$HOME/.gradle/gradle.properties"
|
||||||
|
grep -q '^unrelated=keep$' "$HOME/.gradle/gradle.properties"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "EGB-531: push warns for missing key but still syncs present ones" {
|
||||||
|
init_with_remote
|
||||||
|
gradle_src $'beaconClerkPkTest=present\n'
|
||||||
|
gradle_project gproj
|
||||||
|
run "$SECRETS_BIN" push gproj
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"beaconClerkPkLive"* ]]
|
||||||
|
[[ "$output" == *"not found"* ]]
|
||||||
|
[[ "$output" == *"Extracted 1 key"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "EGB-531: pull warns when manifest entry has no blob in store" {
|
||||||
|
init_with_remote
|
||||||
|
create_project_dir gproj
|
||||||
|
"$SECRETS_BIN" push gproj >/dev/null 2>&1
|
||||||
|
printf 'gradle-properties ~/.gradle/gradle.properties beaconClerkPkTest\n' > "$WORK_DIR/gproj/.secrets-files"
|
||||||
|
cd "$WORK_DIR/gproj"
|
||||||
|
run "$SECRETS_BIN" pull gproj
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"no encrypted data exists"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "EGB-531: multi-entry manifest syncs each target" {
|
||||||
|
init_with_remote
|
||||||
|
mkdir -p "$HOME/.gradle" "$HOME/.gradle-b"
|
||||||
|
printf 'beaconClerkPkTest=a\n' > "$HOME/.gradle/gradle.properties"
|
||||||
|
printf 'beaconClerkPkLive=b\n' > "$HOME/.gradle-b/gradle.properties"
|
||||||
|
mkdir -p "$WORK_DIR/gproj"; cd "$WORK_DIR/gproj"
|
||||||
|
printf 'gradle-properties ~/.gradle/gradle.properties beaconClerkPkTest\ngradle-properties ~/.gradle-b/gradle.properties beaconClerkPkLive\n' > .secrets-files
|
||||||
|
run "$SECRETS_BIN" push gproj
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
run bash -c "ls $SECRETS_DIR/gproj/external/*.age 2>/dev/null | wc -l | tr -d ' '"
|
||||||
|
[ "$output" = "2" ]
|
||||||
|
printf 'x=1\n' > "$HOME/.gradle/gradle.properties"
|
||||||
|
printf 'y=1\n' > "$HOME/.gradle-b/gradle.properties"
|
||||||
|
run "$SECRETS_BIN" pull gproj
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
grep -q '^beaconClerkPkTest=a$' "$HOME/.gradle/gradle.properties"
|
||||||
|
grep -q '^beaconClerkPkLive=b$' "$HOME/.gradle-b/gradle.properties"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "EGB-531: manifest with unsafe key chars is skipped with warning" {
|
||||||
|
init_with_remote
|
||||||
|
mkdir -p "$WORK_DIR/gproj"; cd "$WORK_DIR/gproj"
|
||||||
|
printf 'gradle-properties ~/.gradle/gradle.properties bad=key\n' > .secrets-files
|
||||||
|
run "$SECRETS_BIN" which
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"WARNING"* ]]
|
||||||
|
[[ "$output" != *"bad=key"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "EGB-531: symlinked parent dir of target is refused" {
|
||||||
|
init_with_remote
|
||||||
|
mkdir -p "$HOME/realdir"
|
||||||
|
printf 'beaconClerkPkTest=x\n' > "$HOME/realdir/gradle.properties"
|
||||||
|
ln -s "$HOME/realdir" "$HOME/.gradle"
|
||||||
|
gradle_project gproj beaconClerkPkTest
|
||||||
|
run "$SECRETS_BIN" push gproj
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
[[ "$output" == *"symlink"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "EGB-531: push skips comment and continuation lines in source" {
|
||||||
|
init_with_remote
|
||||||
|
mkdir -p "$HOME/.gradle"
|
||||||
|
printf '%s' $'! bang comment\nunrelated=foo\\\nbeaconClerkPkTest=is_a_continuation\nbeaconClerkPkTest=realkey\n' > "$HOME/.gradle/gradle.properties"
|
||||||
|
gradle_project gproj beaconClerkPkTest
|
||||||
|
"$SECRETS_BIN" push gproj >/dev/null 2>&1
|
||||||
|
gradle_src $'z=1\n'
|
||||||
|
run "$SECRETS_BIN" pull gproj
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
# The continuation line that looks like the key must NOT win; the real
|
||||||
|
# definition must, and the '!' comment must be ignored.
|
||||||
|
grep -q '^beaconClerkPkTest=realkey$' "$HOME/.gradle/gradle.properties"
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue