feat(FN-1899): add login timeout, cancellation, and 409 conflict handling
- Add LoginOutcome type ('pending' | 'success' | 'failed' | 'timeout') with state tracking
- Implement AbortController-based cancellation for OAuth login flows
- Add timeout detection after MAX_POLL_CYCLES (150 polls, ~5 min)
- Handle 409 Conflict when concurrent login is attempted
- Persist login outcomes to onboarding stepData for session recovery
- Add getStepData mock export to App.test.tsx vi.mock
- Fix ToastType: use 'info' instead of invalid 'warning' type
This commit is contained in:
@@ -248,7 +248,7 @@ export function ModelOnboardingModal({
|
|||||||
}
|
}
|
||||||
setAuthActionInProgress(null);
|
setAuthActionInProgress(null);
|
||||||
setLoginOutcomes((prev) => ({ ...prev, [providerId]: "timeout" }));
|
setLoginOutcomes((prev) => ({ ...prev, [providerId]: "timeout" }));
|
||||||
addToast("Login timed out. Please try again.", "warning");
|
addToast("Login timed out. Please try again.", "info");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,7 +276,7 @@ export function ModelOnboardingModal({
|
|||||||
(err && typeof err === "object" && "status" in err && (err as { status: number }).status === 409);
|
(err && typeof err === "object" && "status" in err && (err as { status: number }).status === 409);
|
||||||
|
|
||||||
if (isConcurrentLogin) {
|
if (isConcurrentLogin) {
|
||||||
addToast("Login already in progress. Please wait or cancel the current attempt.", "warning");
|
addToast("Login already in progress. Please wait or cancel the current attempt.", "info");
|
||||||
setLoginOutcomes((prev) => ({ ...prev, [providerId]: "failed" }));
|
setLoginOutcomes((prev) => ({ ...prev, [providerId]: "failed" }));
|
||||||
} else {
|
} else {
|
||||||
addToast(err instanceof Error ? err.message : "Login failed", "error");
|
addToast(err instanceof Error ? err.message : "Login failed", "error");
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ const mockClearOnboardingState = vi.fn();
|
|||||||
const mockIsOnboardingCompleted = vi.fn();
|
const mockIsOnboardingCompleted = vi.fn();
|
||||||
const mockMarkOnboardingCompleted = vi.fn();
|
const mockMarkOnboardingCompleted = vi.fn();
|
||||||
const mockGetOnboardingCompletedAt = vi.fn();
|
const mockGetOnboardingCompletedAt = vi.fn();
|
||||||
|
const mockGetStepData = vi.fn();
|
||||||
|
|
||||||
vi.mock("../../components/model-onboarding-state", () => ({
|
vi.mock("../../components/model-onboarding-state", () => ({
|
||||||
isOnboardingResumable: (...args: unknown[]) => mockIsOnboardingResumable(...args),
|
isOnboardingResumable: (...args: unknown[]) => mockIsOnboardingResumable(...args),
|
||||||
@@ -118,6 +119,7 @@ vi.mock("../../components/model-onboarding-state", () => ({
|
|||||||
isOnboardingCompleted: (...args: unknown[]) => mockIsOnboardingCompleted(...args),
|
isOnboardingCompleted: (...args: unknown[]) => mockIsOnboardingCompleted(...args),
|
||||||
markOnboardingCompleted: (...args: unknown[]) => mockMarkOnboardingCompleted(...args),
|
markOnboardingCompleted: (...args: unknown[]) => mockMarkOnboardingCompleted(...args),
|
||||||
getOnboardingCompletedAt: (...args: unknown[]) => mockGetOnboardingCompletedAt(...args),
|
getOnboardingCompletedAt: (...args: unknown[]) => mockGetOnboardingCompletedAt(...args),
|
||||||
|
getStepData: (...args: unknown[]) => mockGetStepData(...args),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Mock CustomModelDropdown for onboarding modal tests
|
// Mock CustomModelDropdown for onboarding modal tests
|
||||||
|
|||||||
@@ -1321,7 +1321,7 @@ describe("ModelOnboardingModal", () => {
|
|||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(addToast).toHaveBeenCalledWith(
|
expect(addToast).toHaveBeenCalledWith(
|
||||||
"Login already in progress. Please wait or cancel the current attempt.",
|
"Login already in progress. Please wait or cancel the current attempt.",
|
||||||
"warning"
|
"info"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1517,7 +1517,7 @@ describe("ModelOnboardingModal", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Should show timeout toast
|
// Should show timeout toast
|
||||||
expect(addToast).toHaveBeenCalledWith("Login timed out. Please try again.", "warning");
|
expect(addToast).toHaveBeenCalledWith("Login timed out. Please try again.", "info");
|
||||||
|
|
||||||
// Cancel button should not be shown after timeout
|
// Cancel button should not be shown after timeout
|
||||||
expect(screen.queryByText("Cancel")).toBeNull();
|
expect(screen.queryByText("Cancel")).toBeNull();
|
||||||
|
|||||||
Reference in New Issue
Block a user