Files
TREK/server/tests/unit/app-config/app-url.test.ts
T
Maurice 724c88c201 style: format server and shared with prettier
A mechanical prettier --write over server/src, server/tests, server/scripts
and shared/src with the workspaces' .prettierrc; nothing but layout and
import order changes, and every file keeps the same set of imports. shared
is formatted throughout and its format baseline is empty. The server
baseline keeps the 35 files that Prettier would grow past their lint:size
limit (they need a split first, which is not a formatting change), and the
generated entity files stay as their generator writes them.
2026-10-10 21:23:37 +02:00

83 lines
3.0 KiB
TypeScript

import { getAppUrl, getMcpSafeUrl } from '../../../src/app-config/app-url';
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
// These tests PIN the exact behavior moved verbatim from
// services/notifications.ts (2026-07-28): the APP_URL → ALLOWED_ORIGINS →
// localhost fallback chain, the strip-ALL-trailing-slashes quirk, the
// invalid-URL silent fallthrough, and getMcpSafeUrl's HTTPS/localhost issuer
// sanitization. If one fails after an edit, the edit changed runtime
// behavior — fix the edit, not the test (parity is law).
const KEYS = ['APP_URL', 'ALLOWED_ORIGINS', 'PORT'] as const;
const saved: Record<string, string | undefined> = {};
beforeEach(() => {
for (const k of KEYS) {
saved[k] = process.env[k];
delete process.env[k];
}
});
afterEach(() => {
for (const k of KEYS) {
if (saved[k] === undefined) delete process.env[k];
else process.env[k] = saved[k];
}
});
describe('getAppUrl', () => {
it('returns APP_URL with ALL trailing slashes stripped', () => {
process.env.APP_URL = 'https://trek.example.com///';
expect(getAppUrl()).toBe('https://trek.example.com');
});
it('falls through an invalid APP_URL to the first ALLOWED_ORIGINS entry', () => {
process.env.APP_URL = 'not a url';
process.env.ALLOWED_ORIGINS = 'https://a.example/, https://b.example';
expect(getAppUrl()).toBe('https://a.example');
});
it('uses the first ALLOWED_ORIGINS entry (trimmed, slashes stripped) when APP_URL is unset', () => {
process.env.ALLOWED_ORIGINS = ' https://origin.example// ,https://other.example';
expect(getAppUrl()).toBe('https://origin.example');
});
it('falls back to http://localhost:{PORT} when neither is usable', () => {
process.env.ALLOWED_ORIGINS = 'not-a-url';
process.env.PORT = '4123';
expect(getAppUrl()).toBe('http://localhost:4123');
});
it('defaults to port 3001 with no env at all', () => {
expect(getAppUrl()).toBe('http://localhost:3001');
});
it('reads the env live — no caching across calls', () => {
process.env.APP_URL = 'https://first.example';
expect(getAppUrl()).toBe('https://first.example');
process.env.APP_URL = 'https://second.example';
expect(getAppUrl()).toBe('https://second.example');
});
});
describe('getMcpSafeUrl', () => {
it('passes through HTTPS URLs', () => {
process.env.APP_URL = 'https://trek.example.com';
expect(getMcpSafeUrl()).toBe('https://trek.example.com');
});
it('passes through http://localhost and http://127.0.0.1', () => {
process.env.APP_URL = 'http://localhost:8080';
expect(getMcpSafeUrl()).toBe('http://localhost:8080');
process.env.APP_URL = 'http://127.0.0.1:8080';
expect(getMcpSafeUrl()).toBe('http://127.0.0.1:8080');
});
it('sanitizes non-HTTPS, non-localhost URLs to http://localhost:{PORT}', () => {
process.env.APP_URL = 'http://trek.internal.lan';
process.env.PORT = '3005';
expect(getMcpSafeUrl()).toBe('http://localhost:3005');
});
});