mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 16:13:15 +00:00
Stalwart 1.0 puts `$hasattachment` or `$hasnoattachment` on every message and derives `hasAttachment` from them alone. It also refuses keywords over 128 bytes (0.16 cuts them to 128 characters) and more than 100 keywords on one message. The keyword writers ignored Email/set `notUpdated`, so a refused tag showed until the next reload and then vanished. - `markAsRead`, `batchMarkAsRead`, `toggleStar`, `updateEmailKeywords`, `setKeyword`, `removeKeyword` and `batchUpdateKeywords` check the Email/set outcome, and the errors carry the JMAP type. Tagging from the list or the viewer shows "Tagging failed"; a refused category-tab move rejects the plugin's promise and skips the categorize hooks. - Tag ids are capped at 121 characters (128 bytes with `$label:`). - The attachment keywords are hidden in view source, the plugin projection and the `getKeywords` fallback labels, reserved for category tabs, and refused by the plugin keyword API. Email/copy and the cross-server import still carry them over. - A tag comes off by leaving its key out of the full keyword map instead of being sent as false, which RFC 8621 does not allow; `updateEmailKeywords` also drops the false entries local updates leave behind (`$seen: false` after marking unread). - The automatic mark-as-read no longer leaves an unhandled rejection when the server refuses it.
67 lines
2.9 KiB
TypeScript
67 lines
2.9 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import type { Email } from '@/lib/jmap/types';
|
|
import { generateEmailSource } from '@/lib/email-source';
|
|
import { emailToReadView } from '@/lib/plugin-projection';
|
|
import { isServerSetKeyword } from '@/lib/jmap/server-keywords';
|
|
|
|
// Stalwart 1.0 puts `$hasattachment` or `$hasnoattachment` on every message and
|
|
// derives hasAttachment from it. Where keywords are listed they would only
|
|
// repeat hasAttachment, so they are left out; 0.16 never sets them.
|
|
|
|
function makeEmail(keywords: Record<string, boolean>, hasAttachment: boolean): Email {
|
|
return {
|
|
id: 'm1',
|
|
threadId: 't1',
|
|
mailboxIds: { inbox: true },
|
|
keywords,
|
|
size: 2048,
|
|
receivedAt: '2026-09-25T10:00:00Z',
|
|
from: [{ name: 'Ann', email: 'ann@example.org' }],
|
|
to: [{ name: '', email: 'bob@example.org' }],
|
|
subject: 'Report',
|
|
preview: 'See attached',
|
|
hasAttachment,
|
|
textBody: [{ partId: '1', blobId: 'b1', size: 12, type: 'text/plain' }],
|
|
htmlBody: [],
|
|
bodyValues: { '1': { value: 'See attached', isEncodingProblem: false, isTruncated: false } },
|
|
} as unknown as Email;
|
|
}
|
|
|
|
describe('isServerSetKeyword', () => {
|
|
it('matches the two attachment keywords in any case, and nothing else', () => {
|
|
expect(isServerSetKeyword('$hasattachment')).toBe(true);
|
|
expect(isServerSetKeyword('$hasnoattachment')).toBe(true);
|
|
expect(isServerSetKeyword('$HasAttachment')).toBe(true);
|
|
expect(isServerSetKeyword('$seen')).toBe(false);
|
|
expect(isServerSetKeyword('$label:hasattachment')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('server-set keywords in listings', () => {
|
|
it('leaves them out of the view-source keyword line on Stalwart 1.0', () => {
|
|
const source = generateEmailSource(makeEmail({ $seen: true, '$label:work': true, $hasattachment: true }, true));
|
|
expect(source).toContain('Has Attachment: Yes\n');
|
|
expect(source).toContain('Keywords: $seen, $label:work\n');
|
|
expect(source).not.toContain('$hasattachment');
|
|
});
|
|
|
|
it('drops the keyword line when the attachment keyword is the only one on Stalwart 1.0', () => {
|
|
const source = generateEmailSource(makeEmail({ $hasnoattachment: true }, false));
|
|
expect(source).toContain('Has Attachment: No\n');
|
|
expect(source).not.toContain('Keywords:');
|
|
});
|
|
|
|
it('keeps the plugin projection keywords to what the user set, with hasAttachment intact', () => {
|
|
const view = emailToReadView(makeEmail({ $seen: true, $hasattachment: true, $flagged: false }, true));
|
|
expect(view.keywords).toEqual(['$seen']);
|
|
expect(view.hasAttachment).toBe(true);
|
|
expect(view.source).not.toContain('$hasattachment');
|
|
});
|
|
|
|
it('lists keywords unchanged on Stalwart 0.16, which never sets them', () => {
|
|
const email = makeEmail({ $seen: true, '$label:work': true }, true);
|
|
expect(emailToReadView(email).keywords).toEqual(['$seen', '$label:work']);
|
|
expect(generateEmailSource(email)).toContain('Keywords: $seen, $label:work\n');
|
|
});
|
|
});
|