d354362a61
Abschluss-Dokumentation zum nachgeruesteten Verbindungstest (WINDOWS #16). Verifikation unabhaengig nachgemessen, nicht aus dem Ausfuehrungsbericht uebernommen: 646/646 API-Tests, 228/228 Web-Tests, beide Typpruefungen sauber, und das Sprachschluessel-Gate ist von rot ("de fehlt testConnection") auf gruen gewechselt. Sicherheitsseitig geprueft: die DTO traegt kein Eigentuemer-Feld, der Handler zieht userId ausschliesslich aus dem Auth-Kontext (eigener IDOR-Test mit Koeder-userId), Rueckfall auf gespeicherte Zugangsdaten sucht nur ueber denselben userId, und in den geaenderten Dateien findet sich keine Log- oder Antwortstelle mit Zugangsdaten. Status bleibt human_needed: die Browser-Abnahme gegen ein echtes Postfach steht aus und braucht einen Neubau durch den User. WINDOWS #16 bleibt deshalb offen. Ausserdem workflow.use_worktrees auf false: origin/HEAD ist in diesem Repo nicht aufloesbar, der Basis-Check des Workflows verlangt daher selbst sequenzielle Ausfuehrung ("fork-ref-unknown"). Ein isolierter Arbeitsbaum wuerde von einem veralteten Stand abzweigen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
166 lines
11 KiB
Markdown
166 lines
11 KiB
Markdown
---
|
|
phase: quick-260907-let
|
|
plan: 01
|
|
subsystem: api
|
|
tags: [nestjs, next-intl, vitest, inbox-provider, imap, exchange-ews]
|
|
|
|
# Dependency graph
|
|
requires:
|
|
- phase: 14-portal-alert-mailbox-inbox-extraction
|
|
provides: ImapProvider/ExchangeInboxProvider.testConnection (apps/api/src/inbox), shared by DKV and now Tender-Radar
|
|
- phase: 17-eigene-ausschreibungs-quellen-je-nutzer
|
|
provides: per-user TenderEmailConfig ownership (userId @unique) and the extractTriageContext auth-context pattern
|
|
provides:
|
|
- "POST /modules/tender-radar/email-config/test — per-user connection test, no persistence"
|
|
- "TenderEmailConfigService.testConnection(userId, dto) with stored-credential fallback for username AND password"
|
|
- "Verbindung testen button in EmailAlertConfigForm with wait/success/error feedback"
|
|
affects: [tender-radar, inbox, windows-ledger]
|
|
|
|
# Actuals (#2632)
|
|
actuals:
|
|
tokens: 6600
|
|
tasks: 2
|
|
commits: 2
|
|
|
|
tech-stack:
|
|
added: []
|
|
patterns:
|
|
- "Optional trailing-constructor-param provider injection (ImapProvider/ExchangeInboxProvider) so existing 2-arg spec call sites stay type-correct — mirrors TendersController.tenderIngestionService"
|
|
|
|
key-files:
|
|
created: []
|
|
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
|
|
|
|
key-decisions:
|
|
- "testConnection backfills BOTH username and password independently from stored, decrypted credentials — broader than saveConfig's credChanged branching, since a fully-blank test-connection form needs both fields, not just the one the caller didn't touch on a partial re-save."
|
|
- "Route-order comment corrected to state the accurate reason: NestJS resolves routes per HTTP verb, so a GET :id placeholder cannot shadow this POST route today. Ordering is defensive consistency with the surrounding email-config handlers, not a live 404 risk."
|
|
|
|
patterns-established:
|
|
- "A per-user connection-test endpoint (userId from extractTriageContext, never the body) is now the second instance of this pattern after DkvController.testConnection — a template for any future per-user inbox-config module."
|
|
|
|
requirements-completed: [WINDOWS-16]
|
|
|
|
coverage:
|
|
- id: D1
|
|
description: "POST /modules/tender-radar/email-config/test resolves userId from the auth context, never the body, and delegates to TenderEmailConfigService.testConnection"
|
|
requirement: WINDOWS-16
|
|
verification:
|
|
- kind: unit
|
|
ref: "apps/api/src/tenders/tenders.controller.spec.ts#POST /email-config/test resolves userId from the auth context, even when the body carries a different identity field (T-QT16-01, IDOR)"
|
|
status: pass
|
|
- kind: unit
|
|
ref: "apps/api/src/tenders/tenders.controller.spec.ts#declares getEmailConfig/saveEmailConfig/testEmailConnection before getTender so GET /:id cannot shadow \"email-config\""
|
|
status: pass
|
|
human_judgment: false
|
|
- id: D2
|
|
description: "TenderEmailConfigService.testConnection falls back to the same user's stored, decrypted credentials when username/password are left blank, and selects IMAP vs Exchange provider by dto.protocol"
|
|
requirement: WINDOWS-16
|
|
verification:
|
|
- kind: unit
|
|
ref: "apps/api/src/tenders/tender-email-config.service.spec.ts#testConnection (Quick 260907-let, WINDOWS #16) — all 3 cases"
|
|
status: pass
|
|
human_judgment: false
|
|
- id: D3
|
|
description: "\"Verbindung testen\" button in EmailAlertConfigForm — wait state, success/error feedback, form-change clears the previous result"
|
|
requirement: WINDOWS-16
|
|
verification:
|
|
- kind: unit
|
|
ref: "apps/web/.../EmailAlertConfigForm.test.tsx#clicking \"Verbindung testen\" calls testEmailConnection once with a body carrying no password field"
|
|
status: pass
|
|
- kind: unit
|
|
ref: "apps/web/.../EmailAlertConfigForm.test.tsx#a failed connection test shows the server's error message in the document"
|
|
status: pass
|
|
human_judgment: false
|
|
- id: D4
|
|
description: "Browser UAT against a real mailbox: false-positive-free failure on a bad server, success on a real one, password-optional retest, credential-free API logs"
|
|
requirement: WINDOWS-16
|
|
verification: []
|
|
human_judgment: true
|
|
rationale: "The plan's own human-check block states this explicitly: the capability proves itself only against a real IMAP/EWS mailbox, never against mocks. Runs at phase end (human_verify_mode = end-of-phase); the Docker rebuild/restart needed to exercise it belongs to the user, not this executor."
|
|
|
|
# Metrics
|
|
duration: 25min
|
|
completed: 2026-09-07
|
|
status: complete
|
|
---
|
|
|
|
# Quick Task 260907-let: Verbindungstest fuer das Postfach unter "Meine Quellen" Summary
|
|
|
|
**POST /modules/tender-radar/email-config/test wires the already-existing ImapProvider/ExchangeInboxProvider.testConnection into a per-user endpoint, plus a "Verbindung testen" button in EmailAlertConfigForm — closes WINDOWS #16.**
|
|
|
|
## Performance
|
|
|
|
- **Duration:** ~25 min
|
|
- **Completed:** 2026-09-07T13:44:17Z
|
|
- **Tasks:** 2
|
|
- **Files modified:** 10
|
|
|
|
## Accomplishments
|
|
- Backend: `TenderEmailConfigService.testConnection(userId, dto)` — types in the DTO's password go straight to the provider; a blank username or password independently falls back to this same user's stored, decrypted credentials; `dto.protocol === 'exchange'` selects the Exchange provider, everything else IMAP.
|
|
- `POST /modules/tender-radar/email-config/test` on `TendersController` — `userId` comes exclusively from `extractTriageContext(req)`, proven by a dedicated IDOR test where the body carries a decoy `userId` field that never reaches the service call.
|
|
- Declaration-order guard extended to cover `testEmailConnection` alongside the existing `getEmailConfig`/`saveEmailConfig` entries, so a future reshuffle above `@Get(':id')` fails the test immediately.
|
|
- Frontend: "Verbindung testen" button in `EmailAlertConfigForm`, positioned before "Speichern", sharing the disabled-during-either-request semantics with the DKV Fleet `InboxConfigForm` it mirrors. Shows a waiting label, then a green success line or a red line with the server's literal error text; any form-field change clears the previous result.
|
|
- Four new i18n keys (`testConnection`, `testTesting`, `testSuccess`, `testFailed`) added under `tenderRadar.emailAlerts` in both `de.json` and `en.json`, wording taken verbatim from `dkvFleet.form`'s existing four keys.
|
|
- The component's doc comment, which previously stated no test-connection endpoint exists for this form, was rewritten to describe the button that now exists — only the D-15 rationale for the *absent interval field* survived unchanged.
|
|
|
|
## Task Commits
|
|
|
|
Each task was committed atomically:
|
|
|
|
1. **Task 1: Endpunkt POST email-config/test — vom Formularrumpf bis zum Mailserver** - `3bf550b` (feat)
|
|
2. **Task 2: Knopf "Verbindung testen" im Postfach-Formular samt Beschriftungen** - `c4db3b2` (feat)
|
|
|
|
_Both tasks were `tdd="true"`; test cases were authored alongside the implementation in the same commit per this plan's task-scoping (no separate RED/GREEN split was required by the plan)._
|
|
|
|
## Files Created/Modified
|
|
- `apps/api/src/tenders/tender-email-config.service.ts` - added `testConnection(userId, dto)`, optional `ImapProvider`/`ExchangeInboxProvider` constructor params, a service-level `Logger`
|
|
- `apps/api/src/tenders/tenders.controller.ts` - added `POST email-config/test` handler (`testEmailConnection`) directly after `saveEmailConfig`, above `@Get(':id')`
|
|
- `apps/api/src/tenders/tender-email-config.service.spec.ts` - 3 new cases (typed password passthrough, stored-credential fallback, protocol→provider selection)
|
|
- `apps/api/src/tenders/tenders.controller.spec.ts` - `makeFakeEmailConfigService` gained `testConnection`; 1 new IDOR test; declaration-order guard extended
|
|
- `apps/web/src/lib/tender-radar-api.ts` - added `testEmailConnection(payload)`
|
|
- `apps/web/src/app/(portal)/modules/tender-radar/settings/components/EmailAlertConfigForm.tsx` - test button, `isTesting`/`testResult` state, `handleTestConnection`, feedback rendering, corrected doc comment
|
|
- `apps/web/src/app/(portal)/modules/tender-radar/settings/components/EmailAlertConfigForm.test.tsx` - mock factory extended with `testEmailConnection`, next-intl mock gained `params` interpolation + 4 new keys, 2 new test cases
|
|
- `apps/web/src/app/(portal)/modules/tender-radar/my-sources/my-sources.test.tsx` - mock factory and translation table extended so component import doesn't throw (no new test cases required by the plan)
|
|
- `apps/web/src/messages/de.json` / `apps/web/src/messages/en.json` - 4 new keys each under `tenderRadar.emailAlerts`
|
|
|
|
## Decisions Made
|
|
- `testConnection` backfills username AND password independently, unlike `saveConfig`'s `credChanged` gate which only ever needs to fill in the one field the caller left out on a partial re-save. A connection test can arrive with a fully blank form (test right after loading a saved config), so both fields need their own fallback.
|
|
- Used NestJS `Logger` (matching every other service in `apps/api/src/tenders/`) instead of `console.error` for the credential-decrypt failure path, consistent with codebase convention rather than the DKV analog's inline `this.logger`.
|
|
- Route-order comment states the accurate mechanism (NestJS resolves per HTTP verb; a GET placeholder cannot shadow a POST route today) instead of the "sonst 404" phrasing the plan explicitly flagged as false — per the plan-check trap warning.
|
|
|
|
## Deviations from Plan
|
|
|
|
None - plan executed exactly as written, including the three traps called out in execution notes (mock-factory export gap, stale doc-comment, route-order comment accuracy).
|
|
|
|
## Issues Encountered
|
|
|
|
None.
|
|
|
|
## User Setup Required
|
|
|
|
None - no external service configuration required. The browser UAT in Task 2's human-check block (real IMAP/EWS mailbox test) requires a Docker rebuild/restart, which is explicitly the user's responsibility per this repository's conventions (Kein Docker-Deploy auf Testserver) — it runs at phase end, not during this execution.
|
|
|
|
## Next Phase Readiness
|
|
|
|
- Backend and frontend automated verification green: API 646/646 tests (up from a 60-test baseline in the two touched spec files, now 64), typecheck clean; Web 228/228 tests (touched files: 5→7 in `EmailAlertConfigForm.test.tsx`), typecheck clean, locale-key check passes for both languages.
|
|
- Plan-level commit ledger: `plan_head_before: 5da58ad1827e86ea26cbe89e120a3a6cbcbe4f7f`, `commits: 2` (measured via `git rev-list --count`).
|
|
- Outstanding: the browser UAT itself (Task 2's `human-check` block) is not yet run — it needs a real mailbox and the user's own Docker rebuild, and per `human_verify_mode = end-of-phase` is expected to happen at phase close, not mid-execution. WINDOWS #16 stays open until that UAT is confirmed.
|
|
|
|
---
|
|
*Phase: quick-260907-let*
|
|
*Completed: 2026-09-07*
|
|
|
|
## Self-Check: PASSED
|
|
|
|
All 10 modified/created files verified present on disk; both task commits (3bf550b, c4db3b2) verified present in `git log`.
|