encryption.test.ts•7.17 kB
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { CredentialEncryption } from './encryption.js';
describe('CredentialEncryption', () => {
let originalEnv: NodeJS.ProcessEnv;
beforeEach(() => {
originalEnv = process.env;
process.env = { ...originalEnv };
vi.resetModules();
});
afterEach(() => {
process.env = originalEnv;
});
describe('when MASTER_ENCRYPTION_KEY is not set', () => {
beforeEach(() => {
delete process.env.MASTER_ENCRYPTION_KEY;
});
it('should return credentials unchanged', () => {
const encryption = new CredentialEncryption();
const credentials = {
apiKey: 'sk_test_123',
secret: 'my-secret-value'
};
const encrypted = encryption.encrypt(credentials);
expect(encrypted).toEqual(credentials);
const decrypted = encryption.decrypt(encrypted);
expect(decrypted).toEqual(credentials);
});
it('should handle null/undefined credentials', () => {
const encryption = new CredentialEncryption();
expect(encryption.encrypt(null)).toBeNull();
expect(encryption.encrypt(undefined)).toBeUndefined();
expect(encryption.decrypt(null)).toBeNull();
expect(encryption.decrypt(undefined)).toBeUndefined();
});
});
describe('when MASTER_ENCRYPTION_KEY is set', () => {
beforeEach(() => {
process.env.MASTER_ENCRYPTION_KEY = 'test-master-key-12345';
});
it('should encrypt and decrypt credentials', () => {
const encryption = new CredentialEncryption();
const credentials = {
apiKey: 'sk_test_123',
secret: 'my-secret-value',
token: 'bearer-token-xyz'
};
const encrypted = encryption.encrypt(credentials);
// Verify values are encrypted
expect(encrypted.apiKey).not.toEqual(credentials.apiKey);
expect(encrypted.apiKey).toMatch(/^enc:/);
expect(encrypted.secret).not.toEqual(credentials.secret);
expect(encrypted.secret).toMatch(/^enc:/);
expect(encrypted.token).not.toEqual(credentials.token);
expect(encrypted.token).toMatch(/^enc:/);
// Verify format is enc:iv:encryptedData
const parts = encrypted.apiKey.split(':');
expect(parts).toHaveLength(3);
expect(parts[0]).toBe('enc');
expect(parts[1]).toHaveLength(32); // 16 bytes as hex = 32 chars
// Verify decryption works
const decrypted = encryption.decrypt(encrypted);
expect(decrypted).toEqual(credentials);
});
it('should handle empty values in credentials', () => {
const encryption = new CredentialEncryption();
const credentials = {
apiKey: 'sk_test_123',
emptyValue: '',
nullValue: null as any,
undefinedValue: undefined as any
};
const encrypted = encryption.encrypt(credentials);
expect(encrypted.apiKey).toMatch(/^enc:/);
expect(encrypted.emptyValue).toBe('');
expect(encrypted.nullValue).toBeNull();
expect(encrypted.undefinedValue).toBeUndefined();
const decrypted = encryption.decrypt(encrypted);
expect(decrypted.apiKey).toBe('sk_test_123');
expect(decrypted.emptyValue).toBe('');
});
it('should handle mixed encrypted and plain values', () => {
const encryption = new CredentialEncryption();
// Test that plain values pass through unchanged
const plainCredentials = {
plain: 'plain-text-value',
alsoPlain: 'another-plain-value'
};
const result = encryption.decrypt(plainCredentials);
expect(result.plain).toBe('plain-text-value');
expect(result.alsoPlain).toBe('another-plain-value');
// Test that invalid encrypted format throws
const invalidCredentials = {
apiKey: 'enc:invalidformat' // Missing third part
};
expect(() => encryption.decrypt(invalidCredentials)).toThrow('Failed to decrypt credentials');
});
it('should use different IVs for same value', () => {
const encryption = new CredentialEncryption();
const credentials1 = { apiKey: 'same-value' };
const credentials2 = { apiKey: 'same-value' };
const encrypted1 = encryption.encrypt(credentials1);
const encrypted2 = encryption.encrypt(credentials2);
// Same plaintext should produce different ciphertext due to random IV
expect(encrypted1.apiKey).not.toEqual(encrypted2.apiKey);
// Extract IVs and verify they're different
const iv1 = encrypted1.apiKey.split(':')[1];
const iv2 = encrypted2.apiKey.split(':')[1];
expect(iv1).not.toEqual(iv2);
// But both should decrypt to the same value
expect(encryption.decrypt(encrypted1)).toEqual(credentials1);
expect(encryption.decrypt(encrypted2)).toEqual(credentials2);
});
it('should throw error on invalid encrypted data', () => {
const encryption = new CredentialEncryption();
const invalidCredentials = {
apiKey: 'enc:invalidformat'
};
expect(() => encryption.decrypt(invalidCredentials)).toThrow('Failed to decrypt credentials');
});
it('should fail to decrypt with different master key', () => {
// Encrypt with one key
process.env.MASTER_ENCRYPTION_KEY = 'key-1';
const encryption1 = new CredentialEncryption();
const credentials = { apiKey: 'sk_test_123' };
const encrypted = encryption1.encrypt(credentials);
// Try to decrypt with different key
process.env.MASTER_ENCRYPTION_KEY = 'key-2';
const encryption2 = new CredentialEncryption();
expect(() => encryption2.decrypt(encrypted)).toThrow('Failed to decrypt credentials');
});
});
describe('isEnabled', () => {
it('should return false when no master key', () => {
delete process.env.MASTER_ENCRYPTION_KEY;
const encryption = new CredentialEncryption();
expect(encryption.isEnabled()).toBe(false);
});
it('should return true when master key is set', () => {
process.env.MASTER_ENCRYPTION_KEY = 'test-key';
const encryption = new CredentialEncryption();
expect(encryption.isEnabled()).toBe(true);
});
});
});