#!/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 }