Skip to content

Commit 56bee15

Browse files
authored
Merge pull request #6 from TUBAF-IfI-LiaScript/copilot/unify-makefile-and-action
tests: migrate to bats/cram/remake test frameworks
2 parents d2c8c44 + b9a0a46 commit 56bee15

8 files changed

Lines changed: 1021 additions & 3 deletions

File tree

README.md

Lines changed: 108 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,24 @@ SCORM_SCORE = 80
108108
├── digitalesysteme.yml # Kurskonfiguration
109109
├── digitalesysteme.html # Generierte Webseite
110110
├── scripts/
111-
│ ├── check_changes.sh # Intelligente Change-Detection
112-
│ ├── prune_pdfs.sh # Entfernt unreferenzierte PDFs
113-
│ ├── detect_changes.sh # GitHub-Action: Änderungserkennung
111+
│ ├── check_changes.sh # Intelligente Change-Detection (verwendet vom Makefile)
112+
│ ├── detect_changes.sh # Änderungserkennung (GitHub Action)
113+
│ ├── courses.conf # Kurs → Upstream-Repo Mapping
114+
│ ├── courses_lib.sh # Shared library für courses.conf lookups
114115
│ ├── generate_courses.sh # GitHub-Action: Kurs-Generierung
116+
│ ├── prune_pdfs.sh # Entfernt unreferenzierte PDFs
115117
│ └── deployment_summary.sh # GitHub-Action: Deployment-Zusammenfassung
118+
├── tests/
119+
│ ├── run_tests.sh # Test-Runner (bats + cram + remake)
120+
│ ├── bats/ # Shell-Unit-Tests (bats-core)
121+
│ │ ├── check_changes.bats
122+
│ │ ├── detect_changes.bats
123+
│ │ └── courses_lib.bats
124+
│ ├── cram/ # CLI-Integrationstests (cram)
125+
│ │ ├── check_changes.t
126+
│ │ └── detect_changes.t
127+
│ └── remake/ # Makefile-Tests (remake)
128+
│ └── Makefile.test
116129
├── .cache/ # Cache für Change-Detection (von Git ignoriert)
117130
│ └── digitalesysteme # Hash-Cache (YAML + Remote)
118131
├── assets/
@@ -219,3 +232,95 @@ Das System erkennt automatisch neue Commits in den überwachten Repositories:
219232
- **Mit Änderungen**: ~3-5 Minuten (komplette PDF-Generierung)
220233
- **Ohne Änderungen**: ~3-5 Sekunden (Change-Detection + Skip)
221234
- **Überwachte Repositories**: 4 aktive + 1 index (nur lokal)
235+
236+
## Tests
237+
238+
Die Change-Detection-Logik ist durch eine dreistufige Test-Suite in `tests/` abgedeckt:
239+
240+
| Tool | Zweck | Dateien |
241+
|------|-------|---------|
242+
| **[bats](https://github.com/bats-core/bats-core)** | Shell-Unit-Tests | `tests/bats/*.bats` |
243+
| **[cram](https://bitheap.org/cram/)** | CLI-Integrationstests | `tests/cram/*.t` |
244+
| **[remake](https://bashdb.sourceforge.net/remake/)** | Makefile-Tests | `tests/remake/Makefile.test` |
245+
246+
### ▶️ Tests ausführen
247+
248+
```bash
249+
bash tests/run_tests.sh
250+
```
251+
252+
Einzelne Frameworks können auch direkt aufgerufen werden:
253+
254+
```bash
255+
# nur bats
256+
bats tests/bats/
257+
258+
# nur cram
259+
REPO_ROOT=$(pwd) cram --shell=bash tests/cram/*.t
260+
261+
# nur remake
262+
remake -f tests/remake/Makefile.test test
263+
```
264+
265+
### 📁 Test-Struktur
266+
267+
| Datei | Framework | Beschreibung |
268+
|-------|-----------|-------------|
269+
| `tests/run_tests.sh` || Führt alle drei Suiten aus |
270+
| `tests/bats/check_changes.bats` | bats | 12 Unit-Tests für `check_changes.sh` |
271+
| `tests/bats/detect_changes.bats` | bats | 6 Unit-Tests für `detect_changes.sh` |
272+
| `tests/bats/courses_lib.bats` | bats | 11 Unit-Tests für `courses_lib.sh` |
273+
| `tests/cram/check_changes.t` | cram | CLI-Integrationstests für `check_changes.sh` |
274+
| `tests/cram/detect_changes.t` | cram | CLI-Integrationstests für `detect_changes.sh` |
275+
| `tests/remake/Makefile.test` | remake | 8 Tests für Makefile-Variablen und Targets |
276+
277+
### 🧪 Testfälle für `check_changes.sh` (bats + cram)
278+
279+
| Szenario | Erwartetes Verhalten |
280+
|----------|----------------------|
281+
| Kein Argument übergeben | Usage-Meldung, Exit ≠ 0 |
282+
| YAML-Datei fehlt | Fehlermeldung, Exit ≠ 0 |
283+
| Kein Cache, kein HTML | Exit 0 (Rebuild nötig) |
284+
| Kein Cache, HTML vorhanden | Exit 0 (kein Cached-Hash → Rebuild) |
285+
| Cache + HTML stimmen überein | Exit 1 (kein Rebuild, "No changes detected") |
286+
| YAML-Hash geändert | Exit 0, Grund "YAML file changed" |
287+
| Remote-Hash geändert | Exit 0, Grund "Remote repository changed" |
288+
| HTML-Datei fehlt | Exit 0, Grund "HTML file missing" |
289+
| Remote nicht erreichbar, sonst unverändert | Exit 1 (unreachable wird ignoriert) |
290+
| Remote nicht erreichbar, YAML geändert | Exit 0 (Rebuild trotzdem ausgelöst) |
291+
| Kurs ohne Remote-Mapping, aktuell | Exit 1 |
292+
| Kurs ohne Remote-Mapping, YAML geändert | Exit 0 |
293+
294+
### 🧪 Testfälle für `detect_changes.sh` (bats + cram)
295+
296+
| Szenario | Erwartetes Verhalten |
297+
|----------|----------------------|
298+
| Keine YAML-Änderungen, alle HTML vorhanden | `courses_to_generate` ist leer |
299+
| YAML geändert (git diff) | Kurs erscheint in `courses_to_generate` |
300+
| HTML-Datei fehlt | Kurs erscheint in `courses_to_generate` und `missing_html` |
301+
| YAML geändert UND HTML fehlt | Kurs erscheint in beiden Outputs |
302+
| `.github/workflows/*.yml` geändert | Workflow-Datei wird nicht als Kurs behandelt |
303+
| Mehrere Kurse, nur einer geändert | Nur der geänderte Kurs wird regeneriert |
304+
305+
### 🧪 Testfälle für `courses_lib.sh` (bats)
306+
307+
| Szenario | Erwartetes Verhalten |
308+
|----------|----------------------|
309+
| Kein Argument (CLI) | Usage-Meldung, Exit ≠ 0 |
310+
| Kurs mit Mapping (z. B. `digitalesysteme`) | Gibt korrekten Repo-Namen aus |
311+
| Kurs ohne Mapping (z. B. `index`) | Leere Ausgabe |
312+
| Unbekannter Kurs | Leere Ausgabe |
313+
| Als Bibliothek gesourct | `lookup_repo()` gibt korrekte Werte zurück |
314+
315+
### 🧪 Testfälle für das Makefile (remake)
316+
317+
| Test | Was wird geprüft |
318+
|------|-----------------|
319+
| `test-courses-var` | `COURSES`-Variable enthält alle 5 Kurse |
320+
| `test-pdf-courses-var` | `PDF_COURSES` enthält die 4 PDF-Kurse |
321+
| `test-scorm-score-var` | `SCORM_SCORE = 80` |
322+
| `test-scorm-org-var` | `SCORM_ORG` ist nicht leer |
323+
| `test-phony-targets` | Wichtige Targets als `.PHONY` deklariert |
324+
| `test-help-target` | `make help` gibt "Available targets" aus |
325+
| `test-status-target` | `make status` gibt "Build Status" aus |
326+
| `test-clean-cache-target` | `make clean-cache` räumt `.cache/` auf |

tests/bats/check_changes.bats

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
#!/usr/bin/env bats
2+
# Unit tests for scripts/check_changes.sh
3+
#
4+
# Approach
5+
# --------
6+
# setup() creates an isolated working directory, changes into it, and puts a
7+
# mock `curl` binary at the front of PATH so no real network calls are made.
8+
# Each @test runs in its own subshell, so the cd in setup() is local to that
9+
# test. teardown() removes the temp directories.
10+
#
11+
# check_changes.sh sources courses_lib.sh from its own (production) scripts/
12+
# directory, so the real courses.conf is used. Tests use real course names:
13+
# digitalesysteme – has an upstream mapping in courses.conf
14+
# index – listed in COURSES but has NO upstream mapping
15+
16+
REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../.." && pwd)"
17+
SCRIPT="$REPO_ROOT/scripts/check_changes.sh"
18+
19+
# ---------------------------------------------------------------------------
20+
# Per-test lifecycle
21+
# ---------------------------------------------------------------------------
22+
setup() {
23+
TEST_DIR="$(mktemp -d)"
24+
MOCK_BIN="$(mktemp -d)"
25+
# Change into the isolated work directory so check_changes.sh reads/writes
26+
# YAML, HTML, and .cache files from there.
27+
cd "$TEST_DIR"
28+
}
29+
30+
teardown() {
31+
rm -rf "$TEST_DIR" "$MOCK_BIN"
32+
}
33+
34+
# make_mock_curl <sha>
35+
# Writes a mock curl binary to $MOCK_BIN.
36+
# Pass "ERROR" to simulate a network failure (empty response).
37+
make_mock_curl() {
38+
local sha="$1"
39+
if [ "$sha" = "ERROR" ]; then
40+
cat > "$MOCK_BIN/curl" << 'MOCK'
41+
#!/usr/bin/env bash
42+
echo ""
43+
exit 0
44+
MOCK
45+
else
46+
# shellcheck disable=SC2016
47+
printf '#!/usr/bin/env bash\necho '"'"'{"sha":"%s","commit":{}}'"'"'\n' "$sha" \
48+
> "$MOCK_BIN/curl"
49+
fi
50+
chmod +x "$MOCK_BIN/curl"
51+
}
52+
53+
# ---------------------------------------------------------------------------
54+
# Argument handling
55+
# ---------------------------------------------------------------------------
56+
57+
@test "no argument: prints usage and exits non-zero" {
58+
run bash "$SCRIPT"
59+
[ "$status" -ne 0 ]
60+
[[ "$output" == *"Usage:"* ]]
61+
}
62+
63+
@test "missing YAML: exits non-zero with error message" {
64+
make_mock_curl "abc123"
65+
run env PATH="$MOCK_BIN:$PATH" bash "$SCRIPT" "digitalesysteme"
66+
[ "$status" -ne 0 ]
67+
[[ "$output" == *"not found"* ]]
68+
}
69+
70+
# ---------------------------------------------------------------------------
71+
# First-run (no cache)
72+
# ---------------------------------------------------------------------------
73+
74+
@test "no cache, no HTML: rebuild needed (exit 0)" {
75+
make_mock_curl "deadbeef1234"
76+
echo "title: Test" > digitalesysteme.yml
77+
78+
run env PATH="$MOCK_BIN:$PATH" bash "$SCRIPT" "digitalesysteme"
79+
[ "$status" -eq 0 ]
80+
[[ "$output" == *"rebuild needed"* ]]
81+
}
82+
83+
@test "no cache, HTML present: rebuild needed because no cached hash" {
84+
make_mock_curl "deadbeef1234"
85+
echo "title: Test" > digitalesysteme.yml
86+
touch digitalesysteme.html
87+
88+
run env PATH="$MOCK_BIN:$PATH" bash "$SCRIPT" "digitalesysteme"
89+
[ "$status" -eq 0 ]
90+
}
91+
92+
# ---------------------------------------------------------------------------
93+
# Cache matches – nothing changed
94+
# ---------------------------------------------------------------------------
95+
96+
@test "cache matches YAML + remote + HTML present: no rebuild (exit 1)" {
97+
local remote_sha="aabbccddeeff0011"
98+
make_mock_curl "$remote_sha"
99+
100+
echo "title: Test" > digitalesysteme.yml
101+
touch digitalesysteme.html
102+
103+
mkdir -p .cache
104+
local yaml_hash
105+
yaml_hash=$(sha256sum digitalesysteme.yml | cut -d' ' -f1)
106+
printf "%s\n%s\n" "$yaml_hash" "$remote_sha" > .cache/digitalesysteme
107+
108+
run env PATH="$MOCK_BIN:$PATH" bash "$SCRIPT" "digitalesysteme"
109+
[ "$status" -eq 1 ]
110+
[[ "$output" == *"No changes detected"* ]]
111+
}
112+
113+
# ---------------------------------------------------------------------------
114+
# Individual change triggers
115+
# ---------------------------------------------------------------------------
116+
117+
@test "YAML changed: rebuild needed with reason" {
118+
local remote_sha="aabbccddeeff0011"
119+
make_mock_curl "$remote_sha"
120+
121+
echo "title: Test" > digitalesysteme.yml
122+
touch digitalesysteme.html
123+
124+
mkdir -p .cache
125+
printf "%s\n%s\n" "stale_yaml_hash_value_0000" "$remote_sha" \
126+
> .cache/digitalesysteme
127+
128+
run env PATH="$MOCK_BIN:$PATH" bash "$SCRIPT" "digitalesysteme"
129+
[ "$status" -eq 0 ]
130+
[[ "$output" == *"YAML file changed"* ]]
131+
}
132+
133+
@test "remote hash changed: rebuild needed with reason" {
134+
local new_sha="newsha9999"
135+
make_mock_curl "$new_sha"
136+
137+
echo "title: Test" > digitalesysteme.yml
138+
touch digitalesysteme.html
139+
140+
mkdir -p .cache
141+
local yaml_hash
142+
yaml_hash=$(sha256sum digitalesysteme.yml | cut -d' ' -f1)
143+
printf "%s\n%s\n" "$yaml_hash" "oldsha1111" > .cache/digitalesysteme
144+
145+
run env PATH="$MOCK_BIN:$PATH" bash "$SCRIPT" "digitalesysteme"
146+
[ "$status" -eq 0 ]
147+
[[ "$output" == *"Remote repository changed"* ]]
148+
}
149+
150+
@test "HTML file missing: rebuild needed with reason" {
151+
local remote_sha="aabbccddeeff0011"
152+
make_mock_curl "$remote_sha"
153+
154+
echo "title: Test" > digitalesysteme.yml
155+
# No HTML file
156+
157+
mkdir -p .cache
158+
local yaml_hash
159+
yaml_hash=$(sha256sum digitalesysteme.yml | cut -d' ' -f1)
160+
printf "%s\n%s\n" "$yaml_hash" "$remote_sha" > .cache/digitalesysteme
161+
162+
run env PATH="$MOCK_BIN:$PATH" bash "$SCRIPT" "digitalesysteme"
163+
[ "$status" -eq 0 ]
164+
[[ "$output" == *"HTML file missing"* ]]
165+
}
166+
167+
# ---------------------------------------------------------------------------
168+
# Remote unreachable
169+
# ---------------------------------------------------------------------------
170+
171+
@test "remote unreachable, nothing else changed: no rebuild (exit 1)" {
172+
make_mock_curl "ERROR"
173+
174+
echo "title: Test" > digitalesysteme.yml
175+
touch digitalesysteme.html
176+
177+
mkdir -p .cache
178+
local yaml_hash
179+
yaml_hash=$(sha256sum digitalesysteme.yml | cut -d' ' -f1)
180+
printf "%s\n%s\n" "$yaml_hash" "unreachable" > .cache/digitalesysteme
181+
182+
run env PATH="$MOCK_BIN:$PATH" bash "$SCRIPT" "digitalesysteme"
183+
[ "$status" -eq 1 ]
184+
}
185+
186+
@test "remote unreachable but YAML changed: rebuild still triggered" {
187+
make_mock_curl "ERROR"
188+
189+
echo "title: Test" > digitalesysteme.yml
190+
touch digitalesysteme.html
191+
192+
mkdir -p .cache
193+
printf "%s\n%s\n" "stale_yaml_hash" "unreachable" > .cache/digitalesysteme
194+
195+
run env PATH="$MOCK_BIN:$PATH" bash "$SCRIPT" "digitalesysteme"
196+
[ "$status" -eq 0 ]
197+
}
198+
199+
# ---------------------------------------------------------------------------
200+
# Course without a remote mapping (index)
201+
# ---------------------------------------------------------------------------
202+
203+
@test "no remote mapping, cache current: no rebuild (exit 1)" {
204+
make_mock_curl "SHOULD_NOT_BE_CALLED"
205+
206+
echo "title: Index" > index.yml
207+
touch index.html
208+
209+
mkdir -p .cache
210+
local yaml_hash
211+
yaml_hash=$(sha256sum index.yml | cut -d' ' -f1)
212+
printf "%s\n%s\n" "$yaml_hash" "no-remote" > .cache/index
213+
214+
run env PATH="$MOCK_BIN:$PATH" bash "$SCRIPT" "index"
215+
[ "$status" -eq 1 ]
216+
}
217+
218+
@test "no remote mapping, YAML changed: rebuild needed" {
219+
make_mock_curl "SHOULD_NOT_BE_CALLED"
220+
221+
echo "title: Index" > index.yml
222+
touch index.html
223+
224+
mkdir -p .cache
225+
printf "%s\n%s\n" "stale_hash" "no-remote" > .cache/index
226+
227+
run env PATH="$MOCK_BIN:$PATH" bash "$SCRIPT" "index"
228+
[ "$status" -eq 0 ]
229+
}

0 commit comments

Comments
 (0)