import { describe, test, expect } from "bun:test"; import { generateSecureValue } from "../../src/modules/credentials/generateSecureValue"; import { readSecureValue } from "../../src/modules/credentials/readSecureValue"; /** * Tests for the secure value encryption/decryption round-trip. * * The test setup.ts mocks the constants module with a matching RSA key pair, * so generateSecureValue (uses public key) and readSecureValue (uses private key) * will work correctly together. */ describe("generateSecureValue", () => { test("returns an object with encrypted and hash fields", () => { const result = generateSecureValue("my-secret-password"); expect(result).toHaveProperty("encrypted"); expect(result).toHaveProperty("hash"); expect(typeof result.encrypted).toBe("string"); expect(typeof result.hash).toBe("string"); }); test("encrypted is a valid base64 string", () => { const result = generateSecureValue("test-value"); expect(() => Buffer.from(result.encrypted, "base64")).not.toThrow(); const decoded = Buffer.from(result.encrypted, "base64"); expect(decoded.length).toBeGreaterThan(0); }); test("hash follows BLAKE2s format", () => { const result = generateSecureValue("test-value"); expect(result.hash).toMatch(/^BLAKE2s\$/); const parts = result.hash.split("$"); expect(parts[0]).toBe("BLAKE2s"); expect(parts[1].length).toBeGreaterThan(0); // hex hash }); test("different inputs produce different encrypted values", () => { const a = generateSecureValue("password-a"); const b = generateSecureValue("password-b"); expect(a.encrypted).not.toBe(b.encrypted); }); test("different inputs produce different hashes", () => { const a = generateSecureValue("password-a"); const b = generateSecureValue("password-b"); expect(a.hash).not.toBe(b.hash); }); test("encrypts empty string without error", () => { const result = generateSecureValue(""); expect(result.encrypted.length).toBeGreaterThan(0); expect(result.hash.length).toBeGreaterThan(0); }); test("encrypts special characters", () => { const result = generateSecureValue('p@$$w0rd!#%^&*(){}[]|\\:";<>?,./~`'); expect(result.encrypted.length).toBeGreaterThan(0); }); test("encrypts Unicode content", () => { const result = generateSecureValue("密码测试 🔐"); expect(result.encrypted.length).toBeGreaterThan(0); }); }); describe("readSecureValue", () => { test("decrypts a value encrypted by generateSecureValue", () => { const original = "my-secret-password"; const { encrypted } = generateSecureValue(original); const decrypted = readSecureValue(encrypted); expect(decrypted).toBe(original); }); test("decrypts empty string", () => { const { encrypted } = generateSecureValue(""); const decrypted = readSecureValue(encrypted); expect(decrypted).toBe(""); }); test("decrypts special characters", () => { const original = 'p@$$w0rd!#%^&*(){}[]|\\:";<>?,./~`'; const { encrypted } = generateSecureValue(original); const decrypted = readSecureValue(encrypted); expect(decrypted).toBe(original); }); test("decrypts Unicode content", () => { const original = "密码测试 🔐"; const { encrypted } = generateSecureValue(original); const decrypted = readSecureValue(encrypted); expect(decrypted).toBe(original); }); test("validates hash when provided and correct", () => { const original = "test-value"; const { encrypted, hash } = generateSecureValue(original); const decrypted = readSecureValue(encrypted, hash); expect(decrypted).toBe(original); }); test("throws when hash validation fails", () => { const original = "test-value"; const { encrypted } = generateSecureValue(original); const badHash = "BLAKE2s$0000000000000000000000000000000000000000000000000000000000000000$salt"; expect(() => readSecureValue(encrypted, badHash)).toThrow( "Secure value hash validation failed", ); }); test("throws GenericError on invalid encrypted content", () => { try { readSecureValue("not-valid-encrypted-data"); expect(true).toBe(false); // should not reach } catch (e: any) { expect(e.name).toBe("SecureValueDecryptionError"); expect(e.status).toBe(422); } }); test("throws GenericError with descriptive message on key mismatch", () => { try { readSecureValue("dGhpcyBpcyBub3QgZW5jcnlwdGVk"); // base64 but not RSA expect(true).toBe(false); } catch (e: any) { expect(e.message).toContain("Unable to decrypt secure value"); expect(e.cause).toContain("RSA key mismatch"); } }); test("skips hash validation when hash is not provided", () => { const original = "no-hash-check"; const { encrypted } = generateSecureValue(original); // Should not throw even though no hash is passed const decrypted = readSecureValue(encrypted); expect(decrypted).toBe(original); }); }); describe("generateSecureValue + readSecureValue round-trip", () => { test("round-trips various string types", () => { const testValues = [ "simple", "", "a".repeat(100), "line1\nline2\ttab", '{"json": true}', "null", "undefined", "0", " leading-trailing ", ]; for (const original of testValues) { const { encrypted, hash } = generateSecureValue(original); const decrypted = readSecureValue(encrypted, hash); expect(decrypted).toBe(original); } }); });