Skip to content

test: bats unit suite + shellcheck CI gating Docker publish#86

Open
PhilippMundhenk wants to merge 4 commits into
masterfrom
test/initial-shell-test-suite
Open

test: bats unit suite + shellcheck CI gating Docker publish#86
PhilippMundhenk wants to merge 4 commits into
masterfrom
test/initial-shell-test-suite

Conversation

@PhilippMundhenk

Copy link
Copy Markdown
Owner

Summary

Adds focused shell-script test coverage and wires it into the publish workflows so a Docker image can no longer be built/pushed if static analysis or unit tests fail. Also fixes the real bugs in existing scripts that shellcheck surfaced.

What is tested

Script Covered behaviour
trigger_telegram.sh env gating (token/chatid), correct token URL, url-encoded POST body
trigger_inotify.sh env gating, sshpass argv contains user@host + remote path, non-zero exit on ssh failure
sendtoftps.sh env gating, curl invoked with credentials/URL/file, non-zero exit + diagnostic output on failure
remove_blank.sh no-op when REMOVE_BLANK_THRESHOLD unset; pdfinfo/gs/pdftk pipeline runs when set
scantoemail-0.2.4-1.sh trigger banner is logged and scanRear.sh is invoked with forwarded argv
scantoocr.sh / scantoimage.sh placeholder scripts still emit the "not implemented" notice
All shell scripts parse with bash -n; script/ files keep the executable bit; Dockerfile copies script/ into the image and installs pdfinfo/gs/pdftk

Mocking is done by shadowing external commands (curl, wget, sshpass, pdfinfo, gs, pdftk, nawk, bc) via a PATH-prefixed sandbox directory — see tests/helpers/common.bash for mock_command / mock_record.

CI integration

test.yml gains a workflow_call trigger so the three existing publish workflows (publishMaster, publishBranchesPRs, publishTags) can each declare a tests job that reuses it, with needs: tests on the build job. A shellcheck or bats failure now aborts the Docker build instead of silently shipping a broken image.

Drive-by fixes uncovered by shellcheck

Running shellcheck --severity=error against the existing scripts flagged real bugs. Fixed in this PR so CI is green from day one:

  • Missing shebangs in run.sh, update-container.sh, script/sendtoftps.sh, script/trigger_inotify.sh.
  • Unquoted array expansion in script/scanRear.sh and script/scantofile-0.2.4-1.sh (gm convert ${gm_opts[@]}"${gm_opts[@]}").
  • Unquoted $@ in script/scantoemail-0.2.4-1.sh (would drop spaces in a forwarded friendly-name).
  • exit -1 in files/runScanner.shexit 1 (exit codes are 0–255).
  • Broken NAME default in files/runScanner.sh: if [[ -z {$NAME} ]]; then $NAME="Scanner"; fi — literal braces meant the test was always false (SC2157) and the LHS was a syntax error when NAME is empty (SC2281). Replaced with the intended if [[ -z "$NAME" ]]; then NAME="Scanner"; fi. Rarely triggered because the Dockerfile sets ENV NAME="Scanner", but now it actually works.

What is not covered

The full scantofile / scanRear scan pipeline (would need hours of mocked-up fixtures or live hardware), runScanner.sh interface detection (deeply tied to host networking), and the lighttpd PHP front-end. Worth a follow-up if you want broader coverage.

The OCR queue tests will land with #85 once that PR merges; this branch is based on master and so doesn't see the queue scripts yet.

Test plan

  • bats tests/unit — all green in CI.
  • shellcheck --severity=error — clean in CI.
  • Confirm make test / make lint work locally for a maintainer with bats and shellcheck installed.
  • Verify publish workflows actually wait on tests (this will be visible the next time any of them runs).

🤖 Generated with Claude Code

PhilippMundhenk and others added 3 commits May 17, 2026 18:20
Adds focused test coverage for the existing shell scripts plus a
GitHub Actions workflow that runs both static analysis and the unit
suite on every push (except master) and every pull request.

Coverage:

  trigger_telegram.sh   - env gating (token, chat id); wget invoked
                          with the right token URL and url-encoded body
  trigger_inotify.sh    - env gating; sshpass argv contains user@host
                          and remote path; non-zero exit when sshpass
                          fails
  sendtoftps.sh         - env gating; curl argv contains credentials,
                          ftp:// URL and file; non-zero exit + diagnostic
                          output on curl failure
  remove_blank.sh       - no-op when REMOVE_BLANK_THRESHOLD is unset;
                          pdfinfo/gs/pdftk pipeline runs when set
  scantoemail.sh        - prints trigger banner and delegates to
                          scanRear.sh with forwarded argv
  scantoocr.sh,         - placeholder scripts still emit the
  scantoimage.sh          "not implemented" notice
  script_lint           - every shell script parses with `bash -n`,
                          script/ files keep the executable bit, the
                          Dockerfile copies script/ into the image and
                          installs the packages remove_blank depends on

Mocking is done by shadowing external commands (`curl`, `wget`,
`sshpass`, `pdfinfo`, `gs`, `pdftk`, `nawk`, `bc`) via a PATH-prefixed
sandbox directory. See tests/helpers/common.bash for `mock_command`
and `mock_record`.

CI installs `bats` and `shellcheck` from the Ubuntu repos. Shellcheck
runs at --severity=error so pre-existing style issues do not break the
build; tighten once those are cleaned up.

Makefile gains `test`, `lint`, `bats` targets.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* test.yml gains a workflow_call trigger so it can be reused.
* publishMaster.yml, publishBranchesPRs.yml and publishTags.yml each
  add a `tests` job that calls test.yml, and `needs: tests` on the
  build-and-push-image job, so a shellcheck or bats failure aborts
  the Docker build instead of silently shipping a broken image.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The new test workflow runs shellcheck --severity=error and surfaced
several real bugs/quality issues in the existing scripts. None of them
change observable behaviour for typical Docker usage, but the fixes
let CI go green and prevent footguns:

  run.sh, update-container.sh,
  script/sendtoftps.sh,
  script/trigger_inotify.sh
    + missing shebang line (SC2148)

  script/scanRear.sh,
  script/scantofile-0.2.4-1.sh
    `gm convert ${gm_opts[@]}` -> `"${gm_opts[@]}"` so options
    containing spaces (or future additions) are not word-split (SC2068)

  script/scantoemail-0.2.4-1.sh
    `scanRear.sh $@` -> `scanRear.sh "$@"` so a friendly-name with
    spaces is forwarded as one argument (SC2068)

  files/runScanner.sh
    * `exit -1` -> `exit 1`. Exit codes are 0..255; -1 wraps to 255
      and is non-portable across shells (SC2242).
    * `if [[ -z {$NAME} ]]; then $NAME="Scanner"; fi`
      The literal-braces test was always false (SC2157) and the LHS
      `$NAME=...` is a syntax error when NAME is empty (SC2281), so
      the intended "default NAME to Scanner" never ran. The Dockerfile
      sets NAME via ENV so this rarely bit anyone, but the fixed form
      now actually defaults correctly when the env var is missing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
PhilippMundhenk added a commit that referenced this pull request May 17, 2026
ocr_enqueue.bats (5 cases): argument validation, job file format,
atomic-rename hygiene (no leftover .tmp), sortable timestamp prefix,
FIFO ordering of two enqueues.

ocr_worker.bats (6 cases): startup recovery sweeps in_progress/ back
to pending/; successful upload removes the job and writes the output
PDF; REMOVE_ORIGINAL_AFTER_OCR removes the source PDF on success;
repeated curl failures land in failed/ with ATTEMPTS=MAX; missing
OCR_SERVER/PORT/PATH fails the job rather than looping forever;
missing input PDF fails the job. Worker tests use a copy of the
script with the linear backoff sed'd down to zero and a short
OCR_QUEUE_POLL_SECONDS so a full failure cycle completes in seconds.

To make the worker testable without a writable /scans, add an
OCR_OUTPUT_DIR env var (defaults to /scans, override in tests to a
sandbox dir).

tests/helpers/common.bash is identical to the file added in #86 so a
later merge of either branch is a clean no-op.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant