mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-02 08:07:11 +00:00
The "Sender Favicons" setting only covered the in-app avatars. Push notifications always fetched the sender's favicon from icons.duckduckgo.com, so with the setting off every notification still sent the sender's domain and the device's IP to a third party. The webmail has the same setting, on by default, and fetches through its own `/api/favicon` proxy, so DuckDuckGo sees the server rather than the user. Native has no proxy, but turning the default off would silently drop the logos existing users see and diverge from the webmail, which is a product call. So the default stays on and the one existing switch now covers every favicon fetch: the notification task reads it from the persisted settings and shows initials when it is off, or when the settings cannot be read.
112 lines
4.1 KiB
TypeScript
112 lines
4.1 KiB
TypeScript
import { describe, it, expect, vi } from 'vitest';
|
|
|
|
vi.mock('../../api/jmap-client', () => ({
|
|
jmapClient: {
|
|
accountId: 'jmap-primary',
|
|
getStoredCredentials: vi.fn(async () => null),
|
|
setStoredCredentials: vi.fn(async () => undefined),
|
|
},
|
|
}));
|
|
vi.mock('../client-cert', () => ({ secureFetch: vi.fn(async () => ({ ok: false, status: 500 })) }));
|
|
vi.mock('../oauth', () => ({ refreshOAuthAccessToken: vi.fn(async (t: unknown) => t) }));
|
|
|
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
import {
|
|
matchAccountsForPush,
|
|
parseRelayPushData,
|
|
selectNotifiableEmails,
|
|
senderFaviconsAllowed,
|
|
} from '../push-background-task';
|
|
import type { Email } from '../../api/types';
|
|
|
|
describe('parseRelayPushData', () => {
|
|
it('decodes the relay FCM data payload (all values are strings)', () => {
|
|
const parsed = parseRelayPushData({
|
|
kind: 'jmap-email-push',
|
|
accountLabel: 'alice',
|
|
accountId: 'a1',
|
|
emailIds: JSON.stringify(['m1', 'm2']),
|
|
changed: JSON.stringify({ a1: { EmailDelivery: 's1' } }),
|
|
});
|
|
expect(parsed).toEqual({
|
|
kind: 'jmap-email-push',
|
|
accountLabel: 'alice',
|
|
jmapAccountId: 'a1',
|
|
emailIds: ['m1', 'm2'],
|
|
changed: { a1: { EmailDelivery: 's1' } },
|
|
});
|
|
});
|
|
|
|
it('falls back to the first key of `changed` for the account id', () => {
|
|
const parsed = parseRelayPushData({
|
|
kind: 'jmap-state-change',
|
|
emailIds: '[]',
|
|
changed: JSON.stringify({ a9: { Email: 'x' } }),
|
|
});
|
|
expect(parsed.jmapAccountId).toBe('a9');
|
|
expect(parsed.emailIds).toEqual([]);
|
|
});
|
|
|
|
it('tolerates garbage', () => {
|
|
expect(parseRelayPushData(null).emailIds).toEqual([]);
|
|
expect(parseRelayPushData({ emailIds: '{not json' }).emailIds).toEqual([]);
|
|
expect(parseRelayPushData({ kind: 'weird' }).kind).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('matchAccountsForPush', () => {
|
|
const accounts = ['alice@mail.example.com', 'bob@mail.example.com'];
|
|
const registry = [
|
|
{ id: 'alice@mail.example.com', username: 'alice' },
|
|
{ id: 'bob@mail.example.com', username: 'bob' },
|
|
];
|
|
|
|
it('matches on the recorded JMAP account id first', () => {
|
|
const payload = parseRelayPushData({ accountId: 'jb', accountLabel: 'alice' });
|
|
expect(matchAccountsForPush(payload, accounts, { 'bob@mail.example.com': 'jb' }, registry))
|
|
.toEqual(['bob@mail.example.com']);
|
|
});
|
|
|
|
it('falls back to the relay accountLabel (username)', () => {
|
|
const payload = parseRelayPushData({ accountId: 'unknown', accountLabel: 'alice' });
|
|
expect(matchAccountsForPush(payload, accounts, {}, registry)).toEqual(['alice@mail.example.com']);
|
|
});
|
|
|
|
it('checks every account when nothing matches', () => {
|
|
const payload = parseRelayPushData({ accountId: 'unknown', accountLabel: 'carol' });
|
|
expect(matchAccountsForPush(payload, accounts, {}, registry)).toEqual(accounts);
|
|
});
|
|
});
|
|
|
|
describe('selectNotifiableEmails', () => {
|
|
const email = (id: string, keywords: Record<string, boolean> = {}): Email =>
|
|
({ id, threadId: 't', keywords, mailboxIds: {}, size: 0, receivedAt: '', hasAttachment: false } as Email);
|
|
|
|
it('drops read, junk and already-notified messages', () => {
|
|
const out = selectNotifiableEmails(
|
|
[email('a'), email('b', { $seen: true }), email('c', { $junk: true }), email('d')],
|
|
['d'],
|
|
);
|
|
expect(out.map((e) => e.id)).toEqual(['a']);
|
|
});
|
|
});
|
|
|
|
describe('senderFaviconsAllowed', () => {
|
|
const KEY = 'webmail:settings:v1';
|
|
|
|
it('follows the "Sender Favicons" setting the settings store persisted', async () => {
|
|
await AsyncStorage.setItem(KEY, JSON.stringify({ senderFavicons: false }));
|
|
expect(await senderFaviconsAllowed()).toBe(false);
|
|
await AsyncStorage.setItem(KEY, JSON.stringify({ senderFavicons: true }));
|
|
expect(await senderFaviconsAllowed()).toBe(true);
|
|
});
|
|
|
|
it('defaults to on like the store, but skips icons when the settings are unreadable', async () => {
|
|
await AsyncStorage.removeItem(KEY);
|
|
expect(await senderFaviconsAllowed()).toBe(true);
|
|
await AsyncStorage.setItem(KEY, '{not json');
|
|
expect(await senderFaviconsAllowed()).toBe(false);
|
|
await AsyncStorage.removeItem(KEY);
|
|
});
|
|
});
|