import { describe, it, expect, beforeEach } from 'vitest'; import { ProviderCredentialService } from './provider-credential.service'; const KEY_B64 = Buffer.alloc(32, 7).toString('base64'); /** Minimal in-memory stand-in for prisma.iiosProviderCredential (upsert/findUnique). */ function fakePrisma() { const rows = new Map>(); const k = (scopeId: string, providerType: string) => `${scopeId}::${providerType}`; return { _rows: rows, iiosProviderCredential: { // eslint-disable-next-line @typescript-eslint/no-explicit-any async upsert({ where, create, update }: any) { const key = k(where.scopeId_providerType.scopeId, where.scopeId_providerType.providerType); const existing = rows.get(key); const row = existing ? { ...existing, ...update } : { ...create }; rows.set(key, row); return row; }, // eslint-disable-next-line @typescript-eslint/no-explicit-any async findUnique({ where }: any) { return rows.get(k(where.scopeId_providerType.scopeId, where.scopeId_providerType.providerType)) ?? null; }, }, }; } function make(prisma: ReturnType, env: NodeJS.ProcessEnv): ProviderCredentialService { // eslint-disable-next-line @typescript-eslint/no-explicit-any const s = new ProviderCredentialService(prisma as any); s.env = env; return s; } describe('ProviderCredentialService', () => { let prisma: ReturnType; let svc: ProviderCredentialService; beforeEach(() => { prisma = fakePrisma(); svc = make(prisma, { IIOS_CRED_KEY: KEY_B64 } as NodeJS.ProcessEnv); }); it('seals the secret at rest — plaintext token never stored', async () => { await svc.upsert('scope_1', 'TWILIO_SMS', { accountSid: 'AC123', authToken: 'tok_secret', fromNumber: '+15550001111' }); const stored = JSON.stringify([...prisma._rows.values()]); expect(stored).not.toContain('tok_secret'); expect(stored).toContain('cipherText'); }); it('resolves back the exact config it sealed', async () => { const cfg = { accountSid: 'AC123', authToken: 'tok_secret', fromNumber: '+15550001111' }; await svc.upsert('scope_1', 'TWILIO_SMS', cfg); expect(await svc.resolve('scope_1', 'TWILIO_SMS')).toEqual(cfg); }); it('status is masked — reveals hints, never the token', async () => { await svc.upsert('scope_1', 'TWILIO_SMS', { accountSid: 'AC1234567', authToken: 'tok_secret', fromNumber: '+15550001111' }); const status = await svc.status('scope_1', 'TWILIO_SMS'); expect(status).toMatchObject({ configured: true, enabled: true, hints: { fromNumber: '+15550001111', sidLast4: '4567' } }); expect(JSON.stringify(status)).not.toContain('tok_secret'); }); it('reports not-configured for an unknown scope', async () => { expect(await svc.status('nope', 'TWILIO_SMS')).toEqual({ configured: false }); expect(await svc.resolve('nope', 'TWILIO_SMS')).toBeNull(); }); it('does not resolve a disabled credential', async () => { await svc.upsert('scope_1', 'TWILIO_SMS', { accountSid: 'AC123', authToken: 't', fromNumber: '+1' }, { enabled: false }); expect(await svc.resolve('scope_1', 'TWILIO_SMS')).toBeNull(); }); it('throws when the platform key is absent (fail closed, never store plaintext)', async () => { const noKey = make(prisma, {} as NodeJS.ProcessEnv); await expect(noKey.upsert('s', 'TWILIO_SMS', { accountSid: 'A', authToken: 't', fromNumber: '+1' })).rejects.toThrow(/IIOS_CRED_KEY/); }); });