crypto.test.ts
2,001 bytes
| 1 | import { describe, expect, it } from 'vitest'; |
|---|---|
| 2 | import { decryptSecret, encryptSecret, maskKey, safeEqual } from './crypto'; |
| 3 | |
| 4 | describe('encryptSecret / decryptSecret', () => { |
| 5 | it('round-trips a secret', () => { |
| 6 | const secret = 'sk-or-v1-abcdef0123456789'; |
| 7 | const encrypted = encryptSecret(secret); |
| 8 | expect(decryptSecret(encrypted)).toBe(secret); |
| 9 | }); |
| 10 | |
| 11 | it('produces a versioned, dot-delimited ciphertext that hides the plaintext', () => { |
| 12 | const encrypted = encryptSecret('super-secret-key'); |
| 13 | expect(encrypted.startsWith('v1.')).toBe(true); |
| 14 | expect(encrypted.split('.')).toHaveLength(4); |
| 15 | expect(encrypted).not.toContain('super-secret-key'); |
| 16 | }); |
| 17 | |
| 18 | it('uses a fresh IV so identical plaintext encrypts differently', () => { |
| 19 | const a = encryptSecret('same'); |
| 20 | const b = encryptSecret('same'); |
| 21 | expect(a).not.toBe(b); |
| 22 | expect(decryptSecret(a)).toBe('same'); |
| 23 | expect(decryptSecret(b)).toBe('same'); |
| 24 | }); |
| 25 | |
| 26 | it('rejects tampered ciphertext (GCM auth tag)', () => { |
| 27 | const encrypted = encryptSecret('integrity-matters'); |
| 28 | const parts = encrypted.split('.'); |
| 29 | // Flip a character in the ciphertext segment. |
| 30 | const tampered = [parts[0], parts[1], parts[2], `${parts[3]!.slice(0, -1)}X`].join('.'); |
| 31 | expect(() => decryptSecret(tampered)).toThrow(); |
| 32 | }); |
| 33 | |
| 34 | it('rejects a malformed payload', () => { |
| 35 | expect(() => decryptSecret('not-a-valid-payload')).toThrow(); |
| 36 | expect(() => decryptSecret('v2.a.b.c')).toThrow(); |
| 37 | }); |
| 38 | }); |
| 39 | |
| 40 | describe('maskKey', () => { |
| 41 | it('shows a prefix and suffix only', () => { |
| 42 | expect(maskKey('sk-or-v1-abcdef0123456789xyz')).toBe('sk-or-...9xyz'); |
| 43 | }); |
| 44 | it('fully masks short values', () => { |
| 45 | expect(maskKey('short')).toBe('••••'); |
| 46 | }); |
| 47 | }); |
| 48 | |
| 49 | describe('safeEqual', () => { |
| 50 | it('is true for equal strings and false otherwise', () => { |
| 51 | expect(safeEqual('token', 'token')).toBe(true); |
| 52 | expect(safeEqual('token', 'tokes')).toBe(false); |
| 53 | expect(safeEqual('token', 'tokenn')).toBe(false); |
| 54 | }); |
| 55 | }); |
| 56 | |