Files
webmail/lib/__tests__/telemetry-payload.test.ts
T
Linus Rath 9f6b546ffe fix: stop reporting a proxy's version as the Stalwart version in telemetry
The heartbeat's `stalwart_version` came from the `Server` header of
`/.well-known/jmap`. Stalwart never sends one (0.16.0 through 1.0), so
the field was null, or the version of a reverse proxy in front of
Stalwart. The probe is gone: the version is reported only when
`STALWART_VERSION` is set.
2026-09-25 19:32:35 +02:00

62 lines
2.3 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
vi.mock('@/lib/admin/config-manager', () => ({
configManager: {
ensureLoaded: async () => {},
getPolicy: () => ({ features: {} }),
getAll: () => ({}),
get: (key: string, fallback: unknown) => (key === 'jmapServerUrl' ? 'https://mail.example.org' : fallback),
},
}));
vi.mock('@/lib/telemetry/state', () => ({ getInstanceId: async () => 'instance-1' }));
vi.mock('@/lib/telemetry/login-tracker', () => ({ getLoginCounts: async () => ({ total: 3, active7d: 1 }) }));
vi.mock('@/lib/admin/plugin-registry', () => ({
getPluginRegistry: async () => ({ plugins: [] }),
getThemeRegistry: async () => ({ themes: [] }),
}));
// A probe, if one were made, would get past the SSRF guard.
vi.mock('@/lib/telemetry/endpoint-guard', () => ({ resolveEndpointAllowed: async () => ({ ok: true }) }));
import { buildPayload } from '@/lib/telemetry/payload';
// Stalwart (0.16.0 through 1.0) sends no Server header and publishes no
// version a client could read. The heartbeat used to read the Server header of
// /.well-known/jmap anyway: null against Stalwart itself, and the proxy's
// version (nginx/1.25.3 -> "1.25.3") behind a reverse proxy.
describe('telemetry stalwart_version', () => {
let fetchSpy: ReturnType<typeof vi.spyOn>;
beforeEach(() => {
delete process.env.STALWART_VERSION;
fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(
new Response(null, { status: 307, headers: { server: 'nginx/1.25.3', location: '/jmap/session' } }),
);
});
afterEach(() => {
delete process.env.STALWART_VERSION;
vi.restoreAllMocks();
});
it('is empty without STALWART_VERSION, on 0.16 and 1.0 alike, and probes nothing', async () => {
const payload = await buildPayload();
expect(payload.stalwart_version).toBeNull();
expect(fetchSpy).not.toHaveBeenCalled();
});
it('reports the version the admin set', async () => {
process.env.STALWART_VERSION = ' 0.16.23 ';
const payload = await buildPayload();
expect(payload.stalwart_version).toBe('0.16.23');
expect(fetchSpy).not.toHaveBeenCalled();
});
it('treats an empty STALWART_VERSION as unset', async () => {
process.env.STALWART_VERSION = '';
expect((await buildPayload()).stalwart_version).toBeNull();
});
});