secrets/test/test_helper.bash
Brian Majewski 7e6ddf3a12
v0.1.0.0 feat: multi-store support (EGB-281) (#1)
* feat: multi-store support via .secrets-store + --store flag

Layer four-rule store resolution on top of the existing SECRETS_DIR primitive
so users can manage multiple isolated encrypted stores (work vs personal,
per-client, etc.) without giving up the tool's small-bash-script pitch.

Resolution order (highest first):
  1. --store <dir>  flag (parsed in main pre-pass)
  2. .secrets-store file in cwd or any ancestor up to $HOME
  3. SECRETS_DIR    env var (legacy escape hatch)
  4. ~/.secrets     default

resolve_store() updates both SECRETS_DIR and KEY_FILE so existing single-store
codepaths just work. New cmd_which / where / status report the active store.
cmd_init, push, pull, push_workspaces, pull_workspaces, list, rm, rekey, run,
which all call resolve_store at entry.

Hardening from the EGB-281 adversarial review:
- F1: cmd_run EXIT trap is now a named function (not string-interpolated),
  so paths with apostrophes still get plaintext cleaned up
- F2: symlinked .secrets-store files are skipped, never read
- F3/F4: --store flag rejects flag-shaped values and empty --store=
- F5: HOME unset is detected up-front with a directed error
- F11: check_initialized / check_key give context-aware errors that name
  both recovery paths (git clone vs secrets init) when a teammate clones
  a project bound to a non-existent store on their machine

Tests: 37 → 66 (29 new). HOME=\$TEST_TMPDIR added to test setup so the
walk-up logic stays bounded inside fixtures.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: add Multiple stores section to README

Five subsections walk users through: how store resolution works, how to
set up a second store on a machine, how to bind a project, how teammates
join a bound project, and how to undo or change a binding. SECRETS_DIR
table entry now points readers at the new --store flag and .secrets-store
file as the preferred mechanisms.

* chore: bump version and changelog (v0.1.0.0)

First formal release. EGB-281 adds multi-store support; this commit
seeds the VERSION file (4-digit MAJOR.MINOR.PATCH.MICRO) and the
CHANGELOG.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:30:28 -07:00

70 lines
2 KiB
Bash

#!/usr/bin/env bash
# Shared setup/teardown for secrets bats tests.
# Creates isolated temp directories for each test — no side effects.
SECRETS_BIN="$(cd "$(dirname "${BATS_TEST_FILENAME}")/.." && pwd)/secrets"
setup() {
# Check age is available
if ! command -v age >/dev/null 2>&1; then
skip "age is not installed"
fi
# Create isolated temp environment
export TEST_TMPDIR
TEST_TMPDIR=$(mktemp -d)
# Isolate HOME so .secrets-store walk-up cannot stray into the real
# developer's home directory (or pick up files in / if HOME happens to
# not be a real ancestor of /tmp). EGB-281 F9.
export HOME="$TEST_TMPDIR"
# Secrets repo lives in temp
export SECRETS_DIR="$TEST_TMPDIR/secrets-repo"
# Working directory for simulating project dirs (kept under $HOME so
# the .secrets-store walk-up logic, which is bounded by $HOME, can find
# files placed in test fixtures).
export WORK_DIR="$HOME/work"
mkdir -p "$WORK_DIR"
# Create a bare "remote" repo for push/pull testing
export REMOTE_DIR="$TEST_TMPDIR/remote.git"
git init --bare "$REMOTE_DIR" >/dev/null 2>&1
}
teardown() {
rm -rf "$TEST_TMPDIR"
}
# Helper: initialize secrets and add remote
init_with_remote() {
run "$SECRETS_BIN" init
cd "$SECRETS_DIR"
git remote add origin "$REMOTE_DIR"
# Initial commit so push works
git commit --allow-empty -m "init" >/dev/null 2>&1
git push -u origin main >/dev/null 2>&1 || git push -u origin master >/dev/null 2>&1
cd -
}
# Helper: create .env files in a temp project dir and cd into it
create_project_dir() {
local name="${1:-testproj}"
local dir="$WORK_DIR/$name"
mkdir -p "$dir"
echo "SECRET_KEY=abc123" > "$dir/.env"
echo "DB_HOST=staging.db.example.com" > "$dir/.env.staging"
cd "$dir"
}
# Helper: create a project dir bound to a store via .secrets-store file
create_bound_project_dir() {
local name="$1"
local store_value="$2"
local dir="$WORK_DIR/$name"
mkdir -p "$dir"
echo "SECRET_KEY=abc123" > "$dir/.env"
echo "$store_value" > "$dir/.secrets-store"
cd "$dir"
}