39948-vm/backend/tests/helpers.test.ts
2026-07-03 15:59:42 +02:00

110 lines
3.1 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { assertRouteIdMatchesBody } from '../src/helpers.ts';
import { CircuitBreaker, CircuitBreakerOpenError } from '../src/utils/circuit-breaker.ts';
import { envSchema } from '../src/utils/env-validation.ts';
import { normalizeLoggedError } from '../src/utils/logger.ts';
void test('assertRouteIdMatchesBody allows matching top-level body id', () => {
assert.doesNotThrow(() =>
assertRouteIdMatchesBody({
params: { id: 'route-id' },
body: { id: 'route-id' },
}),
);
});
void test('assertRouteIdMatchesBody allows matching data body id', () => {
assert.doesNotThrow(() =>
assertRouteIdMatchesBody({
params: { id: 'route-id' },
body: { data: { id: 'route-id' } },
}),
);
});
void test('assertRouteIdMatchesBody rejects mismatched body id', () => {
assert.throws(
() =>
assertRouteIdMatchesBody({
params: { id: 'route-id' },
body: { data: { id: 'body-id' } },
}),
/Request body id does not match route id/,
);
});
void test('normalizeLoggedError preserves Error instances', () => {
const error = new Error('Original failure');
assert.equal(normalizeLoggedError(error), error);
});
void test('normalizeLoggedError wraps non-Error values', () => {
const reason = { code: 'E_UNKNOWN' };
const error = normalizeLoggedError(reason);
assert.equal(error.message, 'Non-Error value thrown or rejected');
assert.equal(error.cause, reason);
});
void test('normalizeLoggedError uses string reasons as messages', () => {
const error = normalizeLoggedError('plain rejection');
assert.equal(error.message, 'plain rejection');
});
void test('envSchema normalizes file storage provider override', () => {
const result = envSchema.validate({ FILE_STORAGE_PROVIDER: ' S3 ' });
const providerDescriptor =
typeof result.value === 'object' && result.value !== null
? Object.getOwnPropertyDescriptor(result.value, 'FILE_STORAGE_PROVIDER')
: undefined;
assert.ifError(result.error);
assert.equal(providerDescriptor?.value, 's3');
});
void test('CircuitBreaker can ignore non-breaker failures', async () => {
const breaker = new CircuitBreaker({
name: 'test-breaker',
failureThreshold: 1,
shouldRecordFailure: () => false,
});
await assert.rejects(
() =>
breaker.execute('ignored-failure', () =>
Promise.reject(new Error('Validation failure')),
),
/Validation failure/,
);
assert.equal(breaker.getState(), 'closed');
await assert.doesNotReject(() =>
breaker.execute('next-call', () => Promise.resolve('ok')),
);
});
void test('CircuitBreaker opens after recorded failures', async () => {
const breaker = new CircuitBreaker({
name: 'test-breaker',
failureThreshold: 1,
});
await assert.rejects(
() =>
breaker.execute('recorded-failure', () =>
Promise.reject(new Error('Transient failure')),
),
/Transient failure/,
);
await assert.rejects(
() => breaker.execute('blocked-call', () => Promise.resolve('ok')),
CircuitBreakerOpenError,
);
});