diff --git a/.planning/quick/260907-let-verbindungstest-fuer-das-postfach-im-aus/260907-let-PLAN.md b/.planning/quick/260907-let-verbindungstest-fuer-das-postfach-im-aus/260907-let-PLAN.md new file mode 100644 index 0000000..7e68b4c --- /dev/null +++ b/.planning/quick/260907-let-verbindungstest-fuer-das-postfach-im-aus/260907-let-PLAN.md @@ -0,0 +1,352 @@ +--- +phase: quick-260907-let +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - apps/api/src/tenders/tender-email-config.service.ts + - apps/api/src/tenders/tenders.controller.ts + - apps/api/src/tenders/tender-email-config.service.spec.ts + - apps/api/src/tenders/tenders.controller.spec.ts + - apps/web/src/lib/tender-radar-api.ts + - apps/web/src/app/(portal)/modules/tender-radar/settings/components/EmailAlertConfigForm.tsx + - apps/web/src/app/(portal)/modules/tender-radar/settings/components/EmailAlertConfigForm.test.tsx + - apps/web/src/app/(portal)/modules/tender-radar/my-sources/my-sources.test.tsx + - apps/web/src/messages/de.json + - apps/web/src/messages/en.json +autonomous: true +requirements: [WINDOWS-16] + +estimate: + tokens: 55000 + raw_tokens: 55000 + tasks: 2 + confidence: low + +must_haves: + truths: + - "Ein angemeldeter Nutzer kann auf der Seite Meine Quellen die Verbindung zu seinem Postfach pruefen, ohne vorher zu speichern, und sieht entweder eine Erfolgsmeldung oder den Klartext-Fehler des Mailservers." + - "Ein bereits gespeichertes Postfach laesst sich erneut pruefen, ohne dass das Passwort neu eingetippt werden muss — der Server greift dann auf die verschluesselt hinterlegten Zugangsdaten des Nutzers zurueck." + - "Der Test laeuft ausschliesslich gegen das Postfach des angemeldeten Nutzers; eine im Rumpf mitgeschickte fremde Kennung kann daran nichts aendern." + - "Weder die Antwort des Servers noch seine Protokollzeilen enthalten Benutzername oder Passwort." + - "Die neuen Beschriftungen liegen in beiden Sprachdateien vor (Deutsch und Englisch)." + artifacts: + - "apps/api/src/tenders/tender-email-config.service.ts — Methode testConnection" + - "apps/api/src/tenders/tenders.controller.ts — Route POST email-config/test, deklariert vor @Get(':id')" + - "apps/web/src/lib/tender-radar-api.ts — Funktion testEmailConnection" + - "apps/web/src/app/(portal)/modules/tender-radar/settings/components/EmailAlertConfigForm.tsx — Knopf plus Rueckmeldung" + - "apps/web/src/messages/de.json und apps/web/src/messages/en.json — Schluessel unter tenderRadar.emailAlerts" + - "apps/api/src/tenders/tenders.controller.spec.ts — Deklarationsreihenfolge-Waechter um testEmailConnection erweitert" + key_links: + - "Controller-Handler -> extractTriageContext(req).userId -> Service-Aufruf (userId kommt nie aus dem Rumpf)" + - "Service -> ImapProvider.testConnection bzw. ExchangeInboxProvider.testConnection, ausgewaehlt nach dto.protocol" + - "Leeres Passwort im Rumpf -> entschluesselte encryptedInboxCreds derselben userId-Zeile" + - "EmailAlertConfigForm -> testEmailConnection -> POST /modules/tender-radar/email-config/test" + - "vi.mock-Fabriken fuer @/lib/tender-radar-api in EmailAlertConfigForm.test.tsx UND my-sources.test.tsx muessen testEmailConnection auffuehren — sonst wirft Vitest beim Import der Komponente" +--- + + +Der Ausschreibungs-Radar bekommt fuer das Postfach unter "Meine Quellen" einen +Verbindungstest — genau den, den das DKV-Modul seit Phase 7 hat. Schliesst +WINDOWS-Eintrag #16. + +Purpose: Heute faellt ein Tippfehler in der EWS-Endpunkt-URL oder ein falsches +Passwort erst dadurch auf, dass dauerhaft nichts ankommt — und dann ist nicht +unterscheidbar, ob die Verbindung scheitert oder schlicht keine Alarm-Mail da +war. Das blockiert unmittelbar den offenen Live-Test WINDOWS #12, dessen ganzer +Zweck der Beleg des handgeschriebenen NTLM/SOAP-Wegs ist. + +Output: Ein per-Nutzer-Endpunkt POST /modules/tender-radar/email-config/test, +ein Knopf "Verbindung testen" im Formular, Beschriftungen in beiden +Sprachdateien, und ein erweiterter Deklarationsreihenfolge-Waechter. + +Die Faehigkeit selbst wird NICHT neu gebaut: beide Inbox-Provider bringen +testConnection() bereits mit (apps/api/src/inbox/imap.provider.ts:343, +apps/api/src/inbox/exchange-inbox.provider.ts:294, deklariert in +apps/api/src/inbox/inbox-provider.interface.ts:31). Dieser Plan verdrahtet sie +nur — das Vorbild ist DkvController.testConnection (dkv.controller.ts:106) und +DkvService.testConnection (dkv.service.ts:201). + + + +@~/.claude/gsd-core/workflows/execute-plan.md +@~/.claude/gsd-core/templates/summary.md + + + +@.planning/STATE.md +@CLAUDE.md + +Vorbild Backend (gemessen, nicht aus Notizen): +@apps/api/src/dkv/dkv.service.ts +@apps/api/src/tenders/tender-email-config.service.ts +@apps/api/src/tenders/tenders.controller.ts + +Vorbild Frontend: +@apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/InboxConfigForm.tsx +@apps/web/src/app/(portal)/modules/tender-radar/settings/components/EmailAlertConfigForm.tsx +@apps/web/src/lib/tender-radar-api.ts + + + + + + Task 1: Endpunkt POST email-config/test — vom Formularrumpf bis zum Mailserver + apps/api/src/tenders/tender-email-config.service.ts, apps/api/src/tenders/tenders.controller.ts, apps/api/src/tenders/tender-email-config.service.spec.ts, apps/api/src/tenders/tenders.controller.spec.ts + + apps/api/src/dkv/dkv.service.ts (Methode testConnection, Zeilen 195-235) — die Vorlage inklusive Rueckfall auf die gespeicherten Zugangsdaten bei leerem Passwort. + apps/api/src/tenders/tender-email-config.service.ts (Methode saveConfig) — dort steht die bereits erprobte Entschluessel-und-Ergaenzen-Logik dieses Dienstes. + apps/api/src/tenders/tenders.controller.ts Zeilen 328-365 (die beiden email-config-Handler) und Zeile 582 (@Get(':id')). + apps/api/src/tenders/tenders.controller.spec.ts Zeilen 91-93 (makeFakeRequest), 132-140 (makeFakeEmailConfigService), 411-423 (der bestehende email-config-Reihenfolgetest). + + + - TenderEmailConfigService.testConnection(userId, dto) mit gefuelltem Passwort im Rumpf: reicht genau dieses Passwort an den Provider durch, liest die Datenbank gar nicht erst. + - Mit leerem Passwort im Rumpf und vorhandener gespeicherter Zeile: entschluesselt encryptedInboxCreds derselben userId und uebergibt das gespeicherte Passwort an den Provider. + - Mit leerem Benutzernamen im Rumpf: ergaenzt analog den gespeicherten Benutzernamen (gleiche Semantik wie saveConfig in derselben Datei). + - dto.protocol 'exchange' waehlt den Exchange-Provider, alles andere den IMAP-Provider. + - Die Rueckgabe ist unveraendert das, was der Provider liefert: { success: true } oder { success: false, message }. Kein Feld mehr, kein Feld weniger. + - TendersController.testEmailConnection nimmt userId aus extractTriageContext(req) und reicht es als erstes Argument weiter; ein im Rumpf mitgeschicktes Fremdfeld erreicht den Dienst nicht. + - Der bestehende Reihenfolgetest fuer email-config schlaegt fehl, sobald testEmailConnection nach getTender deklariert wird. + + + Schritt 1 — Dienst. In apps/api/src/tenders/tender-email-config.service.ts eine + Methode testConnection(userId: string, dto: TenderEmailConfigDto) ergaenzen, die + { success: boolean; message?: string } liefert. Sie baut ein InboxConfig-Objekt + (Typ aus '../inbox/inbox-provider.interface') aus den DTO-Feldern protocol, host, + port, username, password, encryption, folder, senderFilter, domain — mit denselben + Ersatzwerten wie DkvService.testConnection (host '' , port 993, folder 'INBOX'). + Fehlen username oder password im DTO, wird die Zeile der uebergebenen userId gelesen + und encryptedInboxCreds entschluesselt; nur der jeweils fehlende Wert wird daraus + ergaenzt. Diese Rueckfall-Logik ist strukturell dieselbe, die saveConfig in derselben + Datei schon fuehrt — dort abschauen, nicht neu erfinden. Schlaegt das Entschluesseln + fehl, wird der Fehler still verschluckt und ohne Zugangsdaten weiterprobiert; eine + etwaige Protokollzeile nennt nur die userId, niemals einen Wert aus dem + Zugangsdatensatz (T-05-13, T-QT16-03). Anschliessend + protocol === 'exchange' ? exchangeProvider : imapProvider aufrufen und dessen + Ergebnis unveraendert zurueckgeben. + + Die beiden Provider werden per Konstruktor injiziert. Sie MUESSEN als optionale + Parameter ANGEHAENGT werden (ImapProvider, ExchangeInboxProvider aus + '../inbox/imap.provider' bzw. '../inbox/exchange-inbox.provider') — genau nach dem + in diesem Repository dokumentierten Muster von TendersController.tenderIngestionService + (tenders.controller.ts Zeilen 95-103): so bleiben die acht bestehenden + new TenderEmailConfigService(prisma, crypto)-Aufrufe in der Spec ohne Aenderung + typkorrekt, waehrend Nest im Betrieb immer beide aufloest (InboxModule ist in + tenders.module.ts bereits importiert, ImapProvider und ExchangeInboxProvider werden + dort exportiert — nichts an der Modulverdrahtung ist anzufassen). Ist der benoetigte + Provider wider Erwarten nicht vorhanden, liefert testConnection + { success: false, message: 'Inbox-Anbieter nicht verfuegbar' } statt zu werfen, damit + die Antwortform der Route in jedem Fall stabil bleibt. + + Schritt 2 — Route. In apps/api/src/tenders/tenders.controller.ts unmittelbar nach + saveEmailConfig (also klar VOR @Get(':id') in Zeile 582) einen Handler + testEmailConnection mit @Post('email-config/test') und @UseModule('tender-radar') + ergaenzen. Er liest const { userId } = this.extractTriageContext(req) und gibt + this.tenderEmailConfig.testConnection(userId, dto) zurueck. Der Konstruktor der + Klasse bleibt unangetastet — tenderEmailConfig ist bereits injiziert; das ist der + Grund, warum die Logik in den Dienst und nicht in den Controller gehoert (42 + Konstruktoraufrufe in der Spec wuerden sonst brechen). + + Den Handler mit einem Kommentarblock im Stil der Nachbarhandler versehen. Er soll + zwei Dinge festhalten: erstens, dass userId ausschliesslich aus dem + Authentifizierungskontext stammt und der DTO gar kein Kennungsfeld traegt (T-17-01 / + T-14-03-05, IDOR); zweitens, warum der Handler trotzdem oberhalb von @Get(':id') + steht, obwohl Nest Routen je HTTP-Verb aufloest und ein GET-Platzhalter eine + POST-Route heute nicht verdecken kann — der ganze email-config-Block bleibt beisammen, + und der Waechter greift auch dann noch, wenn spaeter einmal ein Platzhalter fuer POST + hinzukaeme. Diese Einordnung bitte woertlich so treffen, nicht als schlichtes + "sonst 404". + + Schritt 3 — Tests. In apps/api/src/tenders/tender-email-config.service.spec.ts drei + Faelle ergaenzen, die den bestehenden Fabriken (makeFakePrisma, makeFakeCrypto) + folgen und zusaetzlich zwei winzige Provider-Attrappen mit einer testConnection-Zusage + uebergeben: (a) getipptes Passwort geht unveraendert an den Provider, + (b) leeres Passwort holt das gespeicherte, (c) protocol 'exchange' waehlt die + Exchange-Attrappe. In apps/api/src/tenders/tenders.controller.spec.ts erstens + makeFakeEmailConfigService um eine testConnection-Attrappe erweitern, zweitens einen + Fall ergaenzen, der mit makeFakeRequest('u1', ...) belegt, dass der Dienst mit 'u1' + aufgerufen wird, auch wenn der Rumpf ein abweichendes Kennungsfeld mitfuehrt, und + drittens den vorhandenen Reihenfolgetest in Zeile 411 um den Index von + testEmailConnection erweitern (kleiner als der Index von getTender). + + + cd apps/api && npx vitest run src/tenders/tender-email-config.service.spec.ts src/tenders/tenders.controller.spec.ts + cd apps/api && npx tsc --noEmit -p tsconfig.json + grep -n "email-config/test" apps/api/src/tenders/tenders.controller.ts + + + Beide Vitest-Dateien laufen gruen (Ausgangsstand: 8 + 52 Faelle, jetzt mehr), der + API-Typecheck bleibt fehlerfrei, und die Route POST email-config/test steht in + tenders.controller.ts an einer Zeilennummer unterhalb von saveEmailConfig und + oberhalb von @Get(':id'). + + + + + Task 2: Knopf "Verbindung testen" im Postfach-Formular samt Beschriftungen + apps/web/src/lib/tender-radar-api.ts, apps/web/src/app/(portal)/modules/tender-radar/settings/components/EmailAlertConfigForm.tsx, apps/web/src/messages/de.json, apps/web/src/messages/en.json, apps/web/src/app/(portal)/modules/tender-radar/settings/components/EmailAlertConfigForm.test.tsx, apps/web/src/app/(portal)/modules/tender-radar/my-sources/my-sources.test.tsx + + apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/InboxConfigForm.tsx Zeilen 140-205 (Zustandsfelder, update, handleTestConnection) und 520-580 (Knopfreihe und Rueckmeldung) — das nachzubildende Verhalten. + apps/web/src/lib/dkv-api.ts Zeilen 125-141 (testConnection) und apps/web/src/lib/tender-radar-api.ts Zeilen 518-571 (fetchEmailConfig/saveEmailConfig samt extractErrorMessage) — die Hausform des jeweiligen Moduls. + apps/web/src/app/(portal)/modules/tender-radar/my-sources/my-sources.test.tsx Zeilen 14-22 — die vi.mock-Fabrik, die zwingend mitwachsen muss. + + + - Klick auf den Knopf ruft testEmailConnection genau einmal mit demselben Rumpf auf, den auch das Speichern erzeugt (formToPayload), also ohne Passwortfeld, solange keins getippt wurde. + - Waehrend der Anfrage tragen Test- und Speicherknopf den disabled-Zustand, und der Testknopf zeigt die Wartebeschriftung. + - Eine Antwort mit success false zeigt die Fehlermeldung des Servers sichtbar an. + - Eine Aenderung an irgendeinem Formularfeld raeumt die vorherige Rueckmeldung wieder weg. + - Die bestehenden fuenf Faelle in EmailAlertConfigForm.test.tsx bleiben unveraendert gruen. + + + Schritt 1 — Sprachdateien zuerst, damit die Komponente auf vorhandene Schluessel + zugreift. Unter tenderRadar.emailAlerts in apps/web/src/messages/de.json und + apps/web/src/messages/en.json jeweils vier Schluessel ergaenzen: testConnection, + testTesting, testSuccess und testFailed. Wortlaut und Platzhalter woertlich von + dkvFleet.form uebernehmen, wo dieselben vier Schluessel bereits stehen — deutsch + "Verbindung testen" / "Verbindung wird getestet..." / "Verbindung erfolgreich" / + "Verbindung fehlgeschlagen: {error}", englisch entsprechend. Deutsch ist die + Hauptsprache dieser Installation, aber beide Dateien muessen den Satz tragen. + Die 401-Hinweisliste des DKV-Formulars NICHT mitnehmen: sie gehoert dort zu einem + eigenen Hilfetext-Block, den dieses Formular nicht fuehrt. + + Schritt 2 — Api-Klient. In apps/web/src/lib/tender-radar-api.ts eine exportierte + Funktion testEmailConnection ergaenzen, die denselben Nutzlast-Typ wie + saveEmailConfig entgegennimmt und Promise<{ success: boolean; message?: string }> + liefert. Sie sendet POST an ${API_URL}/modules/tender-radar/email-config/test mit + 'Content-Type': 'application/json' und credentials: 'include'. Bei nicht-ok-Antwort + wird extractErrorMessage genutzt — so machen es die beiden Nachbarfunktionen in + derselben Datei; die knappere Fehlerbehandlung aus dkv-api.ts hier bewusst nicht + uebernehmen. + + Schritt 3 — Formular. In EmailAlertConfigForm.tsx testEmailConnection importieren, + die Zustandsfelder isTesting und testResult ergaenzen (Form wie in InboxConfigForm), + in update() zusaetzlich setTestResult(null) aufrufen und einen Handler + handleTestConnection schreiben, der formToPayload(form) sendet und Erfolg wie + Fehlschlag in testResult ablegt; ein geworfener Fehler landet als + { success: false, message } dort. In der Knopfreihe am Ende (heute nur "Speichern") + den Testknopf VOR dem Speicherknopf einsetzen, mit der Rahmen-Optik des DKV-Vorbilds, + disabled bei isTesting || isSaving; der Speicherknopf bekommt zusaetzlich isTesting + in seine disabled-Bedingung. Unter der Knopfreihe die Rueckmeldung rendern, gruen bei + Erfolg und in der Warnfarbe bei Fehlschlag, mit denselben Farbwerten, die das + Formular bereits fuer saveSuccess und saveError verwendet. + + Der Beschreibungsblock ueber der Komponente (heute Zeilen 119-135) behauptet + ausdruecklich, dieses Formular fuehre keinen solchen Knopf und es gebe serverseitig + nichts, wogegen er pruefen koennte. Diese Passage ist ab jetzt falsch und muss + umgeschrieben werden: der Knopf ist da, er spricht die neue Route an, und die + Abrufhaeufigkeit bleibt weiterhin plattformweit geregelt (D-15) — nur Letzteres bleibt + als Begruendung stehen, warum es hier kein Intervallfeld gibt. + + Schritt 4 — Attrappen nachziehen. Beide Testdateien, die + '@/lib/tender-radar-api' per vi.mock mit einer Fabrik ersetzen, muessen + testEmailConnection mitfuehren: EmailAlertConfigForm.test.tsx (Zeilen 8-11) und + my-sources.test.tsx (Zeilen 14-22). Fehlt der Eintrag, wirft Vitest bereits beim + Import der Komponente, weil der Named Export auf der Attrappe nicht existiert — das + ist der wahrscheinlichste Weg, diesen Task rot zu machen. In + EmailAlertConfigForm.test.tsx zusaetzlich die vier neuen Uebersetzungen in die + hand gepflegte Schluesseltabelle des next-intl-Mocks aufnehmen und zwei Faelle + ergaenzen: (a) Klick auf den Testknopf ruft testEmailConnection einmal mit einem + Rumpf ohne Passwortfeld auf, (b) eine Antwort { success: false, message: '401 ...' } + laesst die Fehlermeldung im Dokument erscheinen. + + + cd apps/web && npx vitest run "src/app/(portal)/modules/tender-radar/settings/components/EmailAlertConfigForm.test.tsx" "src/app/(portal)/modules/tender-radar/my-sources/my-sources.test.tsx" + cd apps/web && npx tsc --noEmit + node -e "const de=require('./apps/web/src/messages/de.json'),en=require('./apps/web/src/messages/en.json');const k=['testConnection','testTesting','testSuccess','testFailed'];for(const key of k){if(!de.tenderRadar.emailAlerts[key])throw new Error('de fehlt '+key);if(!en.tenderRadar.emailAlerts[key])throw new Error('en fehlt '+key);}console.log('locale keys ok');" + + Abnahme im Browser — der Test beweist sich nur an einem echten Mailserver, nie + an Attrappen. Der Neubau und Neustart der Container liegt beim Nutzer; dieser + Plan fasst weder Docker noch den Testserver an. + + 1. Portal oeffnen, Ausschreibungs-Radar, Seite "Meine Quellen", Abschnitt + "Mein Postfach". Unterhalb der Felder steht jetzt neben "Speichern" ein + zweiter Knopf "Verbindung testen". + 2. Absichtlich falsch pruefen: einen unsinnigen Servernamen eintragen (zum + Beispiel gibtesnicht.example) und den Testknopf druecken. Erwartung: kurz + "Verbindung wird getestet...", danach in Rot "Verbindung fehlgeschlagen:" + gefolgt vom Klartext des Mailservers. + 3. Richtig pruefen: echte Postfachdaten samt Passwort eintragen und erneut + druecken. Erwartung: gruene Zeile "Verbindung erfolgreich". + 4. Ohne Passwort nachpruefen: speichern, Seite neu laden (das Passwortfeld ist + danach absichtlich leer) und nur den Testknopf druecken. Erwartung: wieder + "Verbindung erfolgreich" — der Server hat auf die gespeicherten Zugangsdaten + zurueckgegriffen. Das ist der Punkt, der den Test im Alltag brauchbar macht. + 5. Kurzer Blick in die API-Protokollzeilen des Laufs: weder Benutzername noch + Passwort duerfen dort auftauchen. + + Punkt 2 und 3 muessen sich unterscheiden — eine gruene Meldung bei absichtlich + falschem Server waere ein Fehlalarm und schlimmer als gar kein Knopf. Faellt + einer der Punkte durch, bleibt WINDOWS #16 offen; die gruenen Unit-Tests taugen + dann nicht als Gegenargument, sie pruefen ausschliesslich gegen Attrappen. + + + + Beide Web-Testdateien laufen gruen (Ausgangsstand EmailAlertConfigForm: 5 Faelle, + jetzt 7), der Web-Typecheck bleibt fehlerfrei, und der Sprachdatei-Check meldet + "locale keys ok" fuer beide Sprachen. Die Browser-Abnahme oben wandert in die + Abnahmeliste am Phasenende (human_verify_mode = end-of-phase). + + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| Browser -> API (POST /modules/tender-radar/email-config/test) | Nutzereingaben aus dem Formular, inklusive frei waehlbarem Servernamen und Zugangsdaten | +| API -> Mailserver (IMAP/EWS) | Ausgehende Verbindung mit entschluesselten Zugangsdaten | +| API -> Datenbank (encryptedInboxCreds) | Ruhende, verschluesselte Zugangsdaten eines einzelnen Nutzers | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan | +|-----------|----------|-----------|----------|-------------|-----------------| +| T-QT16-01 | Elevation of Privilege | TendersController.testEmailConnection | high | mitigate | userId stammt ausschliesslich aus extractTriageContext(req); TenderEmailConfigDto traegt gar kein Kennungsfeld, ein mitgeschicktes Fremdfeld erreicht den Dienst nicht (T-17-01 / T-14-03-05). Belegt durch den Controller-Testfall aus Task 1, Schritt 3 | +| T-QT16-02 | Information Disclosure | Antwort und Protokoll des Tests | high | mitigate | Rueckgabe ist unveraendert das { success, message? } des Providers — kein Durchreichen des Konfigurationsobjekts. Der Dienst protokolliert im Fehlerfall hoechstens die userId; die Provider-Protokollzeilen sind seit T-07-03 bereits zugangsdatenfrei | +| T-QT16-03 | Information Disclosure | entschluesselte gespeicherte Zugangsdaten | medium | mitigate | Entschluesselte Werte existieren nur im Methoden-Scope von testConnection, wandern ausschliesslich in das InboxConfig-Objekt und werden weder zurueckgegeben noch protokolliert (T-05-13) | +| T-QT16-04 | Spoofing | frei waehlbarer host / EWS-Endpunkt im Testrumpf | medium | accept | Identische Oberflaeche und identisches Risiko wie das bereits bestehende PUT /email-config, das denselben Wert entgegennimmt und dauerhaft speichert. Der Test fuegt keine neue Angriffsflaeche hinzu; das Modul-Gate @UseModule('tender-radar') bleibt unveraendert davor | +| T-QT16-05 | Denial of Service | wiederholtes Ausloesen des Tests | low | accept | Der Test bindet eine ausgehende Verbindung pro Klick, genau wie POST /dkv/test-connection seit Phase 7. Kein neues Muster, keine eigene Drosselung — dieselbe Bewertung wie beim Vorbild | + +Keine neuen Pakete: dieser Plan installiert nichts ueber npm, das +Legitimitaets-Gate fuer Paketinstallationen ist daher nicht anwendbar. + + + +Nach Abschluss beider Tasks, aus dem Repository-Wurzelverzeichnis: + +``` +cd apps/api && npx vitest run && npx tsc --noEmit -p tsconfig.json +cd apps/web && npx vitest run && npx tsc --noEmit +``` + +Gemessener Ausgangsstand vor Beginn dieses Plans: beide Typechecks fehlerfrei +(Exit 0), apps/api/src/tenders/tender-email-config.service.spec.ts 8 Faelle +gruen, apps/api/src/tenders/tenders.controller.spec.ts 52 Faelle gruen, +EmailAlertConfigForm.test.tsx 5 Faelle gruen. Die Zahlen duerfen nur steigen. + +Der Reihenfolge-Waechter ist die dokumentierte Ausnahme von der +Reparatur-auf-Zuruf-Regel dieses Repositories (Entscheidung vom 2026-08-11): +er muss laut werden, wenn jemand die Handler spaeter umsortiert. + + + +- POST /modules/tender-radar/email-config/test existiert, ist mit + @UseModule('tender-radar') abgesichert und liefert { success, message? }. +- Die Kennung des Postfachbesitzers stammt nachweislich aus dem + Authentifizierungskontext, nicht aus dem Rumpf — durch einen Testfall belegt. +- Ein leer gelassenes Passwortfeld greift auf die gespeicherten, + verschluesselten Zugangsdaten desselben Nutzers zurueck. +- Der Knopf "Verbindung testen" steht im Postfach-Formular unter "Meine + Quellen", zeigt einen Wartezustand und eine sichtbare Rueckmeldung. +- Beide Sprachdateien tragen die vier neuen Schluessel. +- Der Deklarationsreihenfolge-Waechter deckt testEmailConnection mit ab. +- Alle vier Prueflaeufe (zwei Testsuiten, zwei Typechecks) bleiben gruen. +- Die Browser-Abnahme aus dem human-check-Block von Task 2 ist bestaetigt (sie laeuft am Phasenende, nicht mitten im Lauf). + + + +Create `.planning/quick/260907-let-verbindungstest-fuer-das-postfach-im-aus/260907-let-SUMMARY.md` when done +