Files
caddy-proxy-manager/tests/unit/instance-sync-env.test.ts
akanealw 99819b70ff
Some checks failed
Build and Push Docker Images (Trusted) / build-and-push (., docker/caddy/Dockerfile, caddy) (push) Has been cancelled
Build and Push Docker Images (Trusted) / build-and-push (., docker/l4-port-manager/Dockerfile, l4-port-manager) (push) Has been cancelled
Build and Push Docker Images (Trusted) / build-and-push (., docker/web/Dockerfile, web) (push) Has been cancelled
Tests / test (push) Has been cancelled
added caddy-proxy-manager for testing
2026-04-21 22:49:08 +00:00

249 lines
7.6 KiB
TypeScript
Executable File

/**
* Unit tests for the pure environment-variable-reading functions
* exported by src/lib/instance-sync.ts.
*
* These functions have no DB or network dependency — they only read
* from process.env and do simple parsing/validation.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import {
getEnvSlaveInstances,
getSyncIntervalMs,
isHttpSyncAllowed,
isInstanceModeFromEnv,
isSyncTokenFromEnv,
} from '../../src/lib/instance-sync';
const KEYS = [
'INSTANCE_SLAVES',
'INSTANCE_SYNC_INTERVAL',
'INSTANCE_SYNC_ALLOW_HTTP',
'INSTANCE_MODE',
'INSTANCE_SYNC_TOKEN',
] as const;
beforeEach(() => {
for (const k of KEYS) delete process.env[k];
});
afterEach(() => {
for (const k of KEYS) delete process.env[k];
});
// ---------------------------------------------------------------------------
// getEnvSlaveInstances
// ---------------------------------------------------------------------------
describe('getEnvSlaveInstances', () => {
it('returns empty array when env var is not set', () => {
expect(getEnvSlaveInstances()).toEqual([]);
});
it('returns empty array for empty string', () => {
process.env.INSTANCE_SLAVES = '';
expect(getEnvSlaveInstances()).toEqual([]);
});
it('parses a valid single slave entry', () => {
process.env.INSTANCE_SLAVES = JSON.stringify([
{ name: 'slave1', url: 'https://slave.example.com', token: 'secret123' },
]);
const result = getEnvSlaveInstances();
expect(result).toHaveLength(1);
expect(result[0]).toEqual({
name: 'slave1',
url: 'https://slave.example.com',
token: 'secret123',
});
});
it('parses multiple slave entries', () => {
process.env.INSTANCE_SLAVES = JSON.stringify([
{ name: 'slave1', url: 'https://slave1.example.com', token: 'tok1' },
{ name: 'slave2', url: 'https://slave2.example.com', token: 'tok2' },
]);
expect(getEnvSlaveInstances()).toHaveLength(2);
});
it('returns empty array for non-array JSON', () => {
process.env.INSTANCE_SLAVES = '{"name":"slave1"}'; // object, not array
expect(getEnvSlaveInstances()).toEqual([]);
});
it('returns empty array for malformed JSON', () => {
process.env.INSTANCE_SLAVES = '{bad json';
expect(getEnvSlaveInstances()).toEqual([]);
});
it('filters out entries missing required fields', () => {
process.env.INSTANCE_SLAVES = JSON.stringify([
{ name: 'slave1', url: 'https://slave1.example.com', token: 'tok1' }, // valid
{ name: 'slave2', url: 'https://slave2.example.com' }, // missing token
{ name: 'slave3', token: 'tok3' }, // missing url
{ url: 'https://slave4.example.com', token: 'tok4' }, // missing name
]);
const result = getEnvSlaveInstances();
expect(result).toHaveLength(1);
expect(result[0].name).toBe('slave1');
});
it('filters out entries with empty string fields', () => {
process.env.INSTANCE_SLAVES = JSON.stringify([
{ name: '', url: 'https://slave.example.com', token: 'tok' }, // empty name
]);
expect(getEnvSlaveInstances()).toEqual([]);
});
it('filters out non-object entries', () => {
process.env.INSTANCE_SLAVES = JSON.stringify([
42,
null,
'string',
{ name: 'ok', url: 'https://ok.com', token: 'tok' },
]);
const result = getEnvSlaveInstances();
expect(result).toHaveLength(1);
expect(result[0].name).toBe('ok');
});
});
// ---------------------------------------------------------------------------
// getSyncIntervalMs
// ---------------------------------------------------------------------------
describe('getSyncIntervalMs', () => {
it('returns 0 when env var is not set (disabled)', () => {
expect(getSyncIntervalMs()).toBe(0);
});
it('converts seconds to milliseconds', () => {
process.env.INSTANCE_SYNC_INTERVAL = '60';
expect(getSyncIntervalMs()).toBe(60_000);
});
it('enforces minimum of 30 seconds', () => {
process.env.INSTANCE_SYNC_INTERVAL = '10';
expect(getSyncIntervalMs()).toBe(30_000); // clamped to 30s
});
it('exactly 30 seconds is allowed', () => {
process.env.INSTANCE_SYNC_INTERVAL = '30';
expect(getSyncIntervalMs()).toBe(30_000);
});
it('returns 0 for "0"', () => {
process.env.INSTANCE_SYNC_INTERVAL = '0';
expect(getSyncIntervalMs()).toBe(0);
});
it('returns 0 for negative value', () => {
process.env.INSTANCE_SYNC_INTERVAL = '-60';
expect(getSyncIntervalMs()).toBe(0);
});
it('returns 0 for non-numeric string', () => {
process.env.INSTANCE_SYNC_INTERVAL = 'abc';
expect(getSyncIntervalMs()).toBe(0);
});
it('handles large interval correctly', () => {
process.env.INSTANCE_SYNC_INTERVAL = '3600'; // 1 hour
expect(getSyncIntervalMs()).toBe(3_600_000);
});
});
// ---------------------------------------------------------------------------
// isHttpSyncAllowed
// ---------------------------------------------------------------------------
describe('isHttpSyncAllowed', () => {
it('returns false when env var is not set', () => {
expect(isHttpSyncAllowed()).toBe(false);
});
it('returns true for "true"', () => {
process.env.INSTANCE_SYNC_ALLOW_HTTP = 'true';
expect(isHttpSyncAllowed()).toBe(true);
});
it('returns true for "1"', () => {
process.env.INSTANCE_SYNC_ALLOW_HTTP = '1';
expect(isHttpSyncAllowed()).toBe(true);
});
it('returns false for "false"', () => {
process.env.INSTANCE_SYNC_ALLOW_HTTP = 'false';
expect(isHttpSyncAllowed()).toBe(false);
});
it('returns false for "yes"', () => {
process.env.INSTANCE_SYNC_ALLOW_HTTP = 'yes';
expect(isHttpSyncAllowed()).toBe(false);
});
it('returns false for empty string', () => {
process.env.INSTANCE_SYNC_ALLOW_HTTP = '';
expect(isHttpSyncAllowed()).toBe(false);
});
});
// ---------------------------------------------------------------------------
// isInstanceModeFromEnv
// ---------------------------------------------------------------------------
describe('isInstanceModeFromEnv', () => {
it('returns false when env var is not set', () => {
expect(isInstanceModeFromEnv()).toBe(false);
});
it('returns true for "master"', () => {
process.env.INSTANCE_MODE = 'master';
expect(isInstanceModeFromEnv()).toBe(true);
});
it('returns true for "slave"', () => {
process.env.INSTANCE_MODE = 'slave';
expect(isInstanceModeFromEnv()).toBe(true);
});
it('returns true for "standalone"', () => {
process.env.INSTANCE_MODE = 'standalone';
expect(isInstanceModeFromEnv()).toBe(true);
});
it('returns false for invalid mode', () => {
process.env.INSTANCE_MODE = 'invalid';
expect(isInstanceModeFromEnv()).toBe(false);
});
it('returns false for empty string', () => {
process.env.INSTANCE_MODE = '';
expect(isInstanceModeFromEnv()).toBe(false);
});
});
// ---------------------------------------------------------------------------
// isSyncTokenFromEnv
// ---------------------------------------------------------------------------
describe('isSyncTokenFromEnv', () => {
it('returns false when env var is not set', () => {
expect(isSyncTokenFromEnv()).toBe(false);
});
it('returns true when token is set to a non-empty string', () => {
process.env.INSTANCE_SYNC_TOKEN = 'my-secret-token';
expect(isSyncTokenFromEnv()).toBe(true);
});
it('returns false for empty string token', () => {
process.env.INSTANCE_SYNC_TOKEN = '';
expect(isSyncTokenFromEnv()).toBe(false);
});
it('returns true for any non-empty value', () => {
process.env.INSTANCE_SYNC_TOKEN = ' '; // whitespace counts as non-empty
expect(isSyncTokenFromEnv()).toBe(true);
});
});