From f0f3718b37281a7b011bd30938214900494bcb1b Mon Sep 17 00:00:00 2001 From: Schalli Date: Thu, 8 Oct 2026 22:51:00 +0200 Subject: [PATCH] fix(nextcloud-files): IN-05 Browser-Anmeldung prueft den Stand, bevor sie "abgelaufen" meldet - antwortet die Abfrage mit 404/410, laedt die Seite zuerst den Stand neu; ging die Antwort "verbunden" unterwegs verloren, erscheinen direkt die Dateien statt eines Fehlers - Seitentest fuer den Fall Co-Authored-By: Claude Opus 5.5 (1M context) --- .../nextcloud-files/components/ConnectPanel.tsx | 16 ++++++++++++++-- .../nextcloud-files-page.test.tsx | 15 +++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/apps/web/src/app/(portal)/modules/nextcloud-files/components/ConnectPanel.tsx b/apps/web/src/app/(portal)/modules/nextcloud-files/components/ConnectPanel.tsx index 72f0c1d..fdb1a2c 100644 --- a/apps/web/src/app/(portal)/modules/nextcloud-files/components/ConnectPanel.tsx +++ b/apps/web/src/app/(portal)/modules/nextcloud-files/components/ConnectPanel.tsx @@ -47,7 +47,8 @@ export function ConnectPanel({ /** Name, Farbe und Logo der Nextcloud; solange sie fehlen, steht nur der Rechnername da. */ info?: NextcloudServerInfo | null; expired: boolean; - onConnected: () => void; + /** Verbunden (oder Stand pruefen): die Seite laedt den Stand neu. */ + onConnected: () => void | Promise; }) { const t = useTranslations('nextcloudFiles.connect'); @@ -182,7 +183,18 @@ export function ConnectPanel({ if (err instanceof NextcloudFilesRequestError) { if (err.status === 404 || err.status === 410) { // Abgelaufen oder unbekannt (z. B. nach einem Neustart der API): von vorn beginnen. - stop({ code: 'flowExpired', message: err.message, retryAfterSeconds: null }); + // Vorher den Stand neu laden (IN-05): ging die Antwort "verbunden" unterwegs verloren, + // ist das Konto in Wahrheit verbunden und die Seite zeigt gleich die Dateien. + stopped = true; + const expired: ErrorState = { + code: 'flowExpired', + message: err.message, + retryAfterSeconds: null, + }; + void Promise.resolve() + .then(() => onConnectedRef.current()) + .catch(() => undefined) + .finally(() => stop(expired)); } else if (err.code === 'nextcloudLocked' || (err.status < 500 && err.status !== 429)) { stop(toErrorRef.current(err)); } diff --git a/apps/web/src/app/(portal)/modules/nextcloud-files/nextcloud-files-page.test.tsx b/apps/web/src/app/(portal)/modules/nextcloud-files/nextcloud-files-page.test.tsx index e3c276f..772a98d 100644 --- a/apps/web/src/app/(portal)/modules/nextcloud-files/nextcloud-files-page.test.tsx +++ b/apps/web/src/app/(portal)/modules/nextcloud-files/nextcloud-files-page.test.tsx @@ -497,6 +497,21 @@ describe('Browser-Anmeldung', () => { expect(screen.queryByRole('link', { name: 'Anmeldung bei Nextcloud öffnen' })).toBeNull(); }); + it('410/404 nach verlorener "verbunden"-Antwort: erst den Stand pruefen, dann die Dateien statt "abgelaufen" (IN-05)', async () => { + await startFlow(); + const before = mockGetStatus.mock.calls.length; + mockPollFlow.mockRejectedValue(new NextcloudFilesRequestError(404, 'notFound', 'x')); + mockGetStatus.mockResolvedValue(status({ account: account({ displayName: 'Zwei Faktor' }) })); + await act(async () => { + await vi.advanceTimersByTimeAsync(2000); + }); + expect(await screen.findByText('Angemeldet als Zwei Faktor')).toBeTruthy(); + expect(mockGetStatus.mock.calls.length).toBeGreaterThan(before); + expect( + screen.queryByText('Die Anmeldung ist abgelaufen. Bitte starten Sie sie neu.'), + ).toBeNull(); + }); + it('"failed" beendet die Abfrage und zeigt den Text', async () => { await startFlow(); mockPollFlow.mockResolvedValue({