Files
webmail/lib/__tests__/send-failure-message.test.ts
T
Linus Rath c633185cda Merge branch 'main' into feat/stalwart-1.0
# Conflicts:
#	CONTRIBUTING.md
#	LICENSE
#	README.md
#	app/api/calendar-agenda/route.ts
#	app/api/wopi/files/[fileId]/contents/route.ts
#	app/api/wopi/launch/route.ts
#	components/calendar/calendar-app.tsx
#	components/calendar/calendar-day-view.tsx
#	components/calendar/calendar-month-view.tsx
#	components/calendar/calendar-week-view.tsx
#	components/calendar/event-modal.tsx
#	components/contacts/contact-detail.tsx
#	components/email/email-composer.tsx
#	components/email/email-list.tsx
#	components/files/file-browser.tsx
#	components/files/files-app.tsx
#	components/files/folder-tree-sidebar.tsx
#	components/filters/filter-rule-modal.tsx
#	components/mail/mail-app.tsx
#	hooks/use-mailbox-drop.ts
#	integration/run-tests.sh
#	lib/__tests__/calendar-agenda-route.test.ts
#	lib/__tests__/jmap-draft-lifecycle.test.ts
#	lib/__tests__/web-push.test.ts
#	lib/account-state-manager.ts
#	lib/jmap/client-interface.ts
#	lib/jmap/client.ts
#	lib/jmap/types.ts
#	lib/message-list-order.ts
#	lib/sieve/generator.ts
#	lib/sieve/parser.ts
#	lib/telemetry/payload.ts
#	lib/thread-utils.ts
#	lib/unified-mailbox.ts
#	lib/web-push.ts
#	lib/wopi/token.ts
#	locales/ar/common.json
#	locales/ca/common.json
#	locales/cs/common.json
#	locales/da/common.json
#	locales/de/common.json
#	locales/en/common.json
#	locales/es/common.json
#	locales/fa/common.json
#	locales/fr/common.json
#	locales/he/common.json
#	locales/hu/common.json
#	locales/it/common.json
#	locales/ja/common.json
#	locales/ko/common.json
#	locales/lv/common.json
#	locales/mn/common.json
#	locales/nb/common.json
#	locales/nl/common.json
#	locales/pl/common.json
#	locales/pt/common.json
#	locales/ro/common.json
#	locales/ru/common.json
#	locales/sk/common.json
#	locales/tr/common.json
#	locales/uk/common.json
#	locales/zh-TW/common.json
#	locales/zh/common.json
#	scripts/lite/lib.mjs
#	stores/__tests__/calendar-store-synthetic-ids.test.ts
#	stores/__tests__/email-store-shared-folder-keywords.test.ts
#	stores/__tests__/email-store-unified-actions.test.ts
#	stores/__tests__/file-store.test.ts
#	stores/email-store.ts
#	stores/file-store.ts
#	stores/message-list-tabs-store.ts
2026-10-12 08:35:08 +02:00

123 lines
6.8 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { NoLongerScheduledError, RecipientsRejectedError, RequestTimeoutError, ScheduleTooLateError, SubmissionQuotaError, formatRejectedRecipients } from '../jmap/client';
import { SendCancelledCleanupError, cancelFailureToast, partlyRefusedWarning, rescheduleFailureToast, sendFailureMessage } from '../send-failure-message';
// Keys of a namespace, returned with their values so the test sees which
// message was chosen and what went into it.
const translator = (namespace: string) => (key: string, values?: Record<string, string>) =>
values ? `${namespace}.${key} ${JSON.stringify(values)}` : `${namespace}.${key}`;
const t = translator('email_composer');
const tViewer = translator('email_viewer');
const STALWART_QUOTA = 'There are too many email submissions, please delete some before adding a new one.';
describe('sendFailureMessage', () => {
it('explains a full submission quota instead of showing the server text on Stalwart 1.0', () => {
const error = new SubmissionQuotaError(STALWART_QUOTA, true);
expect(sendFailureMessage(error, t)).toBe('email_composer.submission_quota');
});
it("gives another server's overQuota its own description, not Stalwart's explanation", () => {
expect(sendFailureMessage(new SubmissionQuotaError('Daily sending limit exceeded'), t))
.toBe('email_composer.submission_limit {"reason":"Daily sending limit exceeded"}');
expect(sendFailureMessage(new SubmissionQuotaError(undefined, false), t))
.toBe('email_composer.submission_limit {"reason":"overQuota"}');
});
it('points at Sent after a timeout and names the hold limit', () => {
expect(sendFailureMessage(new RequestTimeoutError(30_000), t)).toBe('email_composer.send_timeout');
expect(sendFailureMessage(new ScheduleTooLateError(604800), t)).toBe('email_composer.schedule_send_too_late');
});
it("shows other errors' own text, and a generic message when there is none", () => {
expect(sendFailureMessage(new Error('Envelope mailFrom does not match identity email address. [forbiddenFrom]'), t))
.toBe('Envelope mailFrom does not match identity email address. [forbiddenFrom]');
expect(sendFailureMessage(new Error(''), t)).toBe('email_composer.send_failed');
expect(sendFailureMessage('nope', t)).toBe('email_composer.send_failed');
});
// Stalwart 0.16 and 1.0 take the submission and refuse the recipients at
// RCPT, each with its own reply.
it('says the message was not sent and names every refused recipient with its reply', () => {
const error = new RecipientsRejectedError([
{ email: 'nobody@example.net', smtpReply: '550 5.1.2 Relay not allowed.' },
{ email: 'busy@example.org', smtpReply: '452 4.4.5 Rate limit exceeded, try again later.' },
]);
expect(sendFailureMessage(error, t)).toBe(
'email_composer.recipients_refused {"recipients":"nobody@example.net (550 5.1.2 Relay not allowed.), busy@example.org (452 4.4.5 Rate limit exceeded, try again later.)"}',
);
});
});
describe('formatRejectedRecipients', () => {
it('lists an address without its reply when the server gave none', () => {
expect(formatRejectedRecipients([{ email: 'a@example.net', smtpReply: '' }, { email: 'b@example.net', smtpReply: '550 5.1.1 No such user' }]))
.toBe('a@example.net, b@example.net (550 5.1.1 No such user)');
});
});
describe('rescheduleFailureToast', () => {
it('says the message is still scheduled, and when, at a full quota on Stalwart 1.0', () => {
const error = new SubmissionQuotaError(STALWART_QUOTA, true);
expect(rescheduleFailureToast(error, tViewer, t, 'Sep 26, 10:00')).toEqual({
title: 'email_viewer.still_scheduled_at {"date":"Sep 26, 10:00"}',
message: 'email_composer.submission_quota',
});
expect(rescheduleFailureToast(error, tViewer, t).title).toBe('email_viewer.still_scheduled');
});
it('says the message went out, or is no longer scheduled, when the original left the queue meanwhile', () => {
expect(rescheduleFailureToast(new NoLongerScheduledError(true), tViewer, t, 'Sep 26, 10:00'))
.toEqual({ title: 'email_viewer.already_sent' });
expect(rescheduleFailureToast(new NoLongerScheduledError(false), tViewer, t))
.toEqual({ title: 'email_viewer.no_longer_scheduled' });
});
// The original is kept for all of its recipients, so the headline must not
// read as "not sent", which invites sending the message a second time.
it('leads with the kept schedule when the server refused recipients of the replacement (Stalwart 0.16 and 1.0)', () => {
const error = new RecipientsRejectedError([{ email: 'bob@example.org', smtpReply: '452 4.4.5 Rate limit exceeded, try again later.' }]);
expect(rescheduleFailureToast(error, tViewer, t, 'Sep 26, 10:00')).toEqual({
title: 'email_viewer.still_scheduled_at {"date":"Sep 26, 10:00"}',
message: 'email_viewer.reschedule_recipients_refused {"recipients":"bob@example.org (452 4.4.5 Rate limit exceeded, try again later.)"}',
});
expect(rescheduleFailureToast(error, tViewer, t).title).toBe('email_viewer.still_scheduled');
});
it('falls back to the send failure message otherwise', () => {
expect(rescheduleFailureToast(new RequestTimeoutError(30_000), tViewer, t, 'Sep 26, 10:00'))
.toEqual({ title: 'email_composer.send_timeout' });
expect(rescheduleFailureToast(new Error('cannotUnsend'), tViewer, t)).toEqual({ title: 'cannotUnsend' });
});
});
describe('partlyRefusedWarning', () => {
it('names the recipients the server refused while it took the others', () => {
expect(partlyRefusedWarning({ rejectedRecipients: [{ email: 'nobody@example.net', smtpReply: '550 5.1.2 Relay not allowed.' }] }, t))
.toBe('email_composer.recipients_partly_refused {"recipients":"nobody@example.net (550 5.1.2 Relay not allowed.)"}');
});
it('says nothing when the server took every recipient', () => {
expect(partlyRefusedWarning({}, t)).toBeNull();
expect(partlyRefusedWarning({ rejectedRecipients: [] }, t)).toBeNull();
});
});
describe('cancelFailureToast', () => {
it('reports a failed cancel with its reason: the mail may still go out', () => {
expect(cancelFailureToast(new Error('The requested message is no longer in the queue.'), tViewer, 'Undo send failed'))
.toEqual({ title: 'Undo send failed', message: 'The requested message is no longer in the queue.' });
});
it('says the send was cancelled when only a step after the cancel failed', () => {
expect(cancelFailureToast(new SendCancelledCleanupError('restore', new Error('503')), tViewer, 'Undo send failed'))
.toEqual({ title: 'email_viewer.send_cancelled_restore_failed' });
expect(cancelFailureToast(new SendCancelledCleanupError('remove', new Error('503')), tViewer, 'Undo send failed'))
.toEqual({ title: 'email_viewer.send_cancelled_remove_failed' });
});
});