fix(email): gate draft attempt rotation
Rotate persisted delivery identity only after the matching owner attempt is definitively failed. Keep stale, foreign, pending, sent, and uncertain drafts non-retryable.
This commit is contained in:
@@ -287,6 +287,40 @@ describe('CorrespondenceInboxPage', () => {
|
||||
expect(screen.getByLabelText(/message/i)).toHaveValue('Conflicting local edit.');
|
||||
});
|
||||
|
||||
test('persists a new delivery identity only after a definitive failed attempt', async () => {
|
||||
const original = mockedApi.get.getMockImplementation();
|
||||
const stored = {
|
||||
id: 'draft-1', jobApplicationId: 42, provider: 'gmail', to: 'maria@acme.test', subject: 'Saved subject', bodyText: 'Saved private draft.',
|
||||
threadId: 'thread-1', clientRequestId: 'failed-request-id', revision: 4, createdAtUtc: new Date().toISOString(), updatedAtUtc: new Date().toISOString(),
|
||||
};
|
||||
mockedApi.get.mockImplementation((url: string, config?: any) => {
|
||||
if (url === '/email/drafts') return Promise.resolve({ data: [stored] } as any);
|
||||
if (url === '/email/providers') return Promise.resolve({ data: [
|
||||
{ provider: 'gmail', displayName: 'Gmail', connected: true, address: 'owner@gmail.test', canRead: true, canSend: true },
|
||||
] } as any);
|
||||
return original!(url, config);
|
||||
});
|
||||
mockedApi.post.mockImplementation((url: string) => {
|
||||
if (url === '/email/send') return Promise.reject({ response: { status: 502, data: {
|
||||
attemptId: 'attempt-1', status: 'failed', duplicate: false, failureCategory: 'provider_rejected',
|
||||
} } });
|
||||
if (url === '/email/drafts/draft-1/new-attempt') return Promise.resolve({ data: {
|
||||
...stored, revision: 5, clientRequestId: 'new-server-request-id', updatedAtUtc: new Date().toISOString(),
|
||||
} } as any);
|
||||
return Promise.reject(new Error(`Unexpected POST ${url}`));
|
||||
});
|
||||
|
||||
renderPage();
|
||||
fireEvent.click(await screen.findByRole('button', { name: /resume saved subject/i }));
|
||||
fireEvent.click(screen.getByRole('button', { name: /review and send/i }));
|
||||
fireEvent.click(await screen.findByRole('button', { name: /^send email$/i }));
|
||||
fireEvent.click(await screen.findByRole('button', { name: /prepare new attempt/i }));
|
||||
|
||||
await waitFor(() => expect(mockedApi.post).toHaveBeenCalledWith('/email/drafts/draft-1/new-attempt', { revision: 4 }));
|
||||
await waitFor(() => expect(screen.queryByText(/provider confirmed that this attempt did not complete/i)).not.toBeInTheDocument());
|
||||
expect(screen.getByRole('button', { name: /review and send/i })).toBeEnabled();
|
||||
});
|
||||
|
||||
test('does not offer retry when delivery is uncertain', async () => {
|
||||
const original = mockedApi.get.getMockImplementation();
|
||||
mockedApi.get.mockImplementation((url: string, config?: any) => {
|
||||
|
||||
Reference in New Issue
Block a user