aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-12 00:56:27 -0500
committerCraig Jennings <c@cjennings.net>2026-05-12 00:56:27 -0500
commit839bbeb14a92a777a3857102dba08a212b21443d (patch)
tree67cadf65b818b17d8421e4063a06d51895e4584b
parent18ba99fda928769adb235bd85b485c8be94c3ddd (diff)
downloaddotemacs-839bbeb14a92a777a3857102dba08a212b21443d.tar.gz
dotemacs-839bbeb14a92a777a3857102dba08a212b21443d.zip
test(scripts): add bats coverage for setup-email.sh password helpers
`setup-email.sh' ran top to bottom, so the only way to exercise `install_encrypted_password' / `decrypt_password' was to run the whole new-machine setup (mbsync, mu init). Its procedural body now lives in a `main()' function guarded by the usual `[[ "${BASH_SOURCE[0]}" == "${0}" ]]' check, so sourcing the script just defines the helpers, and running it directly is unchanged. New `tests/test-setup-email.bats' sources the script, points the password dirs at a per-test tmpdir, and covers both helpers across the normal / skip-existing / missing-source / (for decrypt) gpg-failure paths, stubbing `gpg' so no real key is needed. `make test-bash' runs the bats files, and `make test' picks them up after the Elisp suite when bats is installed.
-rw-r--r--Makefile22
-rwxr-xr-xscripts/setup-email.sh82
-rw-r--r--tests/test-setup-email.bats82
3 files changed, 147 insertions, 39 deletions
diff --git a/Makefile b/Makefile
index 6e45206f..21a2be25 100644
--- a/Makefile
+++ b/Makefile
@@ -6,6 +6,7 @@
# make test-unit - Run unit tests only
# make test-file FILE=test-foo.el - Run specific test file
# make test-name TEST=test-foo-* - Run tests matching pattern
+# make test-bash - Run the bats shell-script tests
# make benchmark - Run performance benchmarks (:perf-tagged tests)
# make coverage - Generate simplecov coverage report
# make coverage-clean - Remove coverage report file
@@ -31,6 +32,7 @@ EMACS_HOME = $(HOME)/.emacs.d
UNIT_TESTS = $(filter-out $(TEST_DIR)/test-integration-%.el, $(wildcard $(TEST_DIR)/test-*.el))
INTEGRATION_TESTS = $(wildcard $(TEST_DIR)/test-integration-%.el)
ALL_TESTS = $(UNIT_TESTS) $(INTEGRATION_TESTS)
+BASH_TESTS = $(wildcard $(TEST_DIR)/*.bats)
# Module files
MODULE_FILES = $(wildcard $(MODULE_DIR)/*.el)
@@ -42,7 +44,7 @@ EMACS_TEST = $(EMACS_BATCH) -L $(TEST_DIR) -L $(MODULE_DIR)
# No colors - using plain text symbols instead
.PHONY: help targets test test-all test-unit test-integration test-file test-name \
- benchmark coverage coverage-clean \
+ test-bash benchmark coverage coverage-clean \
validate-parens validate-modules compile lint profile \
clean clean-compiled clean-tests reset
@@ -61,6 +63,7 @@ help:
@echo " make test-integration - Run integration tests only ($(words $(INTEGRATION_TESTS)) files)"
@echo " make test-file FILE=<filename> - Run specific test file"
@echo " make test-name TEST=<pattern> - Run tests matching pattern"
+ @echo " make test-bash - Run the bats shell-script tests ($(words $(BASH_TESTS)) files)"
@echo " make benchmark - Run performance benchmarks (:perf-tagged)"
@echo ""
@echo " Coverage:"
@@ -92,13 +95,28 @@ help:
test: test-all
test-all:
- @echo "[i] Running all tests ($(words $(ALL_TESTS)) files)..."
+ @echo "[i] Running all tests ($(words $(ALL_TESTS)) Elisp files)..."
@$(MAKE) test-unit
@if [ $(words $(INTEGRATION_TESTS)) -gt 0 ]; then \
$(MAKE) test-integration; \
fi
+ @if [ $(words $(BASH_TESTS)) -gt 0 ] && command -v bats >/dev/null 2>&1; then \
+ $(MAKE) test-bash; \
+ fi
@echo "✓ All tests complete"
+test-bash:
+ @if [ $(words $(BASH_TESTS)) -eq 0 ]; then \
+ echo "No bats tests found"; \
+ exit 0; \
+ fi
+ @if ! command -v bats >/dev/null 2>&1; then \
+ echo "[!] bats not installed — skipping shell-script tests"; \
+ exit 0; \
+ fi
+ @echo "[i] Running bats shell-script tests ($(words $(BASH_TESTS)) files)..."
+ @bats $(BASH_TESTS)
+
test-unit:
@echo "[i] Running unit tests ($(words $(UNIT_TESTS)) files)..."
@echo ""
diff --git a/scripts/setup-email.sh b/scripts/setup-email.sh
index 5d461691..39423c97 100755
--- a/scripts/setup-email.sh
+++ b/scripts/setup-email.sh
@@ -75,41 +75,49 @@ decrypt_password() {
fi
}
-# Decrypt Mail Passwords
-# Skip if destination already exists, install or decrypt if missing.
-echo "→ checking mail passwords..."
-if [[ ! -d "$ENCRYPTED_PASSWORDS_DIR" ]]; then
- echo " ✗ encrypted passwords directory not found: $ENCRYPTED_PASSWORDS_DIR"
- exit 1
+main() {
+ # Decrypt Mail Passwords
+ # Skip if destination already exists, install or decrypt if missing.
+ echo "→ checking mail passwords..."
+ if [[ ! -d "$ENCRYPTED_PASSWORDS_DIR" ]]; then
+ echo " ✗ encrypted passwords directory not found: $ENCRYPTED_PASSWORDS_DIR"
+ exit 1
+ fi
+ mkdir -p "$PASSWORD_DEST_DIR"
+ install_encrypted_password ".gmailpass.gpg"
+ decrypt_password ".cmailpass.gpg" ".cmailpass"
+ install_encrypted_password ".dmailpass.gpg"
+
+ # Check All Prerequisites
+ [[ -x "$MBSYNC" ]] || { echo "ERROR: mbsync not found. Install 'isync'."; exit 1; }
+ [[ -x "$MU" ]] || { echo "ERROR: mu not found. Install 'mu'."; exit 1; }
+ [[ -d "$MU4EDIR" ]] || { echo "ERROR: mu4e elisp not found at $MU4EDIR. Install 'mu'."; exit 1; }
+ [[ -f "$MBSYNCRC" ]] || { echo "ERROR: '~/.mbsyncrc' missing."; exit 1; }
+ [[ -x "$MSMTP" ]] || { echo "ERROR: msmtp not found. Install 'msmtp'."; exit 1; }
+ [[ -f "$MSMTPRC" ]] || { echo "ERROR: '~/.msmtprc' missing."; exit 1; }
+
+ # Ensure Mail Dirs Exist
+ mkdir -p "$GMAILDIR" "$CMAILDIR" "$DMAILDIR"
+
+ # Initial Sync
+ echo "→ syncing all mail with mbsync ..."
+ "$MBSYNC" -aV
+
+ # Init MU and Index Email
+ echo "→ initializing mu ..."
+ "$MU" init --maildir="$MAILROOT" \
+ --my-address="craigmartinjennings@gmail.com" \
+ --my-address="c@cjennings.net" \
+ --my-address="craig.jennings@deepsat.com"
+
+ echo "→ indexing mail ..."
+ "$MU" index
+
+ echo "✅ Mail setup complete."
+}
+
+# Run the setup when executed directly. Sourcing this file (for example from
+# a bats test) just defines the helper functions above.
+if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
+ main "$@"
fi
-mkdir -p "$PASSWORD_DEST_DIR"
-install_encrypted_password ".gmailpass.gpg"
-decrypt_password ".cmailpass.gpg" ".cmailpass"
-install_encrypted_password ".dmailpass.gpg"
-
-# Check All Prerequisites
-[[ -x "$MBSYNC" ]] || { echo "ERROR: mbsync not found. Install 'isync'."; exit 1; }
-[[ -x "$MU" ]] || { echo "ERROR: mu not found. Install 'mu'."; exit 1; }
-[[ -d "$MU4EDIR" ]] || { echo "ERROR: mu4e elisp not found at $MU4EDIR. Install 'mu'."; exit 1; }
-[[ -f "$MBSYNCRC" ]] || { echo "ERROR: '~/.mbsyncrc' missing."; exit 1; }
-[[ -x "$MSMTP" ]] || { echo "ERROR: msmtp not found. Install 'msmtp'."; exit 1; }
-[[ -f "$MSMTPRC" ]] || { echo "ERROR: '~/.msmtprc' missing."; exit 1; }
-
-# Ensure Mail Dirs Exist
-mkdir -p "$GMAILDIR" "$CMAILDIR" "$DMAILDIR"
-
-# Initial Sync
-echo "→ syncing all mail with mbsync ..."
-"$MBSYNC" -aV
-
-# Init MU and Index Email
-echo "→ initializing mu ..."
-"$MU" init --maildir="$MAILROOT" \
- --my-address="craigmartinjennings@gmail.com" \
- --my-address="c@cjennings.net" \
- --my-address="craig.jennings@deepsat.com"
-
-echo "→ indexing mail ..."
-"$MU" index
-
-echo "✅ Mail setup complete."
diff --git a/tests/test-setup-email.bats b/tests/test-setup-email.bats
new file mode 100644
index 00000000..e42335e5
--- /dev/null
+++ b/tests/test-setup-email.bats
@@ -0,0 +1,82 @@
+#!/usr/bin/env bats
+# Tests for the password helpers in scripts/setup-email.sh.
+#
+# `install_encrypted_password' copies a password file from the encrypted
+# assets dir into PASSWORD_DEST_DIR; `decrypt_password' pipes one through
+# `gpg -d' into PASSWORD_DEST_DIR. Both skip when the destination already
+# exists and exit 1 when the source is missing. These tests source the
+# script (which only defines the helpers — `main' runs only when the script
+# is executed directly) and point the two directory vars at a per-test
+# tmpdir, so nothing touches ~/.config or the real mail setup.
+
+setup() {
+ source "${BATS_TEST_DIRNAME}/../scripts/setup-email.sh"
+ ENCRYPTED_PASSWORDS_DIR="${BATS_TEST_TMPDIR}/src"
+ PASSWORD_DEST_DIR="${BATS_TEST_TMPDIR}/dest"
+ mkdir -p "$ENCRYPTED_PASSWORDS_DIR" "$PASSWORD_DEST_DIR"
+}
+
+# --------------------------- install_encrypted_password ---------------------
+
+@test "install_encrypted_password: copies the source and locks it to 600" {
+ printf 'secret' > "$ENCRYPTED_PASSWORDS_DIR/.gmailpass.gpg"
+ run install_encrypted_password ".gmailpass.gpg"
+ [ "$status" -eq 0 ]
+ [ "$(cat "$PASSWORD_DEST_DIR/.gmailpass.gpg")" = "secret" ]
+ [ "$(stat -c '%a' "$PASSWORD_DEST_DIR/.gmailpass.gpg")" = "600" ]
+ [[ "$output" == *"created"* ]]
+}
+
+@test "install_encrypted_password: skips and keeps an existing destination" {
+ printf 'new' > "$ENCRYPTED_PASSWORDS_DIR/.gmailpass.gpg"
+ printf 'kept' > "$PASSWORD_DEST_DIR/.gmailpass.gpg"
+ run install_encrypted_password ".gmailpass.gpg"
+ [ "$status" -eq 0 ]
+ [ "$(cat "$PASSWORD_DEST_DIR/.gmailpass.gpg")" = "kept" ]
+ [[ "$output" == *"already exists, skipping"* ]]
+}
+
+@test "install_encrypted_password: exits 1 when source and destination both missing" {
+ run install_encrypted_password ".gmailpass.gpg"
+ [ "$status" -eq 1 ]
+ [[ "$output" == *"missing"* ]]
+ [ ! -e "$PASSWORD_DEST_DIR/.gmailpass.gpg" ]
+}
+
+# ------------------------------- decrypt_password ---------------------------
+
+@test "decrypt_password: writes the decrypted plaintext and locks it to 600" {
+ printf 'ciphertext' > "$ENCRYPTED_PASSWORDS_DIR/.cmailpass.gpg"
+ gpg() { printf 'plaintext'; } # stub: no real GPG key here
+ run decrypt_password ".cmailpass.gpg" ".cmailpass"
+ [ "$status" -eq 0 ]
+ [ "$(cat "$PASSWORD_DEST_DIR/.cmailpass")" = "plaintext" ]
+ [ "$(stat -c '%a' "$PASSWORD_DEST_DIR/.cmailpass")" = "600" ]
+ [[ "$output" == *"created"* ]]
+}
+
+@test "decrypt_password: skips and keeps an existing destination" {
+ printf 'ciphertext' > "$ENCRYPTED_PASSWORDS_DIR/.cmailpass.gpg"
+ printf 'kept' > "$PASSWORD_DEST_DIR/.cmailpass"
+ gpg() { printf 'plaintext'; }
+ run decrypt_password ".cmailpass.gpg" ".cmailpass"
+ [ "$status" -eq 0 ]
+ [ "$(cat "$PASSWORD_DEST_DIR/.cmailpass")" = "kept" ]
+ [[ "$output" == *"already exists, skipping"* ]]
+}
+
+@test "decrypt_password: exits 1 when the source is missing" {
+ run decrypt_password ".cmailpass.gpg" ".cmailpass"
+ [ "$status" -eq 1 ]
+ [[ "$output" == *"missing"* ]]
+ [ ! -e "$PASSWORD_DEST_DIR/.cmailpass" ]
+}
+
+@test "decrypt_password: removes the partial file and exits 1 when gpg fails" {
+ printf 'ciphertext' > "$ENCRYPTED_PASSWORDS_DIR/.cmailpass.gpg"
+ gpg() { return 1; } # stub: decryption failure
+ run decrypt_password ".cmailpass.gpg" ".cmailpass"
+ [ "$status" -eq 1 ]
+ [[ "$output" == *"failed to decrypt"* ]]
+ [ ! -e "$PASSWORD_DEST_DIR/.cmailpass" ]
+}