feat: .secrets.json manifest core — add command, rails, canonical form (EGB-677 stage 1)
- secrets add <path>: explicit manifest writer, idempotent, atomic write - _validate_dotenv_rel_path: project-relative confinement rail (no .. / absolute / shell metas; @ allowed for npm-scoped workspace dirs) - _check_manifest_file: refuses symlinks, malformed JSON (jq error with file named), unsupported schema versions (directed upgrade error) - canonical serialization: jq --sort-keys + sorted/deduped dotenv — add order produces byte-identical manifests - which: validates + summarizes the manifest (doubles as linter) - jq required only when a manifest exists/is written
This commit is contained in:
parent
6dbc4e0d01
commit
18018dbd3b
2 changed files with 272 additions and 0 deletions
125
test/manifest.bats
Normal file
125
test/manifest.bats
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
#!/usr/bin/env bats
|
||||
# EGB-677 stage 1: .secrets.json manifest — parse, rails, add, generators.
|
||||
|
||||
load test_helper
|
||||
|
||||
# ─── A: manifest core — secrets add + rails + canonical form ──────────
|
||||
|
||||
@test "add creates .secrets.json with version 2 and the dotenv entry" {
|
||||
create_project_dir addproj
|
||||
run "$SECRETS_BIN" add .env
|
||||
[ "$status" -eq 0 ]
|
||||
[ -f ".secrets.json" ]
|
||||
run jq -r '.version' .secrets.json
|
||||
[ "$output" = "2" ]
|
||||
run jq -r '.dotenv[0]' .secrets.json
|
||||
[ "$output" = ".env" ]
|
||||
}
|
||||
|
||||
@test "add is idempotent — no duplicate entries" {
|
||||
create_project_dir addproj
|
||||
"$SECRETS_BIN" add .env >/dev/null
|
||||
run "$SECRETS_BIN" add .env
|
||||
[ "$status" -eq 0 ]
|
||||
run jq -r '.dotenv | length' .secrets.json
|
||||
[ "$output" = "1" ]
|
||||
}
|
||||
|
||||
@test "add accepts nested workspace paths" {
|
||||
create_project_dir addproj
|
||||
mkdir -p packages/web
|
||||
echo "K=v" > packages/web/.env.development
|
||||
run "$SECRETS_BIN" add packages/web/.env.development
|
||||
[ "$status" -eq 0 ]
|
||||
run jq -r '.dotenv | index("packages/web/.env.development") != null' .secrets.json
|
||||
[ "$output" = "true" ]
|
||||
}
|
||||
|
||||
@test "add accepts npm-scoped workspace paths (@)" {
|
||||
create_project_dir addproj
|
||||
mkdir -p "packages/@acme/web"
|
||||
echo "K=v" > "packages/@acme/web/.env"
|
||||
run "$SECRETS_BIN" add "packages/@acme/web/.env"
|
||||
[ "$status" -eq 0 ]
|
||||
run jq -r '.dotenv | index("packages/@acme/web/.env") != null' .secrets.json
|
||||
[ "$output" = "true" ]
|
||||
}
|
||||
|
||||
@test "add rejects path traversal (..)" {
|
||||
create_project_dir addproj
|
||||
run "$SECRETS_BIN" add ../escape/.env
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" == *"project-relative"* ]] || false
|
||||
[ ! -f ".secrets.json" ]
|
||||
}
|
||||
|
||||
@test "add rejects absolute paths" {
|
||||
create_project_dir addproj
|
||||
run "$SECRETS_BIN" add /etc/passwd
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" == *"project-relative"* ]] || false
|
||||
[ ! -f ".secrets.json" ]
|
||||
}
|
||||
|
||||
@test "add rejects shell metacharacters in path" {
|
||||
create_project_dir addproj
|
||||
run "$SECRETS_BIN" add '.env;rm -rf ~'
|
||||
[ "$status" -eq 1 ]
|
||||
[ ! -f ".secrets.json" ]
|
||||
}
|
||||
|
||||
@test "add requires the file to exist" {
|
||||
create_project_dir addproj
|
||||
run "$SECRETS_BIN" add .env.missing
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" == *"not found"* ]] || false
|
||||
}
|
||||
|
||||
@test "manifest serialization is canonical — order of adds does not matter" {
|
||||
create_project_dir addproj
|
||||
echo "A=1" > .env.alpha
|
||||
echo "B=2" > .env.beta
|
||||
"$SECRETS_BIN" add .env.alpha >/dev/null
|
||||
"$SECRETS_BIN" add .env.beta >/dev/null
|
||||
cp .secrets.json "$TEST_TMPDIR/order1.json"
|
||||
rm .secrets.json
|
||||
"$SECRETS_BIN" add .env.beta >/dev/null
|
||||
"$SECRETS_BIN" add .env.alpha >/dev/null
|
||||
cmp -s .secrets.json "$TEST_TMPDIR/order1.json"
|
||||
}
|
||||
|
||||
@test "which shows manifest summary when .secrets.json is present" {
|
||||
create_project_dir addproj
|
||||
"$SECRETS_BIN" add .env >/dev/null
|
||||
run "$SECRETS_BIN" which
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *".secrets.json"* ]] || false
|
||||
[[ "$output" == *".env"* ]] || false
|
||||
}
|
||||
|
||||
@test "malformed .secrets.json dies with a directed error naming the file" {
|
||||
create_project_dir addproj
|
||||
echo '{ not json' > .secrets.json
|
||||
run "$SECRETS_BIN" which
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" == *".secrets.json"* ]] || false
|
||||
[[ "$output" == *"invalid"* ]] || false
|
||||
}
|
||||
|
||||
@test "unsupported manifest version dies with a directed upgrade error" {
|
||||
create_project_dir addproj
|
||||
echo '{"version": 99, "dotenv": [".env"]}' > .secrets.json
|
||||
run "$SECRETS_BIN" which
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" == *"version 99"* ]] || false
|
||||
[[ "$output" == *"supports"* ]] || false
|
||||
}
|
||||
|
||||
@test "symlinked .secrets.json is refused" {
|
||||
create_project_dir addproj
|
||||
echo '{"version":2,"dotenv":[".env"]}' > "$TEST_TMPDIR/real-manifest.json"
|
||||
ln -s "$TEST_TMPDIR/real-manifest.json" .secrets.json
|
||||
run "$SECRETS_BIN" which
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" == *"symlink"* ]] || false
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue