From f5ed6617c592139cdc575c4bb34f2ea55732b386 Mon Sep 17 00:00:00 2001 From: jim kolpack Date: Fri, 3 Jul 2026 19:49:04 -0400 Subject: [PATCH] Add bats test suite for image processing scripts Tests cover skip-already-processed logic, cleanup, idempotency, and the config.sh single-source-of-truth invariant. bats-core, bats-assert, and bats-support added as submodules under tests/libs/. --- .gitmodules | 9 ++++ tests/config.bats | 27 ++++++++++ tests/fixtures/processed.jpg | Bin 0 -> 804 bytes tests/fixtures/unprocessed.jpg | Bin 0 -> 316 bytes tests/helpers/common.bash | 30 +++++++++++ tests/images_resize.bats | 50 ++++++++++++++++++ tests/libs/bats-assert | 1 + tests/libs/bats-core | 1 + tests/libs/bats-support | 1 + tests/mocks/magick | 24 +++++++++ tests/zip_images_resize.bats | 90 +++++++++++++++++++++++++++++++++ 11 files changed, 233 insertions(+) create mode 100644 .gitmodules create mode 100644 tests/config.bats create mode 100644 tests/fixtures/processed.jpg create mode 100644 tests/fixtures/unprocessed.jpg create mode 100644 tests/helpers/common.bash create mode 100644 tests/images_resize.bats create mode 160000 tests/libs/bats-assert create mode 160000 tests/libs/bats-core create mode 160000 tests/libs/bats-support create mode 100755 tests/mocks/magick create mode 100644 tests/zip_images_resize.bats diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..d4d44e8 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "tests/libs/bats-core"] + path = tests/libs/bats-core + url = https://github.com/bats-core/bats-core.git +[submodule "tests/libs/bats-assert"] + path = tests/libs/bats-assert + url = https://github.com/bats-core/bats-assert.git +[submodule "tests/libs/bats-support"] + path = tests/libs/bats-support + url = https://github.com/bats-core/bats-support.git diff --git a/tests/config.bats b/tests/config.bats new file mode 100644 index 0000000..9246dc9 --- /dev/null +++ b/tests/config.bats @@ -0,0 +1,27 @@ +#!/usr/bin/env bats + +load 'libs/bats-support/load' +load 'libs/bats-assert/load' +load 'helpers/common' + +@test "config.sh defines MAGICK_COMMENT" { + run bash -c "source '$REPO_ROOT/config.sh' && echo \"\$MAGICK_COMMENT\"" + assert_success + assert_output "magick:resize=8mp,quality=85,sampling=4:2:0" +} + +@test "config.sh defines MAX_PIXELS" { + run bash -c "source '$REPO_ROOT/config.sh' && echo \"\$MAX_PIXELS\"" + assert_success + assert_output "8000000" +} + +@test "MAGICK_COMMENT matches in images_resize.sh and zip_images_resize.sh" { + local comment_from_config comment_in_resize comment_in_zip + comment_from_config=$(bash -c "source '$REPO_ROOT/config.sh' && echo \"\$MAGICK_COMMENT\"") + comment_in_resize=$(grep 'MAGICK_COMMENT=' "$REPO_ROOT/images_resize.sh" | grep -v source || true) + comment_in_zip=$(grep 'MAGICK_COMMENT=' "$REPO_ROOT/zip_images_resize.sh" | grep -v source || true) + # Neither script should define MAGICK_COMMENT itself — only config.sh should + assert_equal "" "$comment_in_resize" + assert_equal "" "$comment_in_zip" +} diff --git a/tests/fixtures/processed.jpg b/tests/fixtures/processed.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6e9533eb0a0ec1d0fa5689a8e570507c68bdcaf2 GIT binary patch literal 804 zcmex=/dev/null 2>&1 + run bash -c "cd '$TEST_WORKDIR' && '$SCRIPT'" + assert_success + assert_output --partial "Skipping" +} + +@test "processes only JPEGs, ignores other file types" { + cp "$FIXTURES/unprocessed.jpg" "$TEST_WORKDIR/img.jpg" + echo "not an image" > "$TEST_WORKDIR/readme.txt" + run bash -c "cd '$TEST_WORKDIR' && '$SCRIPT'" + assert_success + assert_output --partial "Resized" + refute_output --partial "readme.txt" +} diff --git a/tests/libs/bats-assert b/tests/libs/bats-assert new file mode 160000 index 0000000..697471b --- /dev/null +++ b/tests/libs/bats-assert @@ -0,0 +1 @@ +Subproject commit 697471b7a89d3ab38571f38c6c7c4b460d1f5e35 diff --git a/tests/libs/bats-core b/tests/libs/bats-core new file mode 160000 index 0000000..5a7db7a --- /dev/null +++ b/tests/libs/bats-core @@ -0,0 +1 @@ +Subproject commit 5a7db7a98951d9d89b3b5e7800037e655a93345f diff --git a/tests/libs/bats-support b/tests/libs/bats-support new file mode 160000 index 0000000..0954abb --- /dev/null +++ b/tests/libs/bats-support @@ -0,0 +1 @@ +Subproject commit 0954abb9925cad550424cebca2b99255d4eabe96 diff --git a/tests/mocks/magick b/tests/mocks/magick new file mode 100755 index 0000000..700ccfa --- /dev/null +++ b/tests/mocks/magick @@ -0,0 +1,24 @@ +#!/bin/bash +# Mock for ~/Applications/magick. +# identify: delegate to real system identify. +# convert (resize call): copy in-place and embed the comment so the file +# looks processed on the next run. +if [ "$1" = "identify" ]; then + shift + identify "$@" + exit $? +fi + +# Parse -set comment and the output file (last arg) from the argument list +COMMENT="" +OUTFILE="${@: -1}" +args=("$@") +for i in "${!args[@]}"; do + if [ "${args[$i]}" = "-set" ] && [ "${args[$i+1]}" = "comment" ]; then + COMMENT="${args[$i+2]}" + fi +done + +if [ -n "$COMMENT" ]; then + convert "$OUTFILE" -set comment "$COMMENT" "$OUTFILE" +fi diff --git a/tests/zip_images_resize.bats b/tests/zip_images_resize.bats new file mode 100644 index 0000000..b97f674 --- /dev/null +++ b/tests/zip_images_resize.bats @@ -0,0 +1,90 @@ +#!/usr/bin/env bats + +load 'libs/bats-support/load' +load 'libs/bats-assert/load' +load 'helpers/common' + +SCRIPT="$REPO_ROOT/zip_images_resize.sh" + +setup() { + setup_mock_env +} + +teardown() { + teardown_mock_env +} + +@test "prints usage and exits 1 when no arguments given" { + run "$SCRIPT" + assert_failure + assert_output --partial "Usage:" +} + +@test "skips ZIP whose first JPEG already has the processing comment" { + make_zip "$TEST_WORKDIR/archive.zip" "$FIXTURES/processed.jpg" + run "$SCRIPT" "$TEST_WORKDIR/archive.zip" + assert_success + assert_output --partial "Skipping" + assert_output --partial "already processed" +} + +@test "does not skip ZIP whose JPEG has no processing comment" { + make_zip "$TEST_WORKDIR/archive.zip" "$FIXTURES/unprocessed.jpg" + run "$SCRIPT" "$TEST_WORKDIR/archive.zip" + assert_success + refute_output --partial "Skipping" + assert_output --partial "Resized" +} + +@test "processed ZIP is unchanged on disk after skip" { + make_zip "$TEST_WORKDIR/archive.zip" "$FIXTURES/processed.jpg" + local before_size after_size + before_size=$(stat -c%s "$TEST_WORKDIR/archive.zip") + run "$SCRIPT" "$TEST_WORKDIR/archive.zip" + after_size=$(stat -c%s "$TEST_WORKDIR/archive.zip") + assert_equal "$before_size" "$after_size" +} + +@test "after processing, re-running skips the same ZIP" { + make_zip "$TEST_WORKDIR/archive.zip" "$FIXTURES/unprocessed.jpg" + "$SCRIPT" "$TEST_WORKDIR/archive.zip" >/dev/null 2>&1 + run "$SCRIPT" "$TEST_WORKDIR/archive.zip" + assert_success + assert_output --partial "Skipping" +} + +@test "ZIP with no JPEGs inside is processed without error" { + echo "not an image" > "$TEST_WORKDIR/readme.txt" + (cd "$TEST_WORKDIR" && zip -q archive.zip readme.txt) + run "$SCRIPT" "$TEST_WORKDIR/archive.zip" + assert_success + refute_output --partial "Skipping" + refute_output --partial "Error:" +} + +@test "no temp files left in /dev/shm after processing" { + make_zip "$TEST_WORKDIR/archive.zip" "$FIXTURES/unprocessed.jpg" + local before after + before=$(find /dev/shm -maxdepth 1 -name 'tmp.*' | sort) + run "$SCRIPT" "$TEST_WORKDIR/archive.zip" + after=$(find /dev/shm -maxdepth 1 -name 'tmp.*' | sort) + assert_equal "$before" "$after" +} + +@test "no temp files left in /dev/shm after skipping" { + make_zip "$TEST_WORKDIR/archive.zip" "$FIXTURES/processed.jpg" + local before after + before=$(find /dev/shm -maxdepth 1 -name 'tmp.*' | sort) + run "$SCRIPT" "$TEST_WORKDIR/archive.zip" + after=$(find /dev/shm -maxdepth 1 -name 'tmp.*' | sort) + assert_equal "$before" "$after" +} + +@test "processes multiple ZIPs, skipping already-processed ones" { + make_zip "$TEST_WORKDIR/done.zip" "$FIXTURES/processed.jpg" + make_zip "$TEST_WORKDIR/todo.zip" "$FIXTURES/unprocessed.jpg" + run "$SCRIPT" "$TEST_WORKDIR/done.zip" "$TEST_WORKDIR/todo.zip" + assert_success + assert_output --partial "Skipping" + assert_output --partial "Resized" +}