Files
pulse/server/tests/config.test.js
T

451 lines
17 KiB
JavaScript

const { loadConfiguration, ConfigurationError } = require('../configLoader');
// Mock dotenv
jest.mock('dotenv', () => ({
config: jest.fn(),
}));
const dotenv = require('dotenv'); // require after mock
// Helper function to temporarily set environment variables for a test
const setEnvVars = (vars) => {
const originalEnv = { ...process.env }; // Store original env
Object.keys(vars).forEach(key => {
process.env[key] = vars[key];
});
return originalEnv; // Return original env for restoration
};
// Helper function to restore environment variables
const restoreEnvVars = (originalEnv) => {
// Clear potentially set test variables first
Object.keys(process.env).forEach(key => {
if (!(key in originalEnv)) {
delete process.env[key];
}
});
// Restore original values
Object.keys(originalEnv).forEach(key => {
process.env[key] = originalEnv[key];
});
};
// Set NODE_ENV to test *before* describing the suite
process.env.NODE_ENV = 'test';
// Mock console
let consoleWarnSpy; // Declare spies outside beforeEach/afterEach
let consoleLogSpy;
describe('Configuration Loading (loadConfiguration)', () => {
let originalEnv;
beforeEach(() => {
// Store original environment
originalEnv = { ...process.env };
// --- More robust clearing of process.env ---
// Get all keys BEFORE modifying
const currentEnvKeys = Object.keys(process.env);
// Delete all keys
currentEnvKeys.forEach(key => delete process.env[key]);
// --- End robust clearing ---
// Restore NODE_ENV as it's crucial for the logic
process.env.NODE_ENV = 'test';
// Assign spies in beforeEach
consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
});
afterEach(() => {
// --- Restore original environment more carefully ---
// Clear any keys potentially added during the test
const currentEnvKeys = Object.keys(process.env);
currentEnvKeys.forEach(key => delete process.env[key]);
// Restore the original keys and values
Object.keys(originalEnv).forEach(key => {
process.env[key] = originalEnv[key];
});
// --- End restore ---
// Restore specific spies
consoleWarnSpy.mockRestore();
consoleLogSpy.mockRestore();
});
// Test Case 1: Minimal Valid PVE Config
test('should load minimal PVE config successfully', () => {
setEnvVars({
PROXMOX_HOST: 'pve.example.com',
PROXMOX_TOKEN_ID: 'user@pam!pve',
PROXMOX_TOKEN_SECRET: 'secretpve',
});
// Expect no error to be thrown for valid config
let loadedConfig;
expect(() => {
loadedConfig = loadConfiguration();
}).not.toThrow();
// Check the returned structure
expect(loadedConfig).toBeDefined();
expect(loadedConfig.endpoints).toHaveLength(1); // Check endpoints array
expect(loadedConfig.pbsConfigs).toHaveLength(0); // Expect no PBS configs
// Check the primary PVE endpoint details within the endpoints array
const primaryEndpoint = loadedConfig.endpoints[0];
expect(primaryEndpoint.id).toBe('primary');
expect(primaryEndpoint.host).toBe('pve.example.com');
expect(primaryEndpoint.tokenId).toBe('user@pam!pve');
expect(primaryEndpoint.tokenSecret).toBe('secretpve');
});
// Test Case 2: Missing Primary Proxmox Variables
test('should throw ConfigurationError if primary Proxmox variables are missing', () => {
setEnvVars({
PROXMOX_HOST: '192.168.1.100',
// Missing TOKEN_ID and TOKEN_SECRET
});
expect(() => loadConfiguration()).toThrow(ConfigurationError);
expect(() => loadConfiguration()).toThrow(/Missing required environment variables: PROXMOX_TOKEN_ID, PROXMOX_TOKEN_SECRET/);
});
// Test Case 3: Placeholder Primary Proxmox Variables
test('should warn and set flag if primary Proxmox variables contain placeholders', () => {
const envSetup = {
PROXMOX_HOST: 'your-proxmox-ip-or-hostname',
PROXMOX_TOKEN_ID: 'user@pam!token', // A placeholder not exactly in the list
PROXMOX_TOKEN_SECRET: 'secret-uuid', // Another placeholder not exactly in the list
};
setEnvVars(envSetup);
let config;
// Expect no error to be thrown, but placeholders to be detected
expect(() => {
config = loadConfiguration();
}).not.toThrow();
expect(consoleWarnSpy).toHaveBeenCalledWith(
expect.stringContaining('WARN: Primary Proxmox environment variables seem to contain placeholder values: PROXMOX_HOST, PROXMOX_TOKEN_ID, PROXMOX_TOKEN_SECRET')
);
expect(config.isConfigPlaceholder).toBe(true);
});
// Test Case 4: Valid Primary + Additional Proxmox Endpoints
test('should load successfully with additional valid Proxmox endpoints', () => {
setEnvVars({
PROXMOX_HOST: 'pve1.example.com',
PROXMOX_TOKEN_ID: 'user@pam!token1',
PROXMOX_TOKEN_SECRET: 'secret1',
PROXMOX_NODE_NAME: 'PVE Node 1', // Custom name
PROXMOX_PORT: '8007', // Custom port
PROXMOX_ALLOW_SELF_SIGNED_CERTS: 'true', // Explicitly true
PROXMOX_HOST_2: 'pve2.example.com',
PROXMOX_TOKEN_ID_2: 'user@pam!token2',
PROXMOX_TOKEN_SECRET_2: 'secret2',
PROXMOX_ENABLED_2: 'false', // Disabled endpoint
PROXMOX_HOST_3: 'pve3.example.com',
PROXMOX_TOKEN_ID_3: 'user@pam!token3',
PROXMOX_TOKEN_SECRET_3: 'secret3',
PROXMOX_NODE_NAME_3: 'PVE Node 3', // Custom name
PROXMOX_PORT_3: '8008',
PROXMOX_ALLOW_SELF_SIGNED_CERTS_3: 'false', // Explicitly false
});
const config = loadConfiguration();
expect(config.endpoints).toHaveLength(3);
// Check primary
expect(config.endpoints[0].id).toBe('primary');
expect(config.endpoints[0].name).toBe('PVE Node 1');
expect(config.endpoints[0].host).toBe('pve1.example.com');
expect(config.endpoints[0].port).toBe('8007');
expect(config.endpoints[0].enabled).toBe(true);
expect(config.endpoints[0].allowSelfSignedCerts).toBe(true);
// Check second (disabled)
expect(config.endpoints[1].id).toBe('endpoint_2');
expect(config.endpoints[1].name).toBe('pve2.example.com'); // Defaults to host
expect(config.endpoints[1].host).toBe('pve2.example.com');
expect(config.endpoints[1].port).toBe('8006'); // Default port
expect(config.endpoints[1].enabled).toBe(false);
expect(config.endpoints[1].allowSelfSignedCerts).toBe(true); // Default
// Check third
expect(config.endpoints[2].id).toBe('endpoint_3');
expect(config.endpoints[2].name).toBe('PVE Node 3');
expect(config.endpoints[2].host).toBe('pve3.example.com');
expect(config.endpoints[2].port).toBe('8008');
expect(config.endpoints[2].enabled).toBe(true); // Default
expect(config.endpoints[2].allowSelfSignedCerts).toBe(false);
expect(config.pbsConfigs).toHaveLength(0);
});
// Test Case 5: Incomplete Additional Proxmox Endpoint
test('should skip additional Proxmox endpoint if token details are missing', () => {
setEnvVars({
PROXMOX_HOST: 'pve1.example.com',
PROXMOX_TOKEN_ID: 'user@pam!token1',
PROXMOX_TOKEN_SECRET: 'secret1',
PROXMOX_HOST_2: 'pve2.example.com', // Missing token ID/secret for #2
});
const config = loadConfiguration();
expect(config.endpoints).toHaveLength(1);
expect(config.endpoints[0].id).toBe('primary');
});
// Test Case 6: Placeholder Additional Proxmox Endpoint
test('should skip additional Proxmox endpoint if details contain placeholders', () => {
setEnvVars({
PROXMOX_HOST: 'pve1.example.com',
PROXMOX_TOKEN_ID: 'user@pam!token1',
PROXMOX_TOKEN_SECRET: 'secret1',
PROXMOX_HOST_2: 'your-proxmox-ip-or-hostname', // Placeholder host
PROXMOX_TOKEN_ID_2: 'user@pam!token2',
PROXMOX_TOKEN_SECRET_2: 'secret2',
});
const config = loadConfiguration();
expect(config.endpoints).toHaveLength(1); // Only primary should load
expect(config.endpoints[0].id).toBe('primary');
});
// Test Case 7: Valid Primary PBS Config
test('should load successfully with a valid primary PBS config', () => {
setEnvVars({
// Minimal valid PVE
PROXMOX_HOST: 'pve.example.com',
PROXMOX_TOKEN_ID: 'user@pam!pve',
PROXMOX_TOKEN_SECRET: 'secretpve',
// Valid PBS
PBS_HOST: 'https://pbs.example.com:8007', // Full URL
PBS_TOKEN_ID: 'user@pbs!token',
PBS_TOKEN_SECRET: 'secretpbs',
PBS_NODE_NAME: 'PBS Backup Server',
PBS_ALLOW_SELF_SIGNED_CERTS: 'false',
});
const config = loadConfiguration();
expect(config.endpoints).toHaveLength(1);
expect(config.pbsConfigs).toHaveLength(1);
const pbs = config.pbsConfigs[0];
expect(pbs.id).toBe('pbs_primary_token');
expect(pbs.name).toBe('PBS Backup Server');
expect(pbs.host).toBe('https://pbs.example.com:8007');
expect(pbs.port).toBe('8007'); // Port from env var
expect(pbs.tokenId).toBe('user@pbs!token');
expect(pbs.tokenSecret).toBe('secretpbs');
expect(pbs.authMethod).toBe('token');
expect(pbs.allowSelfSignedCerts).toBe(false);
expect(pbs.enabled).toBe(true);
});
test('should not add primary PBS config if host is set but tokens are missing', () => {
setEnvVars({
PROXMOX_HOST: '192.168.1.100',
PROXMOX_TOKEN_ID: 'user@pam!pve',
PROXMOX_TOKEN_SECRET: 'secretpve',
PBS_HOST: 'pbs.example.com',
// Missing TOKEN_ID and TOKEN_SECRET for PBS
});
let config;
expect(() => {
config = loadConfiguration();
}).not.toThrow();
expect(config.endpoints).toHaveLength(1);
expect(config.pbsConfigs).toHaveLength(0); // PBS should NOT load
expect(consoleWarnSpy).toHaveBeenCalledWith(
expect.stringContaining('WARN: Partial PBS configuration found for PBS_HOST. Please set (PBS_TOKEN_ID + PBS_TOKEN_SECRET)')
);
expect(consoleWarnSpy).toHaveBeenCalledTimes(1); // Only one warning expected from this test
});
// Test Case 8: Valid Primary + Additional PBS Configs
test('should load successfully with additional valid PBS configs', () => {
setEnvVars({
// PVE
PROXMOX_HOST: 'pve.example.com',
PROXMOX_TOKEN_ID: 'user@pam!pve',
PROXMOX_TOKEN_SECRET: 'secretpve',
// PBS 1 (Primary)
PBS_HOST: 'pbs1.example.com', // No protocol/port
PBS_TOKEN_ID: 'user@pbs!token1',
PBS_TOKEN_SECRET: 'secretpbs1',
// PBS 2
PBS_HOST_2: 'https://pbs2.example.com:8008',
PBS_TOKEN_ID_2: 'user@pbs!token2',
PBS_TOKEN_SECRET_2: 'secretpbs2',
PBS_NODE_NAME_2: 'PBS Server 2',
PBS_PORT_2: '9000', // Custom port
// PBS 3 (Placeholder - should skip)
PBS_HOST_3: 'pbs3.example.com',
PBS_TOKEN_ID_3: 'your-api-token-id@pam!your-token-name',
PBS_TOKEN_SECRET_3: 'secretpbs3',
// PBS 4 (Missing Token Secret - should skip)
PBS_HOST_4: 'pbs4.example.com',
PBS_TOKEN_ID_4: 'user@pbs!token4',
});
const config = loadConfiguration();
expect(config.endpoints).toHaveLength(1);
expect(config.pbsConfigs).toHaveLength(2);
// Check PBS 1 (Primary)
expect(config.pbsConfigs[0].id).toBe('pbs_primary_token');
expect(config.pbsConfigs[0].name).toBe('pbs1.example.com'); // Defaults to host
expect(config.pbsConfigs[0].host).toBe('pbs1.example.com');
expect(config.pbsConfigs[0].port).toBe('8007'); // Default port
expect(config.pbsConfigs[0].allowSelfSignedCerts).toBe(true); // Default
// Check PBS 2
expect(config.pbsConfigs[1].id).toBe('pbs_endpoint_2_token');
expect(config.pbsConfigs[1].name).toBe('PBS Server 2');
expect(config.pbsConfigs[1].host).toBe('https://pbs2.example.com:8008');
expect(config.pbsConfigs[1].port).toBe('9000'); // Custom port
expect(config.pbsConfigs[1].allowSelfSignedCerts).toBe(true); // Default
// PBS 3 and 4 should have been skipped
});
// Test Case 9: Incomplete Additional PBS Endpoint (NEW TEST)
test('should skip additional PBS endpoint if token details are missing but host is present', () => {
setEnvVars({
PROXMOX_HOST: 'pve.example.com',
PROXMOX_TOKEN_ID: 'user@pam!pve',
PROXMOX_TOKEN_SECRET: 'secretpve',
// Valid Primary PBS
PBS_HOST: 'pbs1.example.com',
PBS_TOKEN_ID: 'user@pbs!token1',
PBS_TOKEN_SECRET: 'secretpbs1',
// Additional PBS host, missing tokens
PBS_HOST_2: 'pbs2.example.com',
// PBS_TOKEN_ID_2: 'user@pbs!token2', // Missing
// PBS_TOKEN_SECRET_2: 'secretpbs2', // Missing
// Valid third PBS
PBS_HOST_3: 'pbs3.example.com',
PBS_TOKEN_ID_3: 'user@pbs!token3',
PBS_TOKEN_SECRET_3: 'secretpbs3',
});
const config = loadConfiguration();
expect(config.endpoints).toHaveLength(1);
expect(config.pbsConfigs).toHaveLength(2); // Should load primary (PBS1) and PBS3
expect(config.pbsConfigs.map(p => p.host)).toEqual(['pbs1.example.com', 'pbs3.example.com']);
// Check that the warning for the partial config _2 was logged
expect(consoleWarnSpy).toHaveBeenCalledWith(
expect.stringContaining('WARN: Partial PBS configuration found for PBS_HOST_2. Please set (PBS_TOKEN_ID_2 + PBS_TOKEN_SECRET_2)')
);
// Verify the config for PBS_HOST_2 was not added
expect(config.pbsConfigs.find(p => p.host === 'pbs2.example.com')).toBeUndefined();
});
// Test Case 10: No Enabled Endpoints
test('should throw ConfigurationError if no enabled PVE or PBS endpoints are configured', () => {
setEnvVars({
// Valid PVE, but disabled
PROXMOX_HOST: 'pve.example.com',
PROXMOX_TOKEN_ID: 'user@pam!pve',
PROXMOX_TOKEN_SECRET: 'secretpve',
PROXMOX_ENABLED: 'false',
// Valid PBS details, but only HOST is present, no tokens
PBS_HOST: 'pbs.example.com'
});
// Expect the final check in loadConfiguration to throw
expect(() => loadConfiguration()).toThrow(ConfigurationError);
expect(() => loadConfiguration()).toThrow(/No enabled Proxmox VE or PBS endpoints could be configured/);
});
// New Test Case for dotenv loading
test('should call dotenv.config() when NODE_ENV is not \'test\'', () => {
const originalNodeEnv = process.env.NODE_ENV;
process.env.NODE_ENV = 'development'; // Set to non-test environment
// Minimal valid PVE config to allow loadConfiguration to proceed far enough
setEnvVars({
PROXMOX_HOST: 'pve.example.com',
PROXMOX_TOKEN_ID: 'user@pam!pve',
PROXMOX_TOKEN_SECRET: 'secretpve',
});
loadConfiguration();
expect(dotenv.config).toHaveBeenCalled();
// Restore original NODE_ENV and clear mocks for other tests
process.env.NODE_ENV = originalNodeEnv;
dotenv.config.mockClear(); // Clear the mock for other tests
});
// Test Case 11: Placeholder detection with PROXMOX_TOKEN_ID in env
test('should insert PROXMOX_TOKEN_ID in correct position when placeholders detected', () => {
setEnvVars({
PROXMOX_HOST: 'your-proxmox-ip-or-hostname',
PROXMOX_TOKEN_ID: 'user@pam!token',
PROXMOX_TOKEN_SECRET: 'your-api-token-uuid',
});
const config = loadConfiguration();
// Should detect placeholders - the actual implementation includes PROXMOX_TOKEN_ID when it's set
expect(consoleWarnSpy).toHaveBeenCalledWith(
expect.stringContaining('WARN: Primary Proxmox environment variables seem to contain placeholder values: PROXMOX_HOST, PROXMOX_TOKEN_ID')
);
expect(config.isConfigPlaceholder).toBe(true);
});
// Test Case 12: Placeholder detection - TOKEN_ID not in list but exists
test('should add PROXMOX_TOKEN_ID at end if not in placeholder list but exists', () => {
// Only secret is a placeholder, but TOKEN_ID exists and should be added
setEnvVars({
PROXMOX_HOST: 'pve.example.com',
PROXMOX_TOKEN_ID: 'user@pam!mytoken', // exists but not a placeholder
PROXMOX_TOKEN_SECRET: 'your-api-token-uuid', // placeholder
});
const config = loadConfiguration();
// Debug: Check if console.warn was called at all
expect(consoleWarnSpy).toHaveBeenCalledTimes(1);
// Should detect the secret placeholder and add TOKEN_ID
expect(consoleWarnSpy).toHaveBeenCalledWith(
expect.stringContaining('PROXMOX_TOKEN_SECRET')
);
expect(config.isConfigPlaceholder).toBe(true);
});
// Test Case 13: Test line 138 - Add TOKEN_ID when no PROXMOX_HOST in placeholderVars
test('should push PROXMOX_TOKEN_ID when PROXMOX_HOST not in placeholder list', () => {
// Only PROXMOX_PORT is placeholder (not PROXMOX_HOST)
setEnvVars({
PROXMOX_HOST: 'pve.example.com',
PROXMOX_TOKEN_ID: 'user@pam!token', // This IS identified as a placeholder
PROXMOX_TOKEN_SECRET: 'secret123',
PROXMOX_PORT: 'your-port' // This is a placeholder, but not checked in the primary warning
});
const config = loadConfiguration();
// Should detect a placeholder in PROXMOX_TOKEN_ID and warn about it.
// PROXMOX_PORT is not part of the primary placeholder check that generates this specific warning.
expect(consoleWarnSpy).toHaveBeenCalledWith(
expect.stringContaining('PROXMOX_TOKEN_ID')
);
expect(config.isConfigPlaceholder).toBe(true);
});
});