refactor: dedup external extractor + read guards, warn on legacy-pull nested blobs (EGB-701)
EGB-677 stage-1 structural cleanups, no behavior change for the v2 happy path: 1. cmd_which reuses _json_external_entries (the push/pull extractor) instead of its own duplicated jq @tsv projection, so `which` applies the same properties->gradle-properties normalization + skip-with-warning rules the sync path does and can't drift from it. 2. The two external-manifest read guards are factored into _json_readable (plain regular file, silent) / _legacy_readable (warn+skip a symlinked .secrets-files), shared by _external_entries_for_push/_pull. 3. The legacy (manifest-less) pull path now warns when nested <project>/<relpath>.age blobs exist that its non-recursive globs can't see (external/ excluded — pull_external_files handles those), so it never silently under-restores. Tests: +4 in test/manifest.bats (normalized which display, malformed external skipped by which, nested-blob warning fires, external-only no false warning). Full suite green (286/286). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
57f1280262
commit
1ee096cc33
3 changed files with 132 additions and 23 deletions
|
|
@ -642,6 +642,31 @@ m_nojq_path() {
|
|||
[[ "$output" == *"k1"* ]] || false
|
||||
}
|
||||
|
||||
# EGB-701 item 1: `which` and the push/pull external extractor share one
|
||||
# helper (_json_external_entries), so `which` applies the same
|
||||
# properties→gradle-properties normalization the sync path uses — no drift.
|
||||
@test "which normalizes a properties external to the gradle-properties token (EGB-701)" {
|
||||
create_project_dir whichnorm
|
||||
printf '{"version":2,"dotenv":[".env"],"external":[{"type":"properties","path":"~/.gradle/gradle.properties","keys":["k1"]}]}\n' > .secrets.json
|
||||
run "$SECRETS_BIN" which
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"gradle-properties"* ]] || false
|
||||
}
|
||||
|
||||
# EGB-701 item 1: a malformed external (a properties entry with no keys) is
|
||||
# skipped by the sync path; routing `which` through the shared extractor means
|
||||
# `which` skips+warns it too, so it faithfully shows what actually syncs
|
||||
# rather than printing an entry push/pull silently drop.
|
||||
@test "which skips a malformed external entry the sync path would drop (EGB-701)" {
|
||||
create_project_dir whichmalformed
|
||||
printf '{"version":2,"dotenv":[".env"],"external":[{"type":"properties","path":"~/.gradle/gradle.properties"}]}\n' > .secrets.json
|
||||
run "$SECRETS_BIN" which
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"has no keys"* ]] || false
|
||||
# The skipped entry's path must NOT appear in the printed manifest summary.
|
||||
[[ "$output" != *" gradle-properties ~/.gradle/gradle.properties"* ]] || false
|
||||
}
|
||||
|
||||
# ─── F: ship Step 7 coverage backfill (audit gaps) ─────────────────────
|
||||
|
||||
@test "which flags an unsafe dotenv entry with the UNSAFE marker" {
|
||||
|
|
@ -739,6 +764,46 @@ m_nojq_path() {
|
|||
[ "$(cat packages/web/.env.development)" = "N=nested" ]
|
||||
}
|
||||
|
||||
@test "legacy (manifest-less) pull warns about nested blobs it can't restore (EGB-701)" {
|
||||
# The legacy pull path globs only top-level *.age/.*.age. A nested dotenv
|
||||
# blob (<project>/<relpath>.age) written by a manifest-driven push on another
|
||||
# machine is invisible to those globs — restored nothing, counted nothing.
|
||||
# The fix: warn so a manifest-less pull never silently under-restores.
|
||||
init_with_remote
|
||||
create_project_dir nestlegacy
|
||||
mkdir -p packages/web
|
||||
echo "N=nested" > packages/web/.env.development
|
||||
"$SECRETS_BIN" add packages/web/.env.development >/dev/null
|
||||
"$SECRETS_BIN" push >/dev/null 2>&1
|
||||
[ -f "$SECRETS_DIR/nestlegacy/packages/web/.env.development.age" ]
|
||||
# Simulate a machine with no manifest: drop .secrets.json + local files,
|
||||
# forcing the legacy non-recursive glob branch.
|
||||
rm -f .secrets.json
|
||||
rm -rf packages
|
||||
run "$SECRETS_BIN" pull nestlegacy
|
||||
[ "$status" -eq 0 ]
|
||||
# The warning names the nested blob and points at the manifest as the fix.
|
||||
[[ "$output" == *"packages/web/.env.development"* ]] || false
|
||||
[[ "$output" == *"$SECRETS_JSON_NAME"* || "$output" == *".secrets.json"* ]] || false
|
||||
# The legacy path genuinely can't restore it (the warning is the contract).
|
||||
[ ! -f packages/web/.env.development ]
|
||||
}
|
||||
|
||||
@test "legacy pull does NOT warn about external/ blobs (handled separately, EGB-701)" {
|
||||
# external/<slug>.age blobs are restored by pull_external_files, not the
|
||||
# dotenv globs, so they must not trip the nested-blob warning.
|
||||
init_with_remote
|
||||
m_gradle_src $'beaconClerkPkTest=pk_test_abc\n'
|
||||
create_project_dir extnolwarn
|
||||
printf 'gradle-properties ~/.gradle/gradle.properties beaconClerkPkTest\n' > .secrets-files
|
||||
"$SECRETS_BIN" push >/dev/null 2>&1
|
||||
[ -d "$SECRETS_DIR/extnolwarn/external" ]
|
||||
run "$SECRETS_BIN" pull extnolwarn
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" != *"can't restore"* ]] || false
|
||||
[[ "$output" != *"nested encrypted"* ]] || false
|
||||
}
|
||||
|
||||
@test "list shows a nested manifest blob" {
|
||||
init_with_remote
|
||||
create_project_dir nestlist
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue