/** * Tests for encryption utility functions */ import { encryptToken, decryptToken, validateEncryptionSecret } from "../../src/utils/encryption-utils"; describe("encryption-utils", () => { const validSecret = "this-is-a-very-secure-secret-key-at-least-32-chars-long"; const testToken = "test-access-token-value"; describe("validateEncryptionSecret", () => { it("should accept valid secrets (32+ chars)", () => { expect(() => validateEncryptionSecret(validSecret)).not.toThrow(); }); it("should reject short secrets", () => { expect(() => validateEncryptionSecret("short")).toThrowError(/at least 32 characters/); }); it("should reject empty secrets", () => { expect(() => validateEncryptionSecret("")).toThrow(); }); it("should accept exactly 32 character secrets", () => { const exactSecret = "a".repeat(32); expect(() => validateEncryptionSecret(exactSecret)).not.toThrow(); }); }); describe("encryptToken", () => { it("should encrypt a token", () => { const encrypted = encryptToken(testToken, validSecret); expect(encrypted).toBeDefined(); expect(typeof encrypted).toBe("string"); expect(encrypted).not.toBe(testToken); }); it("should produce different ciphertext each time (unique IV)", () => { const encrypted1 = encryptToken(testToken, validSecret); const encrypted2 = encryptToken(testToken, validSecret); expect(encrypted1).not.toBe(encrypted2); }); it("should produce valid hex output with colons", () => { const encrypted = encryptToken(testToken, validSecret); // Format: iv:authTag:encryptedData (all hex-encoded) expect(encrypted).toMatch(/^[a-f0-9]+:[a-f0-9]+:[a-f0-9]+$/); }); it("should handle empty tokens", () => { const encrypted = encryptToken("", validSecret); expect(encrypted).toBeDefined(); expect(typeof encrypted).toBe("string"); }); }); describe("decryptToken", () => { it("should decrypt an encrypted token", () => { const encrypted = encryptToken(testToken, validSecret); const decrypted = decryptToken(encrypted, validSecret); expect(decrypted).toBe(testToken); }); it("should handle empty tokens", () => { const encrypted = encryptToken("", validSecret); const decrypted = decryptToken(encrypted, validSecret); expect(decrypted).toBe(""); }); it("should handle long tokens", () => { const longToken = "a".repeat(1000); const encrypted = encryptToken(longToken, validSecret); const decrypted = decryptToken(encrypted, validSecret); expect(decrypted).toBe(longToken); }); it("should throw error for invalid ciphertext", () => { expect(() => decryptToken("invalid-base64", validSecret)).toThrow(); }); it("should throw error for tampered ciphertext", () => { const encrypted = encryptToken(testToken, validSecret); const tampered = encrypted.substring(0, encrypted.length - 5) + "xxxxx"; expect(() => decryptToken(tampered, validSecret)).toThrow(); }); it("should throw error for wrong secret", () => { const encrypted = encryptToken(testToken, validSecret); const wrongSecret = "different-secret-key-at-least-32-chars-long-enough"; expect(() => decryptToken(encrypted, wrongSecret)).toThrow(); }); }); describe("encrypt/decrypt round-trip", () => { it("should successfully round-trip various token formats", () => { const testCases = [ "simple-token", "token.with.dots", "token-with-dashes", "token_with_underscores", "VeryLongTokenWithMixedCaseAndNumbers123456789", "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.sig", ]; testCases.forEach((token) => { const encrypted = encryptToken(token, validSecret); const decrypted = decryptToken(encrypted, validSecret); expect(decrypted).toBe(token); }); }); it("should handle special characters", () => { const specialToken = "token!@#$%^&*()+=[]{}|;:',.<>?/~`"; const encrypted = encryptToken(specialToken, validSecret); const decrypted = decryptToken(encrypted, validSecret); expect(decrypted).toBe(specialToken); }); it("should handle unicode characters", () => { const unicodeToken = "token-with-emoji-😀-and-unicode-™-©"; const encrypted = encryptToken(unicodeToken, validSecret); const decrypted = decryptToken(encrypted, validSecret); expect(decrypted).toBe(unicodeToken); }); }); });