Files
optima/api/tests/unit/secureValues.test.ts
T

161 lines
5.4 KiB
TypeScript

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);
}
});
});