test(auth): KTO-01 Playwright-E2E (api, web, throttle) und CI-Job e2e #42
No reviewers
Labels
No labels
epic/katalog
epic/konto
epic/partien
epic/querschnitt
epic/sammlung
needs-clarification
prio/could
prio/must
prio/should
size/l
size/m
size/s
size/xl
status/in-progress
status/ready-for-merge
status/spec-review
type/bug
type/epic
type/story
type/tech
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: cschlaefke/Shuffle#42
Loading…
Reference in a new issue
No description provided.
Delete branch "feature/38-auth-e2e"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Zusammenfassung
E2E-Prüfung der Akzeptanzkriterien von KTO-01 (#1) gegen echtes GoTrue und die gebaute Flutter-Web-App (T3) plus CI-Job
e2eaufdocker-hostmit Extra-GoTrue-Container für die Drosselung (D2). Kein App-Code geändert.Closes #38
Spec:
docs/specs/1-auth-registrierung-anmeldung.md(§ 7.1, T3, D2)Umfang
e2e/: Playwright-Projekteapi(52 Tests),web(21),throttle(4),spike(3, nur lokal, protokolliert SMTP-Ausfall-Verhalten); Hilfen für GoTrue, Mailpit, Datenbank (nur lokale Hosts) undauth-config.tsmit AUTH-CFG-Bezug..forgejo/workflows/ci.yml: Jobe2e(nachsupabase-tests),.forgejo/scripts/e2e-run.sh,e2e/support/tcp-forward.mjs(Relay vom dind-Gateway auf 127.0.0.1),e2e/support/start-throttle-gotrue.sh.docs/auth-konfiguration.md§ 7,supabase/README.md.Abdeckung
Krit. 1–11 sind mit API- und/oder Web-Tests abgedeckt, inkl. Regression AUTH-CFG-24 (Krit. 4), Body-Format der Trigger-Fehler (Krit. 10), Einmaligkeit/Ablauf Reset-Link (Krit. 8), Abmeldung anderer Sitzungen (Krit. 9), echte Drosselung Login #31 -> 429 mit zweiter IP unbeeinflusst (Krit. 7).
Lokal: alle drei Projekte grün, 3 Gesamtläufe in Folge.
Befunde
/recover,/resend,/signupfür bekannte und unbekannte Adressen unterschiedliche Status (500 vs. 200). Ergebnis in Issue #37, Entscheidung steht aus.pressFirstButtonals Workaround).expires_atin die Vergangenheit setzen wirkt nicht (Ablauf kommt aus dem JWT-exp); der Test verschiebt die Browser-Uhr um 2 h.Definition of Done
e2eauf dem echten Runner grün (erster Lauf ausstehend; nur dort prüfbar:supabase start/db resetgegen dind, Port-Veröffentlichung,supabase status -o env-Schlüsselnamen, Artefakt-Upload, IPv4/IPv6 beilocalhost:3000)Risiken
head.repo.full_name == github.repository).supabase-testshat dieselbe Lücke (Folge-Issue).e2eundsupabase-teststeilen Container-Namen/Ports im dind; parallele Läufe verschiedener PRs können kollidieren.So testest du es lokal
🤖 Generated with Claude Code
Review (code-reviewer) – PR #42 / Issue #38 (T3 + D2)
Grundlage:
git diff main...HEAD(35 Dateien,app/unverändert → Flutter-Checks nicht betroffen), Specdocs/specs/1-auth-registrierung-anmeldung.md§ 7.1, T3/D2.Selbst ausgeführt (lokal):
bash -nfür.forgejo/scripts/e2e-run.shunde2e/support/start-throttle-gotrue.shok ·node --check e2e/support/tcp-forward.mjsok ·npx tsc --noEmit -p e2eok ·npx playwright test --list: 73 Tests (api 52 + web 21), mitTHROTTLE_GOTRUE_URL/SPIKE_GOTRUE_URL80. Die Tests selbst habe ich nicht gegen einen Stack laufen lassen. shellcheck und actionlint sind lokal nicht installiert.Secrets:
app/env/ci.example.jsonist in diesem PR unverändert und enthält nur Platzhalter (public-anon-key-placeholder, öffentliche Legal-URLs,TERMS_VERSION). Die JWTs ine2e/support/auth-config.ts:22-27sind dekodiert die öffentlichen CLI-Demo-Schlüssel (iss: supabase-demo,role: anonbzw.service_role). Echte Secrets sind nicht im Diff.Blocker
e2e/support/db.ts:10-15: Der Host-Guard lässt sich umgehen.assertLocalprüftnew URL(url).hostname,pgverbindet aber überpg-connection-string, und das wertet den Query-Parameter?host=aus. Selbst geprüft:Damit gilt „läuft nie gegen Nicht-Lokal“ nicht mehr, und der Helfer schreibt in
auth.users.Vorschlag: den Host aus demselben Parser nehmen, den
pgnutzt:import { parse } from 'pg-connection-string'; const cfg = parse(env.dbUrl);.cfg.hostgegen die Allowlist prüfen und denClientauscfgbauen. Alternativ URLs mit Query-Parametern (host,hostaddr) ablehnen.Dazu ein Nit:
new URL(...).hostnameliefert für IPv6[::1]mit Klammern, der Eintrag'::1'greift also nie.host.docker.internalist ein DNS-Name; die Begründung als Kommentar ergänzen oder den Eintrag streichen.e2e/tests/api/krit04-enumeration.spec.ts:23,32: Race durch das globalecountUsers(). MitfullyParallelund 2 (CI) bzw. 4 Workern legen andere Tests (auch das Projektthrottle) zwischen den beiden Zählungen Nutzer an. Der Test wird dann sporadisch rot, undretries: 1verdeckt das nur.Vorschlag: die Absicht der Spec („kein neuer Nutzer“) je Adresse prüfen:
select count(*) from auth.users where email = $1ergibt 1, und es gibt keine Zeile mitid = again.body.id.Sollte
.forgejo/workflows/ci.yml:197: Der Fork-Guard schützt nicht gegen bösartige Forks (blockiert diesen PR nicht). Beion: pull_requestkommt die Workflow-Datei aus dem PR-Head. Ein Fork kann dasif:also einfach entfernen. Außerdem führtsupabase-testsFork-Code ohnehin ungeschützt aufdocker-hostaus. Dieser PR vergrößert das Risiko nicht, der Kommentar (ci.yml ~189-192) unddocs/auth-konfiguration.md§ 7 versprechen aber einen Schutz, den es so nicht gibt. Die wirksame Kontrolle liegt in den Repo- bzw. Runner-Einstellungen: Freigabe von Fork-Workflows, oder das Labeldocker-hostnur für vertrauenswürdige Läufe. Vorschlag: Kommentar und Doku entsprechend korrigieren, Folge-Issue fürsupabase-testsund die Runner-Policy anlegen.e2e/tests/throttle/krit07-drosselung.spec.ts:75: Login #31 muss hier ohne Toleranz 429 liefern. Test 1 (Z. 42-46) erlaubt zwei Versuche Spielraum, dieser Test keinen. Bei 0,5 Token/s und 30 bcrypt-Logins über Relay und dind (vier Throttle-Tests laufen parallel) füllt der Bucket schnell 1-2 Token nach. Vorschlag: einen gemeinsamen HelferexhaustUntil429(client, maxExtra)nutzen. Die Toleranz besser aus der gemessenen Dauer ableiten (ceil(elapsed_s * 0.5) + 1) statt fest auf 2 setzen; das gilt auch für Test 1. Ähnlich, aber seltener: Z. 49 (richtiger Login direkt nach der 429 → 429) kann rot werden, wenn der Bucket kurz vor 1 stand.e2e/tests/web/krit05-sitzung.spec.ts:43-46: Die Gegenprobe prüft nichts.goto('/login')und direkt danachtoHaveURL(/\/login$/)ist erfüllt, bevor Flutter überhaupt startet. Vorschlag: erstenableSemantics(page)undexpect(getByRole('button', { name: 'Sign in', exact: true })).toBeVisible()abwarten, dann die URL prüfen, so wie inkrit09-abmeldung.spec.ts:36-40.e2e/support/mailpit.tsexpectMailCount: Das feste 2-s-Fenster kann unter CI-Last falsch grün sein. Das betrifft die Abwesenheitsnachweise in Krit. 4, 6, 8 und 11. Vorschlag: ein Sentinel-Muster. Danach eine bekannte Mail an dieselbe Adresse auslösen (z. B.recoverbei bestätigten Konten), auf sie warten und dann die exakte Gesamtzahl prüfen. Wo das nicht geht (unbekannte Adressen), das Fenster beibehalten und so kommentieren.SUPABASE_URLundMAILPIT_URL.GoTrueClient.createConfirmedUser(e2e/support/gotrue.ts) schickt den Service-Role-Key an/admin/users. Außerdem ist der Demo-Service-Key als Fallback hinterlegt. ZeigtSUPABASE_URLversehentlich auf Staging, werden dort Nutzer angelegt bzw. der Key gesendet. Vorschlag:assertLocal(in korrigierter Form, siehe 1) in einemglobalSetupauf alle URLs ausenvanwenden, außerthrottle/spikemit Allowlist.e2e/tests/web/krit04-06-07-anmeldung.spec.ts:111(QUE-06 Krit. 3, „nur per Tastatur“): Das E-Mail-Feld wird perfillFieldgeklickt. Damit ist nicht belegt, dass der Ablauf nur mit der Tastatur geht. Vorschlag: vom Seitenanfang perTabins erste Feld,keyboard.type, dann weiter wie bisher..forgejo/workflows/ci.yml:227und:239-240: Die Maskierung kommt zu spät.supabase startgibt die Schlüssel sehr wahrscheinlich unmaskiert ins Log aus, bevoradd-maskläuft. Das Risiko ist gering, weil es Demo-Schlüssel sind, aber der Kommentar „nur maskiert weiterreichen“ stimmt so nicht. Vorschlag: Kommentar anpassen oder die Ausgabe vonstartmit> /dev/nullunterdrücken (Fehler dann überstatusprüfen)..forgejo/scripts/e2e-run.sh:16-21: Die Warteschleife schlägt nie fehl. Sie prüft nur, ob der lokale Relay lauscht, nicht ob das Ziel erreichbar ist. Vorschlag: je Dienst einen HTTP-Probe durch den Relay (curl -fsS http://127.0.0.1:54321/auth/v1/health -H "apikey: $SUPABASE_ANON_KEY",:9998/health,:54324/api/v1/info) mit|| exit 1nach dem Timeout..forgejo/workflows/ci.yml:211: Fehltxz, stehtxzin$missing, undapt-get install xzschlägt fehl, weil das Paketxz-utilsheißt. Vorschlag: Werkzeug und Paket getrennt abbilden (xz→xz-utils).concurrency-Gruppe: Parallele Läufe (zwei PRs, PR plus Push auf main) teilen sich dind, Container-Namen und Ports.start-throttle-gotrue.shmachtdocker rm -f shuffle_auth_throttleund kann damit den Container eines anderen Laufs entfernen.supabase stopbeendet ebenfalls fremde Stacks. Vorschlag:concurrency: { group: docker-host-dind }fürsupabase-testsunde2e; ob Forgejo das unterstützt, prüfen. Sonst die Runner-Kapazität auf 1 setzen und das dokumentieren.Nit
e2e/support/web.ts:129-130und die Tests (krit04-06-07-anmeldung.spec.ts:28,70,krit08-reset.spec.ts:92,krit05-sitzung.spec.ts:27-32):baseURL: 'http://localhost:3000'ist hart kodiert,WEB_URLwird ignoriert.freshContextist definiert, wird aber nicht benutzt. Bitte vereinheitlichen.e2e/tests/api/config.spec.ts:48-50: Der Titel nennt AUTH-CFG-10, geprüft wird nursign_in_sign_ups(AUTH-CFG-11).token_refresh = 150fehlt; daraus ergeben sich die 0,5 Token/s, die der Throttle-Test voraussetzt.e2e/tests/web/krit09-abmeldung.spec.ts:53,57-59:Object.values(...)[0]hängt von der Reihenfolge der Storage-Schlüssel ab (z. B. code-verifier). Die Poll-Bedingung.not.toBe(before)ist auch erfüllt, wenn der Wertundefinedist (Sitzung gelöscht). Bitte zusätzlich auf „truthy“ prüfen. Der Kommentar „Sitzung B: ein zweites Gerät in eigenem Kontext“ (Z. 17) beschreibt nicht, was der Test tut: Es gibt nur einen Browser-Kontext, A ist die API.e2e/support/start-throttle-gotrue.sh:27:grep '^supabase_auth_' | head -n1nimmt bei mehreren lokalen Projekten einen beliebigen Container. Vorschlag: Standardsupabase_auth_shuffle(project_id).API_EXTERNAL_URLwird nicht explizit gesetzt, wie es D2 vorsieht, sondern geerbt. Fürthrottleist das unkritisch (keine Mails), sollte aber im Kommentar stehen.e2e/playwright.config.ts:49:reuseExistingServer: trueauch in CI. Üblich ist!process.env.CI, damit nie gegen einen fremden oder alten Server getestet wird.BIND_ADDR=0.0.0.0(ci.yml, Extra-GoTrue): Der Container mit kopierter Env (JWT-Secret, DB-URL, alles Demo-Werte) ist für die Dauer des Jobs auf allen dind-Interfaces erreichbar. Das entspricht der CLI selbst; ein Satz dazu in der Doku genügt.playwright-report,test-results): Traces enthalten den Anon-Key (öffentlich) sowie Tokens und Passwörter der zufälligen Testkonten.add-maskwirkt dort nicht. Das ist unkritisch, sollte aber in der Doku stehen, weil das Repo öffentlich ist.Positiv
identitiesleer, ID ≠ echte ID, keine Mail und altes Passwort; exaktes Trigger-Body-Format (Krit. 10) inkl.x-sb-error-code; Codepoint-Grenzen mit Emoji; Ablauf überrecovery_sent_at/confirmation_sent_atinkl. Grenzfall „60 s vor Ablauf gültig“.shiftBrowserClock) ist eine gute, begründete Lösung, samt Gegenprobe.page.routemit CORS-Header und begründet; der echte Limiter wird separat gegen den Extra-Container getestet; R13 eingehalten (nieX-Forwarded-Forgegen die CLI).config.spec.tsbindetauth-config.ts,config.tomlund die Doku zusammen.RUNNER_TEMPstatt im Repo,if: always()-Cleanup in der richtigen Reihenfolge (Extra-Container vorsupabase stop),trapfür den Relay, Artefakte nur bei Fehlern mit 7 Tagen Aufbewahrung.Nur auf dem echten Runner prüfbar
Payload-Feld
pull_request.head.repo.full_namein Forgejo und welche Workflow-Datei bei Fork-PRs gilt · Schlüsselnamen insupabase status -o env(CLI 3.0.0:ANON_KEY/SERVICE_ROLE_KEY) · obsupabase startdie Schlüssel ins Log schreibt ·apt-getund Root fürplaywright install --with-depssowie Node und Flutter imdocker-host-Image · Port-Veröffentlichung im dind (0.0.0.0:9998, Health überDOCKER_GW) · IPv4/IPv6 beilocalhost:3000(serve) ·forgejo/upload-artifact@v4· Timing der Drosselung auf CI-Hardware · Kollisionen zwischen Läufen und ob Forgejoconcurrencyunterstützt · Cleanup bei Abbruch (Cancel).Fazit: CHANGES REQUESTED. Grund sind Blocker 1 (Host-Guard in
db.ts) und Blocker 2 (Race beim globalen Nutzerzähler in Krit. 4). Der Merge bleibt bei Chris.CI-Ergebnis (Commit
15ccae2)Alle 5 Jobs grün:
flutter(1m15s),web-build(56s),supabase(3s),supabase-tests(31s),e2e(4m34s). Im E2E-Lauf auf dem Runner: 84 Tests bestanden (api, web, throttle), keine Wiederholungen, keine Ausfälle.Erkenntnisse aus den drei CI-Läufen
supabase/templates/*.html) fehlten im Docker-in-Docker-Daemon (Bind-Mount auf einen Pfad des Job-Containers). Die CLI liefert die Vorlagen über Kong aus; GoTrue verschickte dadurch ein Verzeichnislisting statt der Vorlage, der Betreff kam weiter ausconfig.toml. Das ist der stille Fallback R5 der Spec, hier real aufgetreten.supabase startim dind angelegt (.forgejo/scripts/remote-templates.sh seed) und nachdb resetgeprüft (verify). Damit grün.Review
code-reviewer: 2 Blocker (Host-Guard indb.tsumgehbar, Race im Krit.-4-Test) und wichtige Punkte behoben (aa421b1,da8305c). Fork-PR-Risiko desdocker-host-Runners: Folge-Issue #43. Spec Krit. 9 (Rezept mitexpires_at) korrigiert.Offen / Hinweise
concurrency-Gruppe auf Job-Ebene: Wirkung bei Forgejo 8.0.3 / Runner v5.0.4 ungeprüft; parallele PR-Läufe könnten kollidieren.Merge durch Chris. Mit dem Merge ist Issue #38 geschlossen.