Golden-master tests pinning the CURRENT behavior of high-value modules that had no coverage — integration seams, security helpers, two API route handlers, and complex pure utils. 111 tests across 12 files. New tests: - auth-crypto / session-cookie: AES-256-GCM session encryption roundtrip, tamper/version/missing-secret handling; cookie-slot naming. - unified-mailbox: multi-account fan-out, sort, totals, per-account error isolation, personal-vs-shared JMAP target resolution, counts/roles. - account-state-manager: snapshot/restore across the six real Zustand stores; clearAllStores reset shape; evict. - mdn: RFC 5322 MDN assembly (CRLF, RFC2047, base64 wrap, headers). - tnef: winmail.dat binary parsing from hand-built fixtures. - download-filename / subject-prefix / birthday-calendar / eml-import: filename templating, multilingual prefix stripping, birthday event generation, .eml/.zip import. - webdav / caldav-discover route handlers: auth guards, path validation, upstream URL construction, candidate probing. - helpers/factories.ts: shared makeEmail/makeMailbox/makeFakeJmapClient. Tests follow the repo's existing patterns (route-import, fake IJMAPClient, fetch spy, real store singletons). Where current behavior looks buggy it is pinned and flagged with a // CHARACTERISATION: comment (see PR for the suspected-bugs list); no production code is changed.
129 lines
4.9 KiB
TypeScript
129 lines
4.9 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
import {
|
|
snapshotAccount,
|
|
restoreAccount,
|
|
clearAllStores,
|
|
evictAccount,
|
|
evictAll,
|
|
} from '@/lib/account-state-manager';
|
|
import { useEmailStore } from '@/stores/email-store';
|
|
import { useContactStore } from '@/stores/contact-store';
|
|
import { useCalendarStore } from '@/stores/calendar-store';
|
|
import { useFilterStore } from '@/stores/filter-store';
|
|
import { useIdentityStore } from '@/stores/identity-store';
|
|
import { useVacationStore } from '@/stores/vacation-store';
|
|
import { DEFAULT_SEARCH_FILTERS } from '@/lib/jmap/search-utils';
|
|
import { makeEmail, makeMailbox } from './helpers/factories';
|
|
|
|
// The store singletons and the module-level snapshot cache persist across
|
|
// tests; reset both around every test.
|
|
beforeEach(() => evictAll());
|
|
afterEach(() => evictAll());
|
|
|
|
describe('snapshotAccount / restoreAccount', () => {
|
|
it('round-trips the captured fields across all six stores', () => {
|
|
useEmailStore.setState({
|
|
emails: [makeEmail({ id: 'a1' })],
|
|
mailboxes: [makeMailbox({ id: 'a-in' })],
|
|
selectedMailbox: 'a-in',
|
|
searchQuery: 'queryA',
|
|
quota: { used: 1, total: 2 },
|
|
});
|
|
useContactStore.setState({ supportsSync: true });
|
|
useCalendarStore.setState({ viewMode: 'week', supportsCalendar: true });
|
|
useFilterStore.setState({ isSupported: true });
|
|
useIdentityStore.setState({ preferredPrimaryId: 'idA' });
|
|
useVacationStore.setState({ isEnabled: true });
|
|
|
|
snapshotAccount('A');
|
|
|
|
// Mutate everything to "account B" values.
|
|
useEmailStore.setState({ emails: [], selectedMailbox: 'b-in', searchQuery: 'queryB', quota: null });
|
|
useContactStore.setState({ supportsSync: false });
|
|
useCalendarStore.setState({ viewMode: 'month', supportsCalendar: false });
|
|
useFilterStore.setState({ isSupported: false });
|
|
useIdentityStore.setState({ preferredPrimaryId: 'idB' });
|
|
useVacationStore.setState({ isEnabled: false });
|
|
|
|
expect(restoreAccount('A')).toBe(true);
|
|
|
|
expect(useEmailStore.getState().emails.map((e) => e.id)).toEqual(['a1']);
|
|
expect(useEmailStore.getState().selectedMailbox).toBe('a-in');
|
|
expect(useEmailStore.getState().searchQuery).toBe('queryA');
|
|
expect(useEmailStore.getState().quota).toEqual({ used: 1, total: 2 });
|
|
expect(useContactStore.getState().supportsSync).toBe(true);
|
|
expect(useCalendarStore.getState().viewMode).toBe('week');
|
|
expect(useFilterStore.getState().isSupported).toBe(true);
|
|
expect(useIdentityStore.getState().preferredPrimaryId).toBe('idA');
|
|
expect(useVacationStore.getState().isEnabled).toBe(true);
|
|
});
|
|
|
|
it('CHARACTERISATION: only snapshotted fields are restored; others survive', () => {
|
|
// isLoading is NOT part of the email snapshot subset.
|
|
useEmailStore.setState({ selectedMailbox: 'a-in', isLoading: false });
|
|
snapshotAccount('A');
|
|
useEmailStore.setState({ selectedMailbox: 'b-in', isLoading: true });
|
|
|
|
restoreAccount('A');
|
|
|
|
expect(useEmailStore.getState().selectedMailbox).toBe('a-in'); // restored
|
|
expect(useEmailStore.getState().isLoading).toBe(true); // NOT restored (merge)
|
|
});
|
|
|
|
it('CHARACTERISATION: snapshot stores array references, not deep clones', () => {
|
|
const arr = [makeEmail({ id: '1' })];
|
|
useEmailStore.setState({ emails: arr });
|
|
snapshotAccount('A');
|
|
arr.push(makeEmail({ id: '2' })); // mutate the same array after snapshot
|
|
useEmailStore.setState({ emails: [] });
|
|
|
|
restoreAccount('A');
|
|
// The post-snapshot mutation leaked into the snapshot.
|
|
expect(useEmailStore.getState().emails.map((e) => e.id)).toEqual(['1', '2']);
|
|
});
|
|
|
|
it('returns false and leaves stores untouched for an unknown account', () => {
|
|
useEmailStore.setState({ searchQuery: 'keep' });
|
|
expect(restoreAccount('nope')).toBe(false);
|
|
expect(useEmailStore.getState().searchQuery).toBe('keep');
|
|
});
|
|
});
|
|
|
|
describe('clearAllStores', () => {
|
|
it('resets the email store to fresh empty collections', () => {
|
|
useEmailStore.setState({
|
|
emails: [makeEmail({ id: 'x' })],
|
|
selectedEmailIds: new Set(['x']),
|
|
searchQuery: 'q',
|
|
tagCounts: { a: { total: 1, unread: 0 } },
|
|
threadEmailsCache: new Map([['t', []]]),
|
|
});
|
|
|
|
clearAllStores();
|
|
|
|
const s = useEmailStore.getState();
|
|
expect(s.emails).toEqual([]);
|
|
expect(s.searchQuery).toBe('');
|
|
expect(s.selectedEmailIds.size).toBe(0);
|
|
expect(s.threadEmailsCache.size).toBe(0);
|
|
expect(s.tagCounts).toEqual({});
|
|
expect(s.searchFilters).toEqual(DEFAULT_SEARCH_FILTERS);
|
|
});
|
|
});
|
|
|
|
describe('evictAccount / evictAll', () => {
|
|
it('evictAccount drops a single snapshot', () => {
|
|
snapshotAccount('A');
|
|
evictAccount('A');
|
|
expect(restoreAccount('A')).toBe(false);
|
|
});
|
|
|
|
it('evictAll drops every snapshot', () => {
|
|
snapshotAccount('B');
|
|
snapshotAccount('C');
|
|
evictAll();
|
|
expect(restoreAccount('B')).toBe(false);
|
|
expect(restoreAccount('C')).toBe(false);
|
|
});
|
|
});
|