Files
proof-of-work/proof-of-work/tests/retryAction.test.ts
T
Keysat 0178f8f5cc v1.2.0:2 — retry login/signup server action once on transport failure
iOS Safari reuses a keep-alive socket the server closed while the login
form sat idle during typing, so the first Sign In / Create account POST
dies instantly with NSURLErrorNetworkConnectionLost ("The network
connection was lost"). That rejects the server-action call, hitting the
client-side catch in LoginForm/SignupForm and showing "An unexpected
error occurred"; the second tap lands on a fresh connection and works.

Add lib/retryAction.ts: retryOnTransportError() retries the action once
only when the call throws. A returned { error } (bad password, rate
limit) is a real result and passes straight through. A lost-on-a-stale-
socket POST never reached the server, so retrying it once is safe.
2026-06-15 16:44:33 -05:00

37 lines
1.6 KiB
TypeScript

import { describe, it, expect, vi } from 'vitest';
import { retryOnTransportError } from '@/lib/retryAction';
describe('retryOnTransportError', () => {
it('returns the result without retrying when the call succeeds', async () => {
const fn = vi.fn().mockResolvedValue({ success: true });
const result = await retryOnTransportError(fn);
expect(result).toEqual({ success: true });
expect(fn).toHaveBeenCalledTimes(1);
});
it('passes through an application-level { error } without retrying', async () => {
// A returned error (bad password, rate limit) is a real result, not a
// transport failure — it must not trigger a retry.
const fn = vi.fn().mockResolvedValue({ error: 'Invalid email or password' });
const result = await retryOnTransportError(fn);
expect(result).toEqual({ error: 'Invalid email or password' });
expect(fn).toHaveBeenCalledTimes(1);
});
it('retries once on a thrown transport error and returns the second result', async () => {
const fn = vi
.fn()
.mockRejectedValueOnce(new TypeError('The network connection was lost'))
.mockResolvedValueOnce({ success: true });
const result = await retryOnTransportError(fn);
expect(result).toEqual({ success: true });
expect(fn).toHaveBeenCalledTimes(2);
});
it('rejects after a single retry when both attempts throw', async () => {
const fn = vi.fn().mockRejectedValue(new TypeError('Failed to fetch'));
await expect(retryOnTransportError(fn)).rejects.toThrow('Failed to fetch');
expect(fn).toHaveBeenCalledTimes(2);
});
});