Sammelcommit fuer den ueber mehrere Sitzungen gewachsenen, bisher nicht
eingecheckten Stand. Getrennte Commits waeren ehrlicher gewesen, sind es
nachtraeglich aber nicht mehr: BugRepo, changelog.json und die Doku tragen
Aenderungen aus allen Straengen gleichzeitig, und keine Zwischenstufe waere
lauffaehig.
ROADMAP IM DEPLOYMENTCENTER (2.7.0)
Planungsstand, Reihenfolge und Zustaendigkeit lagen in handgepflegten
Markdown-Dateien neben einem Bugtracker, der genau diese drei Dinge bereits
fuehrt. Zwei Pfleger fuer dasselbe Feld - Drift war eine Frage der Zeit.
Ab jetzt: Zustand im DC, Begruendung in der Datei. Kein Feld hat zwei Pfleger.
- Migration 016: epic, roadmap_rank, target_build, defer_trigger,
doc_anchor, Status "deferred", Tabelle roadmap_epics.
KEINE Roadmap-Tabelle - ein Eintrag ist ein bugtracker_item.
- Zugehoerigkeit ist ein Feld: Ein Item steht auf der Roadmap, wenn es ein
epic hat. Ein zweites Kennzeichen daneben koennte auseinanderlaufen.
- Kennung ist die Item-ID. Handvergebene Kuerzel ("A12") erzeugen die
schlimmste Fehlerart: Beide Seiten stimmig, Verschiedenes gemeint, weil
die Kennung nach einer Loeschung neu vergeben wurde.
- Zurueckstellen verlangt defer_trigger. Ohne benannten Anlass ist ein
Vorhaben nicht zurueckgestellt, sondern vergessen. Die Regel sitzt im
Repository, damit sie auf allen drei Schreibwegen gleich greift.
- Modules/Bugtracker/Roadmap.php, /api/bugtracker/v1/roadmap.php,
WebUI-Modul mit Filtern, cli/roadmap.php --write/--check,
dc_roadmap und dc_roadmap_set im MCP-Adapter.
- cli/roadmap.php --write ERZEUGT den Zustandsteil der Markdown-Datei;
--check prueft nur die Prosa. Was erzeugt wird, kann nicht driften.
- docs/ROADMAP_MIGRATION.md: Uebernahme fuer die anderen Projekte.
TESTINSTANZ UND TESTTOR
- dc-dev.mhdf.de: zweite Installation, eigene DB, eigene Schluessel,
Basic-Auth davor, Rocket.Chat aus.
- 124 Testfaelle in tests/e2e gegen API, Rechte, Migrationen, Sperre.
- scripts/deploy.py bringt vor jedem Upload erst den anstehenden Stand auf
die Testinstanz und laesst dann den Testlauf laufen. Erst bei
Rueckgabewert 0 geht etwas auf die Produktion (--ohne-test als Notausgang).
Andersherum prueft der Lauf aelteren Code als den, der gleich hochgeht.
- tests/ ist aus dem Produktiv-Deploy ausgeschlossen; testctl.php kann
nicht versehentlich auf dc.mhdf.de landen.
- Ein Lauf mit mehr uebersprungenen als geprueften Faellen endet mit
Rueckgabewert 2 statt 0. Nichts ist kaputt, geprueft wurde es aber auch
nicht - der Unterschied darf nicht am Rueckgabewert verschwinden.
SUPERVISOR, PROJEKTLANDSCHAFT, NACHPRUEFUNG (2.6.0/2.6.1)
- Migration 013: projektuebergreifend melden (dev_agent, evidence,
triage_external, affects).
- Migration 014: supervisor_findings, Katalog, fuenf serverseitige
Detektoren im Tick.
- Migration 015: Nachpruefung als Arbeitsschlange statt Benachrichtigung.
- DEV-Lizenzschluessel zur Selbstbedienung, dc-testkit fuer die Test-VM.
- MCP-Adapter: 15 Werkzeuge, ohne Fremdabhaengigkeiten, mit Abgrenzung
von Fremddaten gegen Prompt-Injektion.
BEHOBEN
- observe und status im Supervisor legten den Zustand des Befundes als
"status" in die Antwort. Http::ok() merged ['status' => 'success']
darunter, also gewann der Befundzustand - eine angenommene Meldung kam
als status="open" an. Heisst jetzt finding_status. War Item #59.
Migrationen 013-016 sind auf dem Produktivserver noch NICHT ausgefuehrt.
Ohne sie bricht das WebUI, weil die Roadmap-Ansicht bei jedem Seitenaufruf
gegen bugtracker_items.epic abfragt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
290 lines
12 KiB
Markdown
290 lines
12 KiB
Markdown
# Testinstanz und automatisierter Testlauf
|
|
|
|
**Gilt für:** `https://dc-dev.mhdf.de` — eine vollständige zweite Installation des
|
|
Deploymentcenters, die jederzeit gelöscht und neu aufgebaut werden darf.
|
|
|
|
> **Bewusst nicht unter `public/`.** Diese Datei nennt interne Adressen und den Weg, die
|
|
> Datenbank der Testinstanz zu leeren. `/docs/` auf dem Deploymentcenter ist öffentlich
|
|
> erreichbar; dort gehört das nicht hin. Zugangsdaten stehen ausschließlich in
|
|
> `Serverdaten.txt` und `scripts/deploy_dev_config.json` — beide sind per `.gitignore`
|
|
> ausgeschlossen und werden nie hochgeladen.
|
|
|
|
---
|
|
|
|
## 1. Wozu es sie gibt — und wozu nicht
|
|
|
|
| | Testinstanz `dc-dev.mhdf.de` | Test-VM (siehe [TESTUMGEBUNG.md](TESTUMGEBUNG.md)) |
|
|
|---|---|---|
|
|
| Prüft | **den Server**: API, Rechte, Migrationen, Zugangssperre | **den Client**: Installer, Updateagent, Oberfläche |
|
|
| Datenbestand | wird bei jedem Lauf gelöscht | bleibt |
|
|
| Zugriff | HTTPS + Basic-Auth | SSH |
|
|
|
|
Beides zusammen deckt die Kette ab. Getrennt zu halten ist Absicht: Ein Testlauf, der
|
|
Tabellen löscht, darf nicht auf derselben Maschine laufen wie ein Test, der eine
|
|
Installation über Stunden beobachtet.
|
|
|
|
**Was die Testinstanz nicht prüft:** die Produktion. Sie hat eigene Schlüssel, eine eigene
|
|
Datenbank und **Rocket.Chat ist abgeschaltet** (`rocketchat.enabled = false`). Ein Testlauf,
|
|
der echte Kanäle vollschreibt, wäre der Fehler, der am ehesten unbemerkt teuer wird.
|
|
|
|
---
|
|
|
|
## 2. Aufbau
|
|
|
|
```
|
|
dc-dev.mhdf.de Subdomain, Docroot = /deploymentcenter_dev/ auf dem FTP
|
|
├── .htaccess Sperre + Routen (erzeugt, siehe §5)
|
|
├── config/
|
|
│ ├── .htpasswd erzeugt aus scripts/deploy_dev_config.json
|
|
│ ├── config.php erzeugt aus scripts/dev/config.dev.php.tmpl
|
|
│ └── release-signing.pem auf der Instanz erzeugt (testctl genkey)
|
|
├── public/ src/ sql/ var/ cli/ wie in der Produktion
|
|
└── testctl.php nur hier — Steuerung des Testlaufs (§6)
|
|
```
|
|
|
|
Im Repository:
|
|
|
|
| Datei | Zweck |
|
|
|---|---|
|
|
| `scripts/deploy_dev.py` | Deployment auf die Testinstanz |
|
|
| `scripts/deploy_dev_config.json` | Zugangsdaten (**nicht im Git**, Vorlage: `…example.json`) |
|
|
| `scripts/dev/htaccess-prelude.conf` | der Sperrblock, der der `.htaccess` vorangestellt wird |
|
|
| `scripts/dev/config.dev.php.tmpl` | Vorlage der `config.php` der Testinstanz |
|
|
| `tests/server/testctl.php` | wird nach `/testctl.php` hochgeladen — **nur** von `deploy_dev.py` |
|
|
| `tests/e2e/` | der Testlauf |
|
|
|
|
`scripts/deploy.py` (Produktion) schließt `tests/` vollständig aus. `testctl.php` kann nicht
|
|
versehentlich auf `dc.mhdf.de` landen.
|
|
|
|
---
|
|
|
|
## 3. Deployment
|
|
|
|
```bash
|
|
python scripts/deploy_dev.py --dry-run
|
|
```
|
|
|
|
```bash
|
|
python scripts/deploy_dev.py
|
|
```
|
|
|
|
Überträgt nur geänderte Dateien (eigener Cache: `scripts/.deploy_dev_cache.json`).
|
|
`--full` ignoriert den Cache.
|
|
|
|
Drei Dinge macht das Skript anders als das Produktiv-Deployment:
|
|
|
|
1. **Die `.htaccess` wird erzeugt** — der Sperrblock aus `scripts/dev/htaccess-prelude.conf`
|
|
plus die `.htaccess` des Repositories. Eine zweite gepflegte Kopie der Routen gibt es
|
|
damit nicht; Änderungen an den Routen wirken auf beiden Instanzen.
|
|
2. **Die `config.php` wird erzeugt** — mit der Testdatenbank, eigenen Schlüsseln, `debug =
|
|
true` und abgeschaltetem Rocket.Chat. Die Produktivkonfiguration wird nie übertragen.
|
|
3. **`testctl.php` kommt dazu.**
|
|
|
|
Das Skript bricht ab, wenn die Zugangsdaten auf den Produktivserver zeigen oder `base_url`
|
|
kein `dc-dev` enthält.
|
|
|
|
### Das Testtor vor der Produktion
|
|
|
|
`scripts/deploy.py` (Produktion) führt seit dem 29.08.2026 **vor** jedem Upload aus:
|
|
|
|
1. `scripts/deploy_dev.py` — der Stand, der gleich hochgehen soll, kommt auf die Testinstanz
|
|
2. `tests/e2e/run.py` — der vollständige Testlauf
|
|
|
|
Erst bei Rückgabewert 0 beginnt der Upload auf `dc.mhdf.de`. Schlägt etwas fehl, wird
|
|
**nichts** übertragen.
|
|
|
|
Die Reihenfolge ist der Punkt: Liefe der Testlauf gegen den vorhandenen Dev-Stand, prüfte er
|
|
einen älteren Code als den, der gleich produktiv geht — und gäbe grünes Licht für etwas, das
|
|
er nie gesehen hat.
|
|
|
|
```bash
|
|
python scripts/deploy.py --ohne-test
|
|
```
|
|
|
|
Der Notausgang. Er gehört in die Hand eines Menschen, der weiß, warum er ihn nimmt —
|
|
`--dry-run` läuft ohnehin ohne Testlauf, weil dabei nichts übertragen wird.
|
|
|
|
---
|
|
|
|
## 4. Testlauf
|
|
|
|
```bash
|
|
python tests/e2e/run.py
|
|
```
|
|
|
|
Der Lauf setzt zuerst die Datenbank zurück, spielt alle Migrationen ein, legt einen
|
|
Administrator und ein Master-Token an — und prüft dann in dieser Reihenfolge:
|
|
|
|
| Gruppe | Was geprüft wird |
|
|
|---|---|
|
|
| `sperre` | Basic-Auth, gesperrte Verzeichnisse, Sicherheitskopfzeilen, `testctl`-Schlösser |
|
|
| `kern` | Health, OpenAPI, Migration (auch: wiederholbar), 404/405/400-Verhalten |
|
|
| `token` | Master-/Sub-Token, Rechteeinschränkung, keine Rechteausweitung |
|
|
| `bugtracker` | melden, Idempotenz, Arbeit holen, Reservierung, Kommentar, Abschluss |
|
|
| `roadmap` | Epics, Einplanung, Lesewege |
|
|
| `fehlerstrom` | Ausnahmen aus Clients |
|
|
| `lizenz` | Entwicklungsschlüssel, Prüfung, Rückgabe |
|
|
| `watchdog` | Lebenszeichen, Ereignisse, Kennzahlen, Auswertung |
|
|
| `update` | Release veröffentlichen, Update finden bzw. nicht finden, Signierschlüssel |
|
|
| `setup` | Erstinstallation: Anmeldung, Katalog, Projekttoken, Monitor |
|
|
| `supervisor` | Befunde melden, lesen, einstufen |
|
|
|
|
Nützliche Schalter:
|
|
|
|
```bash
|
|
python tests/e2e/run.py --gruppe sperre --gruppe kern
|
|
```
|
|
|
|
```bash
|
|
python tests/e2e/run.py --nur roadmap --log
|
|
```
|
|
|
|
```bash
|
|
python tests/e2e/run.py --bericht bericht.json --kein-reset
|
|
```
|
|
|
|
| Schalter | Wirkung |
|
|
|---|---|
|
|
| `--gruppe X` | nur diese Gruppe (mehrfach möglich) |
|
|
| `--nur TEXT` | Namensfilter |
|
|
| `--kein-reset` | Datenbestand behalten (dann `master_token` in der Konfiguration hinterlegen) |
|
|
| `--log` | bei Fehlschlägen das Serverlog der Instanz anhängen |
|
|
| `--stopp` | beim ersten Fehlschlag abbrechen |
|
|
| `--bericht DATEI` | Ergebnisse als JSON |
|
|
| `--liste` | nur auflisten, nichts ausführen |
|
|
| `--ipv4` | IPv4 erzwingen (siehe §8) |
|
|
|
|
**Rückgabewert:** `0` alles bestanden, `1` Fehlschläge, `2` der Lauf sagt nichts aus.
|
|
Damit lässt er sich ohne Auswertung der Ausgabe in eine Pipeline hängen.
|
|
|
|
`2` kommt in zwei Fällen: der Lauf konnte gar nicht starten (Zurücksetzen fehlgeschlagen,
|
|
kein Testfall passt zur Auswahl) — oder es wurde **mehr übersprungen als geprüft**.
|
|
|
|
Der zweite Fall trifft fast nur gefilterte Läufe. Übersprungen wird, wenn eine
|
|
Voraussetzung aus einem früheren Testfall fehlt, und bei `--gruppe`/`--nur` ist der oft
|
|
mit weggefiltert: `ctx['freies_item']` etwa entsteht in der Gruppe `bugtracker`, wird aber
|
|
in `roadmap` gebraucht. Vorher endete so ein Lauf mit „2 bestanden, 0 fehlgeschlagen,
|
|
5 übersprungen" und Rückgabewert 0 — grün, obwohl fünf von sieben Fällen nie liefen.
|
|
|
|
Nichts ist dabei kaputt; geprüft wurde es aber auch nicht, und der Unterschied zwischen
|
|
„kaputt" und „nicht prüfbar" darf nicht ausgerechnet am Rückgabewert verschwinden. Der
|
|
Bericht führt dasselbe als `aussagekräftig: false`.
|
|
|
|
### Einen Testfall ergänzen
|
|
|
|
```python
|
|
@test('bugtracker', 'Kurze Aussage, was gelten soll')
|
|
def _(ctx):
|
|
"""Warum das wichtig ist - eine Zeile, kein Roman."""
|
|
r = ctx.client.post('/api/bugtracker/v1/report', body={...})
|
|
erwarte_erfolg(r)
|
|
ctx['bug_id'] = r.feld('item_id') # steht dem nächsten Testfall zur Verfügung
|
|
```
|
|
|
|
Die Reihenfolge der Registrierung ist die Reihenfolge der Ausführung; die Importe in
|
|
`run.py` legen die Reihenfolge der Dateien fest. Fehlt eine Voraussetzung (`ctx['bug_id']`
|
|
wurde nie gesetzt), wird der Testfall **übersprungen** statt fälschlich rot — der
|
|
Unterschied zwischen „kaputt" und „nicht prüfbar" muss im Bericht sichtbar bleiben.
|
|
|
|
### Bekannte Befunde
|
|
|
|
Ein gefundener Fehler bekommt zweierlei: ein Item im Bugtracker **und** einen Testfall, der
|
|
ihn festhält. Damit dieser Testfall nicht jedes Deployment blockiert, bis der Fehler behoben
|
|
ist, steht er in `tests/e2e/bekannte_befunde.json`:
|
|
|
|
```json
|
|
"supervisor/Antwortumschlag von observe bleibt erkennbar": {
|
|
"item": 59,
|
|
"seit": "2026-08-29",
|
|
"grund": "observe ueberschreibt das Umschlagfeld 'status' mit dem Zustand des Befundes."
|
|
}
|
|
```
|
|
|
|
Der Fall läuft weiter mit, erscheint als `BEK` und im Bericht — aber der Lauf bleibt grün.
|
|
Die beiden Alternativen wären schlechter: den Testfall löschen heißt, den Befund zu
|
|
vergessen; mit einem dauerhaft roten Lauf leben heißt, dass niemand mehr hinsieht, wenn
|
|
etwas Neues rot wird.
|
|
|
|
**Ein Eintrag ohne Item-Nummer ist eine Ausrede, kein Befund.** Besteht der Fall wieder,
|
|
meldet der Lauf das ausdrücklich (`NEU!`) — dann gehört der Eintrag heraus und das Item auf
|
|
`resolved`.
|
|
|
|
---
|
|
|
|
## 5. Die Zugangssperre
|
|
|
|
Die Testinstanz läuft mit `debug = true`, mit Beispieldaten und mit einem Datenbestand, den
|
|
jeder Testlauf löschen darf. Nichts davon gehört ins offene Netz.
|
|
|
|
* **Basic-Auth vor allem**, was ein Mensch im Browser aufruft — WebUI, `/docs/`,
|
|
`install_db.php`, `testctl.php`.
|
|
* **Ausnahme für `/api/`**, wenn der Aufruf bereits ein Token der Anwendung mitbringt
|
|
(`X-Agent-Token`, `X-Master-Token`, `X-License-Key`, `X-Watchdog-Key` oder
|
|
`Authorization: Bearer …`).
|
|
|
|
Ohne diese Ausnahme müsste jedes SDK zusätzlich zu seinem Token die Basic-Auth-Daten kennen.
|
|
Die Testinstanz prüfte dann einen Aufrufweg, den es auf `dc.mhdf.de` nicht gibt. Die
|
|
Ausnahme öffnet nichts, was in der Produktion nicht ohnehin offen wäre — geprüft wird das
|
|
Token weiterhin von `ApiAuth`/`TokenManager`. Ein Token-Header allein schaltet weder WebUI
|
|
noch Dokumentation frei; die Gruppe `sperre` prüft genau das bei jedem Lauf.
|
|
|
|
`config/.htpasswd` liegt im Webroot, ist aber durch die zweite `.htaccess` in `config/`
|
|
gesperrt (`Require all denied`) — die Datei muss dort liegen, weil `AuthUserFile` einen
|
|
absoluten Pfad braucht.
|
|
|
|
---
|
|
|
|
## 6. `testctl.php`
|
|
|
|
Steuert die Instanz. Erreichbar nur mit Basic-Auth **und** Shared Key, und nur auf einer
|
|
Instanz mit `testing.enabled = true` und `app.env = 'test'`.
|
|
|
|
```bash
|
|
curl -s -u deploy:PASSWORT -H "X-Master-Token: SHARED_KEY" \
|
|
"https://dc-dev.mhdf.de/testctl.php?action=status"
|
|
```
|
|
|
|
| Aktion | Wirkung |
|
|
|---|---|
|
|
| `status` | Tabellen, Zeilenzahlen, angewendete Migrationen, Debug-Zustand |
|
|
| `reset` | löschen, migrieren, Administrator und Master-Token anlegen (braucht `confirm`) |
|
|
| `wipe` | nur löschen (braucht `confirm` = Name der Datenbank) |
|
|
| `migrate` | Migrationen nachziehen |
|
|
| `genkey` | Signierschlüssel für den Updateservice erzeugen |
|
|
| `log` | die letzten Zeilen aus `var/log/` |
|
|
| `clearlog` | Logdateien leeren |
|
|
|
|
`reset` und `wipe` verlangen `confirm` mit dem Namen der Datenbank. Damit lässt sich der
|
|
Aufruf nicht versehentlich gegen die falsche Instanz absetzen.
|
|
|
|
---
|
|
|
|
## 7. Wenn etwas fehlschlägt
|
|
|
|
1. `python tests/e2e/run.py --nur "<Name>" --log` — der einzelne Fall mit Serverlog.
|
|
2. `--kein-reset` dazu, wenn der Datenbestand des fehlgeschlagenen Laufs erhalten bleiben
|
|
soll.
|
|
3. Die Instanz steht im Browser offen: `https://dc-dev.mhdf.de` (Basic-Auth, dann
|
|
Anmeldung mit dem Administrator aus `Serverdaten.txt`).
|
|
|
|
Ein Fehlschlag ist **nicht** automatisch ein Fehler der Anwendung — er kann auch heißen,
|
|
dass der Testfall einen Vertrag beschreibt, den es so nicht mehr gibt. Beides ist ein
|
|
Befund; die Frage ist nur, welche Seite geändert wird.
|
|
|
|
---
|
|
|
|
## 8. Bekannte Eigenheiten der Umgebung
|
|
|
|
**IPv6.** Alle Hoster-Adressen haben einen AAAA-Eintrag. Ist von einem Arbeitsplatz aus
|
|
keine IPv6-Route vorhanden, läuft in Python **jeder** Aufruf zuerst 21 Sekunden in einen
|
|
TCP-Timeout, bevor er auf IPv4 zurückfällt (`curl` tut das sofort, Python nicht). Der
|
|
Testlauf prüft IPv6 deshalb einmal beim Start und schaltet bei Bedarf für den ganzen Lauf
|
|
auf IPv4 um; die gewählte Familie steht in der Kopfzeile der Ausgabe. Betrifft nur den
|
|
Testlauf — für andere Python-Werkzeuge gegen `dc.mhdf.de` gilt dasselbe.
|
|
|
|
**PHP 8.2.33, FPM**, `memory_limit` 512 MB, `max_execution_time` 120 s. Die Produktion kann
|
|
davon abweichen; wo eine Laufzeitgrenze zum Befund gehört, muss sie mitgeprüft werden.
|
|
|
|
**Datenbank `watchdog`** auf `lwrs.your-database.de` — vorher die Datenbank des alten
|
|
Watchdogs, jetzt ausschließlich Testbestand. Sie wird bei jedem Lauf mit `reset` geleert.
|