- Implemented Zustand store for S/MIME functionality, including state management for key records and public certificates. - Added methods for importing PKCS#12 files and public certificates, binding identities to keys, and managing unlocked keys. - Introduced session storage for remembering unlocked keys across sessions. - Enhanced error handling and loading states during data operations.
152 lines
5.0 KiB
TypeScript
152 lines
5.0 KiB
TypeScript
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
import 'fake-indexeddb/auto';
|
|
|
|
// Each test file gets a fresh global indexedDB via fake-indexeddb/auto.
|
|
// Since openDB() caches connections implicitly, we re-import the module for each test.
|
|
// However, to keep it simple, we'll just test in order and accept cumulative state,
|
|
// or we can test with unique IDs.
|
|
|
|
import {
|
|
saveKeyRecord,
|
|
getKeyRecord,
|
|
getKeyRecordForEmail,
|
|
listKeyRecords,
|
|
deleteKeyRecord,
|
|
savePublicCert,
|
|
getPublicCertForEmail,
|
|
listPublicCerts,
|
|
deletePublicCert,
|
|
} from '../key-storage';
|
|
import type { SmimeKeyRecord, SmimePublicCert } from '../types';
|
|
|
|
function makeKeyRecord(overrides: Partial<SmimeKeyRecord> = {}): SmimeKeyRecord {
|
|
return {
|
|
id: 'key-1',
|
|
email: 'user@example.com',
|
|
certificate: new ArrayBuffer(10),
|
|
certificateChain: [],
|
|
encryptedPrivateKey: new ArrayBuffer(32),
|
|
salt: new ArrayBuffer(16),
|
|
iv: new ArrayBuffer(12),
|
|
kdfIterations: 600000,
|
|
issuer: 'CN=Test CA',
|
|
subject: 'CN=Test User',
|
|
serialNumber: '01',
|
|
notBefore: '2024-01-01T00:00:00Z',
|
|
notAfter: '2030-12-31T23:59:59Z',
|
|
fingerprint: 'aa:bb:cc',
|
|
algorithm: 'RSA-2048',
|
|
capabilities: { canSign: true, canEncrypt: true },
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function makePublicCert(overrides: Partial<SmimePublicCert> = {}): SmimePublicCert {
|
|
return {
|
|
id: 'cert-1',
|
|
email: 'recipient@example.com',
|
|
certificate: new ArrayBuffer(10),
|
|
issuer: 'CN=Test CA',
|
|
subject: 'CN=Recipient',
|
|
notBefore: '2024-01-01T00:00:00Z',
|
|
notAfter: '2030-12-31T23:59:59Z',
|
|
fingerprint: 'dd:ee:ff',
|
|
source: 'manual',
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
// Use unique IDs for each test to avoid state leakage
|
|
let testCounter = 0;
|
|
function uid() { return `test-${++testCounter}-${Date.now()}`; }
|
|
|
|
describe('key-storage', () => {
|
|
describe('key records', () => {
|
|
it('saves and retrieves a key record by id', async () => {
|
|
const id = uid();
|
|
const record = makeKeyRecord({ id });
|
|
await saveKeyRecord(record);
|
|
const retrieved = await getKeyRecord(id);
|
|
expect(retrieved).toBeDefined();
|
|
expect(retrieved!.id).toBe(id);
|
|
expect(retrieved!.email).toBe('user@example.com');
|
|
});
|
|
|
|
it('returns undefined for non-existent key record', async () => {
|
|
const result = await getKeyRecord('absolutely-non-existent-' + uid());
|
|
expect(result).toBeUndefined();
|
|
});
|
|
|
|
it('retrieves key record by email', async () => {
|
|
const id = uid();
|
|
const email = `alice-${id}@example.com`;
|
|
const record = makeKeyRecord({ id, email });
|
|
await saveKeyRecord(record);
|
|
const result = await getKeyRecordForEmail(email);
|
|
expect(result).toBeDefined();
|
|
expect(result!.email).toBe(email);
|
|
});
|
|
|
|
it('lists key records (includes previously saved)', async () => {
|
|
const id1 = uid();
|
|
const id2 = uid();
|
|
await saveKeyRecord(makeKeyRecord({ id: id1, email: `${id1}@example.com` }));
|
|
await saveKeyRecord(makeKeyRecord({ id: id2, email: `${id2}@example.com` }));
|
|
const records = await listKeyRecords();
|
|
expect(records.length).toBeGreaterThanOrEqual(2);
|
|
expect(records.find(r => r.id === id1)).toBeDefined();
|
|
expect(records.find(r => r.id === id2)).toBeDefined();
|
|
});
|
|
|
|
it('deletes a key record', async () => {
|
|
const id = uid();
|
|
const record = makeKeyRecord({ id });
|
|
await saveKeyRecord(record);
|
|
await deleteKeyRecord(id);
|
|
const result = await getKeyRecord(id);
|
|
expect(result).toBeUndefined();
|
|
});
|
|
|
|
it('updates existing record with same id', async () => {
|
|
const id = uid();
|
|
const record1 = makeKeyRecord({ id, email: 'old@example.com' });
|
|
await saveKeyRecord(record1);
|
|
const record2 = makeKeyRecord({ id, email: 'new@example.com' });
|
|
await saveKeyRecord(record2);
|
|
const retrieved = await getKeyRecord(id);
|
|
expect(retrieved!.email).toBe('new@example.com');
|
|
});
|
|
});
|
|
|
|
describe('public certs', () => {
|
|
it('saves and retrieves by email', async () => {
|
|
const id = uid();
|
|
const email = `recipient-${id}@example.com`;
|
|
const cert = makePublicCert({ id, email });
|
|
await savePublicCert(cert);
|
|
const result = await getPublicCertForEmail(email);
|
|
expect(result).toBeDefined();
|
|
expect(result!.email).toBe(email);
|
|
});
|
|
|
|
it('lists public certs (includes previously saved)', async () => {
|
|
const id1 = uid();
|
|
const id2 = uid();
|
|
await savePublicCert(makePublicCert({ id: id1, email: `${id1}@test.com` }));
|
|
await savePublicCert(makePublicCert({ id: id2, email: `${id2}@test.com` }));
|
|
const certs = await listPublicCerts();
|
|
expect(certs.find(c => c.id === id1)).toBeDefined();
|
|
expect(certs.find(c => c.id === id2)).toBeDefined();
|
|
});
|
|
|
|
it('deletes a public cert', async () => {
|
|
const id = uid();
|
|
const cert = makePublicCert({ id });
|
|
await savePublicCert(cert);
|
|
await deletePublicCert(id);
|
|
const certs = await listPublicCerts();
|
|
expect(certs.find(c => c.id === id)).toBeUndefined();
|
|
});
|
|
});
|
|
});
|