Files
webmail/lib/__tests__/route-groups.test.ts
T
Linus Rath cba1109444 feat: report browser and server errors to an error tracker the admin sets
Admins had logs and nothing else: client errors only reached the
browser console. An admin can now set a Sentry-protocol address (DSN)
for Bugsink, GlitchTip or Sentry under System > Monitoring, or with
`ERROR_REPORTING_DSN`, `ERROR_REPORTING_DSN_FILE`,
`ERROR_REPORTING_SAMPLE_RATE` and `ERROR_REPORTING_ENVIRONMENT`.
Bulwark Lite reads the same from `errorReporting` in policy.json.
Everything stays off while no address is set.

- browsers report errors from the error boundaries, window.onerror,
  unhandled rejections and failed JMAP requests (method and error type
  only), with route and JMAP method breadcrumbs and the chunks' debug IDs
- the server reports what Next hands to onRequestError and what routes
  log with logger.error
- every report is scrubbed first: quoted text, addresses, URLs, file
  names, tokens and the rest after the first line are removed, and the
  server scrubs what browsers post to /api/errors again
- reports are deduplicated and rate-limited per session and process
- errors are counted per day for "errors today" on the admin page
- startup messages and audit entries go through the logger, so
  `LOG_FORMAT=json` covers them
2026-11-01 02:50:19 +01:00

22 lines
932 B
TypeScript

// @vitest-environment node
import { readdirSync, statSync } from 'node:fs';
import path from 'node:path';
import { describe, expect, it } from 'vitest';
import { API_GROUPS, PAGE_AREAS } from '@/lib/route-groups';
const ROOT = path.resolve(__dirname, '..', '..');
const dirs = (rel: string) => readdirSync(path.join(ROOT, rel)).filter((name) => statSync(path.join(ROOT, rel, name)).isDirectory());
// The groups are a fixed list so metric labels and report routes stay
// bounded; a new API folder or page area has to be added to it.
describe('route groups', () => {
it('list every API folder', () => {
expect([...API_GROUPS].sort()).toEqual(dirs('app/api').filter((d) => d !== '__tests__').sort());
});
it('list every page area', () => {
const areas = dirs('app/(main)/[locale]').filter((d) => !d.startsWith('[') && !d.startsWith('('));
for (const area of areas) expect(PAGE_AREAS).toContain(area);
});
});