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>
475 lines
18 KiB
Python
475 lines
18 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Bugtracker, Roadmap und Fehlerstrom.
|
|
|
|
Diese Gruppe geht den Weg, den ein Agent tatsaechlich nimmt: melden, Arbeit
|
|
holen, uebernehmen, kommentieren, abschliessen. Einzelne Endpunkte fuer sich
|
|
zu pruefen wuerde die Uebergaenge auslassen - und dort sitzen die Fehler, die
|
|
weh tun (doppelte Items, zwei Agenten am selben Item, verlorene Reservierung).
|
|
"""
|
|
|
|
import time
|
|
|
|
from harness import (test, erwarte, erwarte_status, erwarte_erfolg,
|
|
erwarte_fehler, ueberspringen)
|
|
|
|
PROJEKT = 'myapp' # aus sql/schema.sql - ein Projekt, das nach dem Zuruecksetzen existiert
|
|
BUILD = '9.9.9-test'
|
|
|
|
|
|
def _item_id(resp):
|
|
"""
|
|
Die Antwort nennt die ID als item_id. Ueber diesen einen Ort geholt,
|
|
damit eine Umbenennung im Vertrag genau einen Testfall betrifft und
|
|
nicht zehn.
|
|
"""
|
|
for name in ('item_id', 'id'):
|
|
wert = resp.feld(name)
|
|
if isinstance(wert, int) and wert > 0:
|
|
return wert
|
|
return resp.feld('item', 'id')
|
|
|
|
|
|
def _liste(resp, *kandidaten):
|
|
"""Die Liste aus einer Antwort holen, ohne sich auf einen Feldnamen zu verlassen."""
|
|
for name in kandidaten:
|
|
wert = resp.feld(name)
|
|
if isinstance(wert, list):
|
|
return wert
|
|
for wert in (resp.json or {}).values():
|
|
if isinstance(wert, list):
|
|
return wert
|
|
return []
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Melden
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@test('bugtracker', 'Meldung legt ein Item an')
|
|
def _(ctx):
|
|
ref = 'e2e-%d' % int(time.time())
|
|
r = ctx.client.post('/api/bugtracker/v1/report', body={
|
|
'project_slug': PROJEKT,
|
|
'title': 'Automatischer Testlauf: Beispielbefund',
|
|
'description': 'Angelegt von tests/e2e. Darf jederzeit geloescht werden.',
|
|
'severity': 'high',
|
|
'environment': 'testing',
|
|
'build_version': BUILD,
|
|
'error_message': 'RuntimeException: Beispiel',
|
|
'file_path': 'tests/e2e/suite_bugtracker.py',
|
|
'line_no': 42,
|
|
'client_ref': ref,
|
|
})
|
|
erwarte_erfolg(r)
|
|
item_id = _item_id(r)
|
|
erwarte(isinstance(item_id, int) and item_id > 0,
|
|
"Keine Item-ID in der Antwort: %s" % r.kurz(300))
|
|
ctx['bug_id'] = item_id
|
|
ctx['bug_ref'] = ref
|
|
|
|
|
|
@test('bugtracker', 'Wiederholte Meldung erzeugt kein Duplikat')
|
|
def _(ctx):
|
|
"""
|
|
Gleiches client_ref, gleiches Item. Ein Agent, dem die Verbindung
|
|
wegbricht, sendet erneut - und soll damit nicht den Tracker fluten.
|
|
"""
|
|
r = ctx.client.post('/api/bugtracker/v1/report', body={
|
|
'project_slug': PROJEKT,
|
|
'title': 'Automatischer Testlauf: Beispielbefund',
|
|
'description': 'Zweite Sendung derselben Meldung.',
|
|
'severity': 'high',
|
|
'environment': 'testing',
|
|
'build_version': BUILD,
|
|
'client_ref': ctx['bug_ref'],
|
|
})
|
|
erwarte_erfolg(r)
|
|
erneut = _item_id(r)
|
|
erwarte(erneut == ctx['bug_id'],
|
|
"Zweite Sendung erzeugte Item #%s statt #%s" % (erneut, ctx['bug_id']))
|
|
erwarte(r.feld('is_new') is not True,
|
|
"Die zweite Sendung gilt als neues Item: %s" % r.kurz(300))
|
|
|
|
|
|
@test('bugtracker', 'Idempotency-Key im Header wirkt wie client_ref')
|
|
def _(ctx):
|
|
ref = 'e2e-header-%d' % int(time.time())
|
|
body = {
|
|
'project_slug': PROJEKT,
|
|
'title': 'Automatischer Testlauf: Kopfzeilen-Idempotenz',
|
|
'description': 'Erste Sendung.',
|
|
'environment': 'testing',
|
|
'build_version': BUILD,
|
|
}
|
|
r1 = ctx.client.post('/api/bugtracker/v1/report', body=body,
|
|
headers={'Idempotency-Key': ref})
|
|
erwarte_erfolg(r1)
|
|
r2 = ctx.client.post('/api/bugtracker/v1/report', body=body,
|
|
headers={'Idempotency-Key': ref})
|
|
erwarte_erfolg(r2)
|
|
erwarte(_item_id(r1) == _item_id(r2),
|
|
"Header-Idempotenz greift nicht: #%s dann #%s" % (_item_id(r1), _item_id(r2)))
|
|
erwarte(r2.feld('idempotent_hit') is True,
|
|
"Die zweite Sendung wurde nicht als Wiederholung erkannt: %s" % r2.kurz(300))
|
|
|
|
|
|
@test('bugtracker', 'Unbekannter Schweregrad faellt auf den Standard zurueck')
|
|
def _(ctx):
|
|
"""
|
|
Ein Client mit einem Tippfehler soll seine Meldung loswerden, nicht eine
|
|
Fehlermeldung. Die Anwendung normalisiert - das muss so bleiben, sonst
|
|
verlieren Agenten Befunde an einer Kleinigkeit.
|
|
"""
|
|
r = ctx.client.post('/api/bugtracker/v1/report', body={
|
|
'project_slug': PROJEKT,
|
|
'title': 'Automatischer Testlauf: unbekannter Schweregrad',
|
|
'severity': 'ganz-dringend-echt-jetzt',
|
|
'environment': 'testing',
|
|
'build_version': BUILD,
|
|
'client_ref': 'e2e-severity-%d' % int(time.time()),
|
|
})
|
|
erwarte_erfolg(r)
|
|
|
|
|
|
@test('bugtracker', 'Absender kommt aus dem Token, nicht aus dem Body')
|
|
def _(ctx):
|
|
"""created_by darf nicht frei waehlbar sein - sonst ist die Herkunft wertlos."""
|
|
ref = 'e2e-absender-%d' % int(time.time())
|
|
r = ctx.client.post('/api/bugtracker/v1/report', body={
|
|
'project_slug': PROJEKT,
|
|
'title': 'Automatischer Testlauf: Absender',
|
|
'environment': 'testing',
|
|
'build_version': BUILD,
|
|
'created_by': 'jemand-ganz-anderes',
|
|
'client_ref': ref,
|
|
})
|
|
erwarte_erfolg(r)
|
|
|
|
detail = ctx.client.get('/api/bugtracker/v1/manage',
|
|
query={'action': 'get', 'id': _item_id(r)})
|
|
erwarte_erfolg(detail)
|
|
item = detail.feld('item') or detail.json
|
|
erstellt_von = str(item.get('created_by', '')) if isinstance(item, dict) else ''
|
|
erwarte(erstellt_von != 'jemand-ganz-anderes',
|
|
"created_by wurde aus dem Body uebernommen: %r" % erstellt_von)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Lesen
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@test('bugtracker', 'Projektliste enthaelt die angelegten Projekte')
|
|
def _(ctx):
|
|
"""
|
|
Die Liste kommt aus dc_projects, nicht aus den Meldungen. Genau das ist
|
|
der Unterschied, den ein Client kennen muss: In ein unbekanntes Projekt
|
|
zu melden ist erlaubt, aber es taucht deshalb nicht in der Liste auf.
|
|
"""
|
|
r = ctx.client.get('/api/bugtracker/v1/projects')
|
|
erwarte_erfolg(r)
|
|
slugs = [p.get('slug') for p in _liste(r, 'projects') if isinstance(p, dict)]
|
|
erwarte(PROJEKT in slugs, "Projekt %r fehlt in der Liste: %s" % (PROJEKT, slugs))
|
|
erwarte('deploymentcenter' in slugs,
|
|
"Das eigene Projekt fehlt in der Liste: %s" % slugs)
|
|
|
|
|
|
@test('bugtracker', 'Liste findet das angelegte Item')
|
|
def _(ctx):
|
|
r = ctx.client.get('/api/bugtracker/v1/manage',
|
|
query={'action': 'list', 'project_slug': PROJEKT, 'limit': 50})
|
|
erwarte_erfolg(r)
|
|
items = _liste(r, 'items', 'bugs', 'data')
|
|
ids = [i.get('id') for i in items if isinstance(i, dict)]
|
|
erwarte(ctx['bug_id'] in ids, "Item #%s nicht in der Liste (%s)" % (ctx['bug_id'], ids[:10]))
|
|
|
|
|
|
@test('bugtracker', 'Einzelabruf liefert das Item')
|
|
def _(ctx):
|
|
r = ctx.client.get('/api/bugtracker/v1/manage',
|
|
query={'action': 'get', 'id': ctx['bug_id']})
|
|
erwarte_erfolg(r)
|
|
erwarte(str(ctx['bug_id']) in (r.body or ''), "Item #%s nicht enthalten" % ctx['bug_id'])
|
|
|
|
|
|
@test('bugtracker', 'Einzelabruf eines fehlenden Items antwortet 404')
|
|
def _(ctx):
|
|
r = ctx.client.get('/api/bugtracker/v1/manage',
|
|
query={'action': 'get', 'id': 999999})
|
|
erwarte_fehler(r, http=404, code='not_found')
|
|
|
|
|
|
@test('bugtracker', 'Kennzahlen abrufbar')
|
|
def _(ctx):
|
|
r = ctx.client.get('/api/bugtracker/v1/manage',
|
|
query={'action': 'stats', 'project_slug': PROJEKT})
|
|
erwarte_erfolg(r)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Arbeiten
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@test('bugtracker', 'Item laesst sich uebernehmen')
|
|
def _(ctx):
|
|
r = ctx.client.post('/api/bugtracker/v1/manage',
|
|
query={'action': 'claim', 'id': ctx['bug_id']}, body={})
|
|
erwarte_erfolg(r)
|
|
|
|
|
|
@test('bugtracker', 'Ein uebernommenes Item ist fuer andere gesperrt')
|
|
def _(ctx):
|
|
"""
|
|
Der Kern der Reservierung: Ein zweiter Agent darf dasselbe Item nicht
|
|
bekommen. Ohne diese Sperre arbeiten zwei Agenten am selben Fehler und
|
|
ueberschreiben sich gegenseitig.
|
|
"""
|
|
r = ctx.client.post('/api/bugtracker/v1/manage',
|
|
query={'action': 'claim', 'id': ctx['bug_id']},
|
|
token=ctx.get('zweites_token') or ctx['master_token'], body={})
|
|
if r.ok:
|
|
# Dasselbe Token darf sein eigenes Item erneut uebernehmen; das ist
|
|
# kein Fehler. Geprueft wird der fremde Zugriff - dafuer braucht es
|
|
# ein zweites Token, das der Testlauf sich hier holt.
|
|
prov = ctx.client.post('/api/tokens/v1/provision', body={
|
|
'client_name': 'e2e-zweiter-agent',
|
|
'scopes': ['bugtracker:manage'],
|
|
})
|
|
erwarte_erfolg(prov)
|
|
ctx['zweites_token'] = prov.feld('sub_token')
|
|
r = ctx.client.post('/api/bugtracker/v1/manage',
|
|
query={'action': 'claim', 'id': ctx['bug_id']},
|
|
token=ctx['zweites_token'], body={})
|
|
erwarte_fehler(r, http=409, code='already_claimed')
|
|
|
|
|
|
@test('bugtracker', 'Kommentar wird angenommen')
|
|
def _(ctx):
|
|
r = ctx.client.post('/api/bugtracker/v1/manage',
|
|
query={'action': 'comment', 'id': ctx['bug_id']},
|
|
body={'comment': 'Automatischer Testlauf: Zwischenstand.',
|
|
'action_taken': 'investigated'})
|
|
erwarte_erfolg(r)
|
|
|
|
|
|
@test('bugtracker', 'Leerer Kommentar wird abgewiesen')
|
|
def _(ctx):
|
|
r = ctx.client.post('/api/bugtracker/v1/manage',
|
|
query={'action': 'comment', 'id': ctx['bug_id']},
|
|
body={'comment': ' '})
|
|
erwarte_fehler(r, http=400, code='missing_comment')
|
|
|
|
|
|
@test('bugtracker', 'Status laesst sich setzen')
|
|
def _(ctx):
|
|
r = ctx.client.post('/api/bugtracker/v1/manage',
|
|
query={'action': 'status', 'id': ctx['bug_id']},
|
|
body={'status': 'in_progress', 'notes': 'Automatischer Testlauf.'})
|
|
erwarte_erfolg(r)
|
|
|
|
|
|
@test('bugtracker', 'Status ohne Angabe wird abgewiesen')
|
|
def _(ctx):
|
|
r = ctx.client.post('/api/bugtracker/v1/manage',
|
|
query={'action': 'status', 'id': ctx['bug_id']}, body={})
|
|
erwarte_fehler(r, http=400, code='missing_status')
|
|
|
|
|
|
@test('bugtracker', 'Naechste Arbeit wird ausgegeben und reserviert')
|
|
def _(ctx):
|
|
r = ctx.client.post('/api/bugtracker/v1/manage',
|
|
query={'action': 'next'},
|
|
body={'project_slug': PROJEKT, 'limit': 1})
|
|
erwarte_erfolg(r)
|
|
items = _liste(r, 'items', 'item')
|
|
ctx['next_id'] = items[0].get('id') if items and isinstance(items[0], dict) else None
|
|
|
|
|
|
@test('bugtracker', 'Abschluss verlangt die Build-Angabe')
|
|
def _(ctx):
|
|
"""Ohne "behoben in" ist ein Abschluss nicht nachpruefbar."""
|
|
r = ctx.client.post('/api/bugtracker/v1/manage',
|
|
query={'action': 'resolve', 'id': ctx['bug_id']}, body={})
|
|
erwarte_fehler(r, http=400, code='missing_build')
|
|
|
|
|
|
@test('bugtracker', 'Item laesst sich abschliessen')
|
|
def _(ctx):
|
|
r = ctx.client.post('/api/bugtracker/v1/manage',
|
|
query={'action': 'resolve', 'id': ctx['bug_id']},
|
|
body={'resolved_in_build': BUILD,
|
|
'resolution_notes': 'Automatischer Testlauf.'})
|
|
erwarte_erfolg(r)
|
|
|
|
|
|
@test('bugtracker', 'Reservierung laesst sich zuruecknehmen')
|
|
def _(ctx):
|
|
neu = ctx.client.post('/api/bugtracker/v1/report', body={
|
|
'project_slug': PROJEKT,
|
|
'title': 'Automatischer Testlauf: Reservierung zuruecknehmen',
|
|
'environment': 'testing',
|
|
'build_version': BUILD,
|
|
'client_ref': 'e2e-release-%d' % int(time.time()),
|
|
})
|
|
erwarte_erfolg(neu)
|
|
item = _item_id(neu)
|
|
|
|
erwarte_erfolg(ctx.client.post('/api/bugtracker/v1/manage',
|
|
query={'action': 'claim', 'id': item}, body={}))
|
|
r = ctx.client.post('/api/bugtracker/v1/manage',
|
|
query={'action': 'release', 'id': item},
|
|
body={'note': 'Automatischer Testlauf.'})
|
|
erwarte_erfolg(r)
|
|
ctx['freies_item'] = item
|
|
|
|
|
|
@test('bugtracker', 'Unbekannte Aktion wird abgewiesen')
|
|
def _(ctx):
|
|
r = ctx.client.get('/api/bugtracker/v1/manage', query={'action': 'alles-loeschen'})
|
|
erwarte_fehler(r, http=(400, 404))
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Roadmap
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@test('roadmap', 'Epic laesst sich anlegen')
|
|
def _(ctx):
|
|
r = ctx.client.post('/api/bugtracker/v1/roadmap.php',
|
|
query={'action': 'epic'},
|
|
body={'project_slug': PROJEKT, 'epic': 'e2e-epic',
|
|
'title': 'Testlauf-Epic',
|
|
'summary': 'Angelegt vom automatischen Testlauf.',
|
|
'sort_order': 10})
|
|
erwarte_erfolg(r)
|
|
|
|
|
|
@test('roadmap', 'Item laesst sich einplanen')
|
|
def _(ctx):
|
|
r = ctx.client.post('/api/bugtracker/v1/roadmap.php',
|
|
query={'action': 'set', 'id': ctx['freies_item']},
|
|
body={'project_slug': PROJEKT, 'epic': 'e2e-epic',
|
|
'roadmap_rank': 5, 'target_build': BUILD})
|
|
erwarte_erfolg(r)
|
|
|
|
|
|
@test('roadmap', 'Einplanen ohne Feld wird abgewiesen')
|
|
def _(ctx):
|
|
r = ctx.client.post('/api/bugtracker/v1/roadmap.php',
|
|
query={'action': 'set', 'id': ctx['freies_item']},
|
|
body={'project_slug': PROJEKT})
|
|
erwarte_fehler(r, http=400, code='nothing_to_do')
|
|
|
|
|
|
@test('roadmap', 'Einplanen eines fehlenden Items antwortet 404')
|
|
def _(ctx):
|
|
r = ctx.client.post('/api/bugtracker/v1/roadmap.php',
|
|
query={'action': 'set', 'id': 999999},
|
|
body={'project_slug': PROJEKT, 'epic': 'e2e-epic'})
|
|
erwarte_fehler(r, http=404, code='not_found')
|
|
|
|
|
|
@test('roadmap', 'Zurueckstellen ohne Ausloeser wird abgewiesen')
|
|
def _(ctx):
|
|
"""
|
|
Die einzige echte Regel des Moduls.
|
|
|
|
Ohne benannten Anlass ist ein Vorhaben nicht zurueckgestellt, sondern
|
|
vergessen - und ein vergessener Punkt sieht in jeder Liste aus wie ein
|
|
geplanter. Die Regel sitzt im Repository und muss deshalb auf allen
|
|
Schreibwegen gleich greifen; geprueft wird hier der ueber roadmap.php.
|
|
"""
|
|
r = ctx.client.post('/api/bugtracker/v1/roadmap.php',
|
|
query={'action': 'set', 'id': ctx['freies_item']},
|
|
body={'project_slug': PROJEKT, 'status': 'deferred'})
|
|
erwarte_fehler(r, http=400, code='invalid_update')
|
|
|
|
|
|
@test('roadmap', 'Zurueckstellen mit Ausloeser wird angenommen')
|
|
def _(ctx):
|
|
"""
|
|
Die Gegenprobe. Ohne sie liesse sich der Fall darueber auch dadurch
|
|
bestehen, dass 'deferred' gar nicht mehr geht.
|
|
"""
|
|
r = ctx.client.post('/api/bugtracker/v1/roadmap.php',
|
|
query={'action': 'set', 'id': ctx['freies_item']},
|
|
body={'project_slug': PROJEKT, 'status': 'deferred',
|
|
'defer_trigger': 'sobald der Testlauf danach fragt'})
|
|
erwarte_erfolg(r)
|
|
|
|
|
|
@test('roadmap', 'Zurueckgestelltes taucht nicht in der Arbeitsschlange auf')
|
|
def _(ctx):
|
|
"""
|
|
Der Zweck des Status. Stuende ein zurueckgestelltes Vorhaben weiterhin in
|
|
?action=next, waere es lediglich anders beschriftet.
|
|
"""
|
|
r = ctx.client.post('/api/bugtracker/v1/manage',
|
|
query={'action': 'next'}, body={'limit': 5})
|
|
erwarte_erfolg(r)
|
|
ids = [i.get('id') for i in (r.feld('items', default=[]) or [])]
|
|
|
|
# ?action=next uebernimmt, was es ausgibt. Wieder freigeben, sonst haelt
|
|
# dieser Testfall fuenf Items bis zum Lease-Ende - und der naechste Lauf
|
|
# oder ein Mensch an der Instanz wundert sich, wo die Arbeit hin ist.
|
|
for i in ids:
|
|
ctx.client.post('/api/bugtracker/v1/manage',
|
|
query={'action': 'release', 'id': i},
|
|
body={'note': 'Automatischer Testlauf.'})
|
|
|
|
erwarte(ctx['freies_item'] not in ids,
|
|
'Item #%s ist zurueckgestellt, wurde aber als Arbeit ausgegeben'
|
|
% ctx['freies_item'])
|
|
|
|
|
|
@test('roadmap', 'Roadmap laesst sich lesen')
|
|
def _(ctx):
|
|
for aktion in ('list', 'epics', 'next', 'active'):
|
|
r = ctx.client.get('/api/bugtracker/v1/roadmap.php',
|
|
query={'action': aktion, 'project_slug': PROJEKT})
|
|
erwarte_erfolg(r)
|
|
|
|
|
|
@test('roadmap', 'Roadmap laesst sich rendern')
|
|
def _(ctx):
|
|
r = ctx.client.get('/api/bugtracker/v1/roadmap.php',
|
|
query={'action': 'render', 'project_slug': PROJEKT})
|
|
erwarte_erfolg(r)
|
|
|
|
|
|
@test('roadmap', 'Roadmap-Schreibzugriff verlangt Verwaltungsrecht')
|
|
def _(ctx):
|
|
r = ctx.client.post('/api/bugtracker/v1/roadmap.php',
|
|
query={'action': 'set', 'id': ctx['freies_item']},
|
|
token=ctx['sub_token'],
|
|
body={'project_slug': PROJEKT, 'epic': 'e2e-epic'})
|
|
erwarte_fehler(r, http=(401, 403))
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Fehlerstrom
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@test('fehlerstrom', 'Ausnahme laesst sich melden')
|
|
def _(ctx):
|
|
r = ctx.client.post('/api/errors/v1/report', body={
|
|
'project_slug': PROJEKT,
|
|
'exception': 'System.NullReferenceException',
|
|
'message': 'Automatischer Testlauf: Beispielausnahme',
|
|
'stack_trace': 'at Test.Method() in Test.cs:line 1',
|
|
'level': 'error',
|
|
'build': BUILD,
|
|
'environment': 'testing',
|
|
'file': 'Test.cs',
|
|
'line': 1,
|
|
'client_ref': 'e2e-error-%d' % int(time.time()),
|
|
})
|
|
erwarte_erfolg(r)
|
|
|
|
|
|
@test('fehlerstrom', 'Meldung ohne Token abgewiesen')
|
|
def _(ctx):
|
|
r = ctx.client.post('/api/errors/v1/report', token=None,
|
|
body={'project_slug': PROJEKT, 'message': 'ohne Token'})
|
|
erwarte_fehler(r, http=401)
|