Files
TREK/server/tests/unit/app-config/config-templates.test.ts
T
Maurice 256f8c08e2 feat(server): ask the TREK services' Photon before Nominatim for search and reverse lookups
An installation that holds a key for the TREK services and has no
NOMINATIM_URL of its own now sends the OpenStreetMap search and the
reverse lookups to photon.liketrek.com first. The answers are read into
Nominatim's shape, so the place records stay the same, and every miss
(refused key, allowance spent, timeout, error, foreign body) falls back
to Nominatim, with a one minute pause after four misses in a row.

TREK_PHOTON_URL points at another copy, and the default of
TREK_SERVICES_HOSTS now lists photon.liketrek.com and
valhalla.liketrek.com beside places.liketrek.com.
2026-10-29 13:34:28 +01:00

188 lines
9.0 KiB
TypeScript

/**
* The shipped configuration templates (server/.env.example, docker-compose.yml,
* the Helm values and ConfigMap) are the first place an operator looks for a
* variable, and nothing else ties them to the code: a default raised in
* derive.ts stays wrong in the template until somebody notices, and a switch
* that only the wiki knows about is one the compose file never shows. These
* tests pin the pieces that drifted once.
*/
import { OVERPASS_TIMEOUT_DEFAULT_MS } from '../../../src/nest/maps/maps.helpers';
import fs from 'node:fs';
import path from 'node:path';
import { describe, it, expect } from 'vitest';
const REPO_ROOT = path.join(__dirname, '..', '..', '..', '..');
const envExample = fs.readFileSync(path.join(REPO_ROOT, 'server', '.env.example'), 'utf8');
const compose = fs.readFileSync(path.join(REPO_ROOT, 'docker-compose.yml'), 'utf8');
const helmValues = fs.readFileSync(path.join(REPO_ROOT, 'charts', 'trek', 'values.yaml'), 'utf8');
const helmConfigMap = fs.readFileSync(path.join(REPO_ROOT, 'charts', 'trek', 'templates', 'configmap.yaml'), 'utf8');
const helmSecret = fs.readFileSync(path.join(REPO_ROOT, 'charts', 'trek', 'templates', 'secret.yaml'), 'utf8');
const helmDeployment = fs.readFileSync(path.join(REPO_ROOT, 'charts', 'trek', 'templates', 'deployment.yaml'), 'utf8');
describe('OVERPASS_TIMEOUT_MS in the templates', () => {
it('.env.example shows the code default as both the example value and the stated default', () => {
const line = envExample.split(/\r?\n/).find((l) => l.includes('OVERPASS_TIMEOUT_MS='));
expect(line).toBeDefined();
expect(line).toContain(`OVERPASS_TIMEOUT_MS=${OVERPASS_TIMEOUT_DEFAULT_MS}`);
expect(line).toContain(`(default: ${OVERPASS_TIMEOUT_DEFAULT_MS})`);
});
it('the Helm values show the code default as both the example value and the stated default', () => {
expect(helmValues).toContain(`# OVERPASS_TIMEOUT_MS: "${OVERPASS_TIMEOUT_DEFAULT_MS}"`);
expect(helmValues).toContain(`Defaults to ${OVERPASS_TIMEOUT_DEFAULT_MS}.`);
});
});
describe('the Places and Amap switches in the templates', () => {
// The index is asked by default and the only way to stop that is this
// variable, so an operator has to be able to find it where they look.
const documented = [
'TREK_PLACES_ENABLED',
'TREK_PLACES_URL',
'GOOGLE_LIST_DATA_BASE',
'PLACES_API_KEY',
'AMAP_API_KEY',
'AMAP_API_SECRET',
'AMAP_API_BASE',
];
it('.env.example documents every one of them', () => {
for (const name of documented) {
expect(envExample).toContain(`# ${name}=`);
}
});
it('docker-compose.yml documents every one of them', () => {
for (const name of documented) {
expect(compose).toContain(`# - ${name}=`);
}
});
it('the Helm ConfigMap passes through the non-secret ones the values file documents', () => {
for (const name of ['TREK_PLACES_ENABLED', 'TREK_PLACES_URL', 'GOOGLE_LIST_DATA_BASE', 'AMAP_API_BASE']) {
expect(helmValues).toContain(`# ${name}:`);
expect(helmConfigMap).toContain(`${name}: {{ .Values.env.${name} | quote }}`);
}
});
it('the Helm ConfigMap emits TREK_PLACES_ENABLED on presence, since its one useful value is false', () => {
// A guard of the `if .Values.env.X` shape drops a boolean false, and with
// it the only setting anyone makes here.
expect(helmConfigMap).not.toContain('{{- if .Values.env.TREK_PLACES_ENABLED }}');
expect(helmConfigMap).toContain('{{- if not (kindIs "invalid" .Values.env.TREK_PLACES_ENABLED) }}');
});
it('the Helm chart carries the Google and Amap credentials in the Secret, never in the ConfigMap', () => {
// A ConfigMap is readable by anyone who can read the namespace; the key and
// its signing secret belong next to the other credentials, in both the
// plain and the generated branch of the Secret, and the pod reads them from
// there like it reads the Unsplash key.
for (const name of ['PLACES_API_KEY', 'AMAP_API_KEY', 'AMAP_API_SECRET']) {
expect(helmValues).toContain(` ${name}: ""`);
expect(helmSecret).toContain(`${name}: {{ .Values.secretEnv.${name} | b64enc | quote }}`);
expect(helmSecret).toContain(`${name}: {{ .Values.secretEnv.${name} | quote }}`);
expect(helmDeployment).toContain(`- name: ${name}\n valueFrom:\n secretKeyRef:`);
expect(helmDeployment).toContain(`key: ${name}\n optional: true`);
expect(helmConfigMap).not.toContain(name);
}
});
});
describe('the Web Push keys in the templates', () => {
// All optional, since TREK keeps a generated pair in its database; an
// operator who wants their own pair has to find the names somewhere.
const documented = ['VAPID_PUBLIC_KEY', 'VAPID_PRIVATE_KEY', 'VAPID_SUBJECT'];
it('.env.example and docker-compose.yml document every one of them', () => {
for (const name of documented) {
expect(envExample).toContain(`# ${name}=`);
expect(compose).toContain(`# - ${name}=`);
}
});
it('the Helm ConfigMap passes through the public key and the subject', () => {
for (const name of ['VAPID_PUBLIC_KEY', 'VAPID_SUBJECT']) {
expect(helmValues).toContain(`# ${name}:`);
expect(helmConfigMap).toContain(`${name}: {{ .Values.env.${name} | quote }}`);
}
});
it('no template promises an admin address as the push contact, since TREK never sends one on its own', () => {
const subjectDocs = [
envExample.split(/\r?\n/).find((l) => l.includes('# VAPID_SUBJECT=')),
compose.split(/\r?\n/).find((l) => l.includes('- VAPID_SUBJECT=')),
helmValues.slice(helmValues.indexOf('# VAPID_SUBJECT:')).split(/\r?\n\r?\n/)[0],
];
for (const doc of subjectDocs) {
expect(doc).toContain('APP_URL');
expect(doc).not.toContain('ADMIN_EMAIL');
}
});
it('no template says a broken pair is ignored, since push is off until it is fixed and no stored pair stands in', () => {
// The Helm comment wraps, so its lines are joined back into one sentence first.
const helmPrivateKey = helmValues
.slice(
helmValues.indexOf('# Optional Web Push key pair, private half'),
helmValues.indexOf(' VAPID_PRIVATE_KEY: ""'),
)
.replace(/\s*\n\s*#\s*/g, ' ');
const privateKeyDocs = [
envExample.split(/\r?\n/).find((l) => l.includes('# VAPID_PRIVATE_KEY=')),
compose.split(/\r?\n/).find((l) => l.includes('- VAPID_PRIVATE_KEY=')),
helmPrivateKey,
];
for (const doc of privateKeyDocs) {
expect(doc).toContain('turns push off');
expect(doc).not.toContain('ignored');
expect(doc).not.toContain('keeps working');
}
});
it('the Helm chart carries the private key in the Secret, never in the ConfigMap', () => {
const name = 'VAPID_PRIVATE_KEY';
expect(helmValues).toContain(` ${name}: ""`);
expect(helmSecret).toContain(`${name}: {{ .Values.secretEnv.${name} | b64enc | quote }}`);
expect(helmSecret).toContain(`${name}: {{ .Values.secretEnv.${name} | quote }}`);
expect(helmDeployment).toContain(`- name: ${name}\n valueFrom:\n secretKeyRef:`);
expect(helmDeployment).toContain(`key: ${name}\n optional: true`);
expect(helmConfigMap).not.toContain(name);
});
});
describe('the TREK services switches in the templates', () => {
// The installation registers itself by default; an operator who wants it
// off, or who was issued a key, has to find the names where they look.
it('.env.example and docker-compose.yml document the switch and the key', () => {
for (const name of ['TREK_SERVICES', 'TREK_SERVICES_KEY']) {
expect(envExample).toContain(`# ${name}=`);
expect(compose).toContain(`# - ${name}=`);
}
expect(envExample).toContain('# TREK_SERVICES_HOSTS=');
});
it('every template names the Photon address and the default hosts it joined', () => {
expect(envExample).toContain('# TREK_PHOTON_URL=https://photon.liketrek.com ');
expect(compose).toContain('# - TREK_PHOTON_URL=https://photon.liketrek.com ');
expect(helmValues).toContain('# TREK_PHOTON_URL:');
expect(helmConfigMap).toContain('TREK_PHOTON_URL: {{ .Values.env.TREK_PHOTON_URL | quote }}');
expect(envExample).toContain('(default: places.liketrek.com,photon.liketrek.com,valhalla.liketrek.com)');
});
it('the Helm ConfigMap passes the switch and the hosts, the Secret carries the key', () => {
for (const name of ['TREK_SERVICES', 'TREK_SERVICES_HOSTS']) {
expect(helmValues).toContain(`# ${name}:`);
expect(helmConfigMap).toContain(`${name}: {{ .Values.env.${name} | quote }}`);
}
const name = 'TREK_SERVICES_KEY';
expect(helmValues).toContain(` ${name}: ""`);
expect(helmSecret).toContain(`${name}: {{ .Values.secretEnv.${name} | b64enc | quote }}`);
expect(helmSecret).toContain(`${name}: {{ .Values.secretEnv.${name} | quote }}`);
expect(helmDeployment).toContain(`- name: ${name}\n valueFrom:\n secretKeyRef:`);
expect(helmDeployment).toContain(`key: ${name}\n optional: true`);
expect(helmConfigMap).not.toContain(name);
});
});