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
249 lines
7.6 KiB
TypeScript
Executable File
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);
|
|
});
|
|
});
|