@actuate-media/cms-core 0.25.1 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/__tests__/api/stats-routes.test.d.ts +2 -0
  2. package/dist/__tests__/api/stats-routes.test.d.ts.map +1 -0
  3. package/dist/__tests__/api/stats-routes.test.js +251 -0
  4. package/dist/__tests__/api/stats-routes.test.js.map +1 -0
  5. package/dist/__tests__/content-health/scanner.test.d.ts +2 -0
  6. package/dist/__tests__/content-health/scanner.test.d.ts.map +1 -0
  7. package/dist/__tests__/content-health/scanner.test.js +151 -0
  8. package/dist/__tests__/content-health/scanner.test.js.map +1 -0
  9. package/dist/__tests__/diagnostics/api-metrics.test.d.ts +2 -0
  10. package/dist/__tests__/diagnostics/api-metrics.test.d.ts.map +1 -0
  11. package/dist/__tests__/diagnostics/api-metrics.test.js +153 -0
  12. package/dist/__tests__/diagnostics/api-metrics.test.js.map +1 -0
  13. package/dist/api/handler-factory.d.ts.map +1 -1
  14. package/dist/api/handler-factory.js +31 -1
  15. package/dist/api/handler-factory.js.map +1 -1
  16. package/dist/api/handlers.d.ts.map +1 -1
  17. package/dist/api/handlers.js +263 -0
  18. package/dist/api/handlers.js.map +1 -1
  19. package/dist/content-health/cron.d.ts +67 -0
  20. package/dist/content-health/cron.d.ts.map +1 -0
  21. package/dist/content-health/cron.js +167 -0
  22. package/dist/content-health/cron.js.map +1 -0
  23. package/dist/content-health/scanner.d.ts +118 -0
  24. package/dist/content-health/scanner.d.ts.map +1 -0
  25. package/dist/content-health/scanner.js +263 -0
  26. package/dist/content-health/scanner.js.map +1 -0
  27. package/dist/diagnostics/api-metrics.d.ts +135 -0
  28. package/dist/diagnostics/api-metrics.d.ts.map +1 -0
  29. package/dist/diagnostics/api-metrics.js +374 -0
  30. package/dist/diagnostics/api-metrics.js.map +1 -0
  31. package/package.json +1 -1
  32. package/prisma/migrations/0007_dashboard_metrics/migration.sql +74 -0
  33. package/prisma/schema.prisma +180 -73
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=stats-routes.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stats-routes.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/api/stats-routes.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,251 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
2
+ import { handleActuateAPI } from '../../api/index.js';
3
+ import { createSession } from '../../auth/session.js';
4
+ import { initDB } from '../../db.js';
5
+ import { generateToken as generateCsrfToken } from '../../security/csrf.js';
6
+ /**
7
+ * Integration tests for the dashboard stats endpoints introduced for
8
+ * the "Delivery API" and "Content Health" cards on the dashboard.
9
+ *
10
+ * These tests lock down the JSON response shape the dashboard depends
11
+ * on so a future refactor can't silently regress the client. They use
12
+ * the same mock-DB pattern as `notifications-routes.test.ts`.
13
+ */
14
+ const SECRET = 'test-secret-for-stats-routes-aaaaaaaaaaaaaaaaaa';
15
+ function createMockDB(args = {}) {
16
+ const metrics = args.metrics ?? [];
17
+ const issues = args.issues ?? [];
18
+ return {
19
+ session: { findUnique: async () => ({ revokedAt: null }) },
20
+ user: {},
21
+ apiRequestMetric: {
22
+ async findMany({ where, select: _select, }) {
23
+ return metrics.filter((m) => {
24
+ if (m.bucketStart < where.bucketStart.gte)
25
+ return false;
26
+ if (where.bucketStart.lt && m.bucketStart >= where.bucketStart.lt)
27
+ return false;
28
+ return true;
29
+ });
30
+ },
31
+ },
32
+ webhookEndpoint: {
33
+ async count({ where } = {}) {
34
+ if (where?.active === true)
35
+ return 2;
36
+ return 3;
37
+ },
38
+ },
39
+ contentIssue: {
40
+ async groupBy({ by, _sum }) {
41
+ void by;
42
+ void _sum;
43
+ const grouped = new Map();
44
+ for (const i of issues)
45
+ grouped.set(i.type, (grouped.get(i.type) ?? 0) + i.count);
46
+ return Array.from(grouped, ([type, count]) => ({ type, _sum: { count } }));
47
+ },
48
+ async findFirst({ orderBy: _orderBy, select: _select }) {
49
+ if (issues.length === 0)
50
+ return null;
51
+ const latest = issues.reduce((a, b) => (a.lastSeenAt > b.lastSeenAt ? a : b));
52
+ return { lastSeenAt: latest.lastSeenAt };
53
+ },
54
+ },
55
+ };
56
+ }
57
+ async function sessionHeaders(userId = 'user-1') {
58
+ const token = await createSession({ userId, role: 'ADMIN', sessionId: `session-${userId}` }, { secret: SECRET });
59
+ const csrf = await generateCsrfToken();
60
+ return {
61
+ cookie: `actuate_session=${token}; actuate_csrf=${csrf}`,
62
+ 'x-csrf-token': csrf,
63
+ 'content-type': 'application/json',
64
+ };
65
+ }
66
+ describe('GET /api/cms/stats/api-delivery', () => {
67
+ const originalSecret = process.env.CMS_SECRET;
68
+ beforeEach(() => {
69
+ process.env.CMS_SECRET = SECRET;
70
+ });
71
+ afterEach(() => {
72
+ if (originalSecret === undefined)
73
+ delete process.env.CMS_SECRET;
74
+ else
75
+ process.env.CMS_SECRET = originalSecret;
76
+ });
77
+ it('rejects unauthenticated requests with 401', async () => {
78
+ const db = createMockDB();
79
+ initDB(db);
80
+ const handler = handleActuateAPI({ prismaClient: db });
81
+ const res = await handler(new Request('https://example.com/api/cms/stats/api-delivery'));
82
+ expect(res.status).toBe(401);
83
+ });
84
+ it('returns the empty payload (hasData=false) when no metrics exist', async () => {
85
+ const db = createMockDB();
86
+ initDB(db);
87
+ const handler = handleActuateAPI({ prismaClient: db });
88
+ const headers = await sessionHeaders();
89
+ const res = await handler(new Request('https://example.com/api/cms/stats/api-delivery', {
90
+ headers: { cookie: headers.cookie },
91
+ }));
92
+ expect(res.status).toBe(200);
93
+ const body = (await res.json());
94
+ expect(body.data).toMatchObject({
95
+ hasData: false,
96
+ requests24h: 0,
97
+ requestsPrev24h: 0,
98
+ deltaPercent: null,
99
+ errorRate: 0,
100
+ avgLatencyMs: 0,
101
+ p95LatencyMs: 0,
102
+ });
103
+ // Webhook counters still come from the mocked WebhookEndpoint table.
104
+ expect(body.data.webhookCount).toBe(3);
105
+ expect(body.data.webhookActiveCount).toBe(2);
106
+ });
107
+ it('aggregates 24h request volume, error rate, and avg latency', async () => {
108
+ // Two buckets:
109
+ // - 100 2xx requests, sumLatency 5000 (avg 50ms)
110
+ // - 10 5xx requests, sumLatency 1000 (avg 100ms)
111
+ // Total: 110 requests, 10 errors → 9.09% error rate. Avg latency
112
+ // = 6000 / 110 ≈ 54.5ms.
113
+ const now = new Date();
114
+ const bucketTime = new Date(now.getTime() - 60_000);
115
+ const hist2xx = new Array(12).fill(0);
116
+ hist2xx[6] = 100; // 32-64ms bucket
117
+ const hist5xx = new Array(12).fill(0);
118
+ hist5xx[7] = 10; // 64-128ms bucket
119
+ const db = createMockDB({
120
+ metrics: [
121
+ {
122
+ statusBucket: '2xx',
123
+ count: 100,
124
+ sumLatencyMs: 5000,
125
+ maxLatencyMs: 60,
126
+ latencyHistogram: hist2xx,
127
+ bucketStart: bucketTime,
128
+ },
129
+ {
130
+ statusBucket: '5xx',
131
+ count: 10,
132
+ sumLatencyMs: 1000,
133
+ maxLatencyMs: 110,
134
+ latencyHistogram: hist5xx,
135
+ bucketStart: bucketTime,
136
+ },
137
+ ],
138
+ });
139
+ initDB(db);
140
+ const handler = handleActuateAPI({ prismaClient: db });
141
+ const headers = await sessionHeaders();
142
+ const res = await handler(new Request('https://example.com/api/cms/stats/api-delivery', {
143
+ headers: { cookie: headers.cookie },
144
+ }));
145
+ expect(res.status).toBe(200);
146
+ const body = (await res.json());
147
+ expect(body.data.hasData).toBe(true);
148
+ expect(body.data.requests24h).toBe(110);
149
+ expect(body.data.errorRate).toBeCloseTo(0.0909, 3);
150
+ expect(body.data.avgLatencyMs).toBeCloseTo(54.5, 1);
151
+ // p95 of 100×bucket6 + 10×bucket7 = watermark at 104.5; cumulative
152
+ // hits 110 in bucket 7 → upper bound 128.
153
+ expect(body.data.p95LatencyMs).toBe(128);
154
+ });
155
+ });
156
+ describe('GET /api/cms/stats/content-health', () => {
157
+ const originalSecret = process.env.CMS_SECRET;
158
+ beforeEach(() => {
159
+ process.env.CMS_SECRET = SECRET;
160
+ });
161
+ afterEach(() => {
162
+ if (originalSecret === undefined)
163
+ delete process.env.CMS_SECRET;
164
+ else
165
+ process.env.CMS_SECRET = originalSecret;
166
+ });
167
+ it('rejects unauthenticated requests with 401', async () => {
168
+ const db = createMockDB();
169
+ initDB(db);
170
+ const handler = handleActuateAPI({ prismaClient: db });
171
+ const res = await handler(new Request('https://example.com/api/cms/stats/content-health'));
172
+ expect(res.status).toBe(401);
173
+ });
174
+ it('returns a perfect score (100, "Good") and zero counts when no issues exist', async () => {
175
+ const db = createMockDB();
176
+ initDB(db);
177
+ const handler = handleActuateAPI({ prismaClient: db });
178
+ const headers = await sessionHeaders();
179
+ const res = await handler(new Request('https://example.com/api/cms/stats/content-health', {
180
+ headers: { cookie: headers.cookie },
181
+ }));
182
+ expect(res.status).toBe(200);
183
+ const body = (await res.json());
184
+ expect(body.data.score).toBe(100);
185
+ expect(body.data.label).toBe('Good');
186
+ expect(body.data.totalIssues).toBe(0);
187
+ expect(body.data.lastScanAt).toBeNull();
188
+ expect(body.data.counts).toEqual({
189
+ MISSING_META_DESCRIPTION: 0,
190
+ MISSING_ALT_TEXT: 0,
191
+ BROKEN_INTERNAL_LINK: 0,
192
+ OUTDATED_CONTENT: 0,
193
+ });
194
+ });
195
+ it('aggregates counts and computes a penalty-weighted score', async () => {
196
+ // 5 missing meta (5×5=25 penalty)
197
+ // 12 missing alt (12×2=24 penalty)
198
+ // 2 broken links (2×10=20 penalty)
199
+ // 8 outdated (8×3=24 penalty)
200
+ // → score = 100 - 93 = 7
201
+ const db = createMockDB({
202
+ issues: [
203
+ {
204
+ type: 'MISSING_META_DESCRIPTION',
205
+ count: 5,
206
+ lastSeenAt: new Date('2026-05-28T00:00:00Z'),
207
+ },
208
+ { type: 'MISSING_ALT_TEXT', count: 12, lastSeenAt: new Date('2026-05-28T01:00:00Z') },
209
+ { type: 'BROKEN_INTERNAL_LINK', count: 2, lastSeenAt: new Date('2026-05-28T02:00:00Z') },
210
+ { type: 'OUTDATED_CONTENT', count: 8, lastSeenAt: new Date('2026-05-28T03:00:00Z') },
211
+ ],
212
+ });
213
+ initDB(db);
214
+ const handler = handleActuateAPI({ prismaClient: db });
215
+ const headers = await sessionHeaders();
216
+ const res = await handler(new Request('https://example.com/api/cms/stats/content-health', {
217
+ headers: { cookie: headers.cookie },
218
+ }));
219
+ expect(res.status).toBe(200);
220
+ const body = (await res.json());
221
+ expect(body.data.totalIssues).toBe(27);
222
+ expect(body.data.score).toBe(7);
223
+ expect(body.data.label).toBe('Poor');
224
+ expect(body.data.counts.MISSING_META_DESCRIPTION).toBe(5);
225
+ expect(body.data.counts.MISSING_ALT_TEXT).toBe(12);
226
+ expect(body.data.counts.BROKEN_INTERNAL_LINK).toBe(2);
227
+ expect(body.data.counts.OUTDATED_CONTENT).toBe(8);
228
+ expect(body.data.lastScanAt).toBe('2026-05-28T03:00:00.000Z');
229
+ });
230
+ it('floors the score at 0 when penalties exceed 100', async () => {
231
+ const db = createMockDB({
232
+ issues: [
233
+ {
234
+ type: 'BROKEN_INTERNAL_LINK',
235
+ count: 50, // 500 penalty
236
+ lastSeenAt: new Date(),
237
+ },
238
+ ],
239
+ });
240
+ initDB(db);
241
+ const handler = handleActuateAPI({ prismaClient: db });
242
+ const headers = await sessionHeaders();
243
+ const res = await handler(new Request('https://example.com/api/cms/stats/content-health', {
244
+ headers: { cookie: headers.cookie },
245
+ }));
246
+ const body = (await res.json());
247
+ expect(body.data.score).toBe(0);
248
+ expect(body.data.label).toBe('Poor');
249
+ });
250
+ });
251
+ //# sourceMappingURL=stats-routes.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stats-routes.test.js","sourceRoot":"","sources":["../../../src/__tests__/api/stats-routes.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=scanner.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/content-health/scanner.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,151 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { classifyLink, countMissingAltImages, extractLinks, scanDocument, } from '../../content-health/scanner.js';
3
+ const DAY = 24 * 60 * 60 * 1000;
4
+ function fixture(overrides = {}) {
5
+ return {
6
+ id: 'doc-1',
7
+ data: {
8
+ metaDescription: 'A perfectly fine meta description.',
9
+ body: '<h1>Title</h1><p>Body text.</p><img src="a.jpg" alt="A">',
10
+ ...overrides.data,
11
+ },
12
+ updatedAt: new Date(Date.now() - 7 * DAY),
13
+ publishedAt: new Date(Date.now() - 7 * DAY),
14
+ ...overrides,
15
+ };
16
+ }
17
+ describe('countMissingAltImages', () => {
18
+ it('returns 0 when every <img> has an alt attribute', () => {
19
+ const html = '<img src="a.jpg" alt="A"><img src="b.jpg" alt=""><img src="c.jpg" alt="C">';
20
+ expect(countMissingAltImages(html)).toBe(0);
21
+ });
22
+ it('counts <img> tags missing alt regardless of attribute order', () => {
23
+ const html = '<img src="a.jpg"><img alt="ok" src="b.jpg"><IMG SRC="c.jpg">';
24
+ expect(countMissingAltImages(html)).toBe(2);
25
+ });
26
+ it('does not falsely match `alt` substrings in other attributes', () => {
27
+ // The naive regex must not be fooled by `data-default-alt="..."`.
28
+ const html = '<img src="x.jpg" data-default-alt="hi">';
29
+ expect(countMissingAltImages(html)).toBe(1);
30
+ });
31
+ it('handles empty / non-HTML input gracefully', () => {
32
+ expect(countMissingAltImages('')).toBe(0);
33
+ expect(countMissingAltImages('no html here')).toBe(0);
34
+ });
35
+ });
36
+ describe('extractLinks', () => {
37
+ it('extracts href values across quote styles', () => {
38
+ const html = `<a href="/foo">x</a><a href='/bar'>y</a><a class="x" href="/baz">z</a>`;
39
+ expect(extractLinks(html).sort()).toEqual(['/bar', '/baz', '/foo']);
40
+ });
41
+ it('ignores anchors without href', () => {
42
+ const html = `<a>no href</a><a href="/foo">x</a>`;
43
+ expect(extractLinks(html)).toEqual(['/foo']);
44
+ });
45
+ });
46
+ describe('classifyLink', () => {
47
+ const origin = 'https://example.com';
48
+ it('treats relative paths as internal', () => {
49
+ expect(classifyLink('/about', origin)).toEqual({
50
+ isInternal: true,
51
+ resolved: 'https://example.com/about',
52
+ });
53
+ expect(classifyLink('./relative', origin).isInternal).toBe(true);
54
+ });
55
+ it('treats same-origin absolute URLs as internal', () => {
56
+ expect(classifyLink('https://example.com/page', origin).isInternal).toBe(true);
57
+ });
58
+ it('classifies cross-origin URLs as external', () => {
59
+ expect(classifyLink('https://other.com/x', origin).isInternal).toBe(false);
60
+ });
61
+ it('rejects non-http schemes and hash-only anchors', () => {
62
+ expect(classifyLink('mailto:a@b.com', origin).resolved).toBeNull();
63
+ expect(classifyLink('tel:1234', origin).resolved).toBeNull();
64
+ expect(classifyLink('javascript:alert(1)', origin).resolved).toBeNull();
65
+ expect(classifyLink('#section', origin).resolved).toBeNull();
66
+ expect(classifyLink('', origin).resolved).toBeNull();
67
+ });
68
+ it('returns nulls when siteOrigin is malformed', () => {
69
+ expect(classifyLink('/about', 'not-a-url').resolved).toBeNull();
70
+ });
71
+ });
72
+ describe('scanDocument', () => {
73
+ it('produces no issues for a healthy document', async () => {
74
+ const issues = await scanDocument(fixture(), { checkLink: null });
75
+ expect(issues).toEqual([]);
76
+ });
77
+ it('flags missing meta description', async () => {
78
+ const doc = fixture();
79
+ doc.data = { ...doc.data, metaDescription: undefined, seoDescription: undefined };
80
+ const issues = await scanDocument(doc, { checkLink: null });
81
+ expect(issues.map((i) => i.type)).toContain('MISSING_META_DESCRIPTION');
82
+ });
83
+ it('reads meta description from a nested `seo` object', async () => {
84
+ const doc = fixture();
85
+ doc.data = { body: doc.data.body, seo: { description: 'nested ok' } };
86
+ const issues = await scanDocument(doc, { checkLink: null });
87
+ expect(issues.map((i) => i.type)).not.toContain('MISSING_META_DESCRIPTION');
88
+ });
89
+ it('flags missing alt text with the per-document count', async () => {
90
+ const doc = fixture();
91
+ doc.data = {
92
+ metaDescription: 'ok',
93
+ body: '<img src="a.jpg"><img src="b.jpg"><img alt="ok" src="c.jpg">',
94
+ };
95
+ const issues = await scanDocument(doc, { checkLink: null });
96
+ const altIssue = issues.find((i) => i.type === 'MISSING_ALT_TEXT');
97
+ expect(altIssue?.count).toBe(2);
98
+ });
99
+ it('flags outdated content beyond the configured threshold', async () => {
100
+ const doc = fixture({
101
+ updatedAt: new Date(Date.now() - 200 * DAY),
102
+ publishedAt: new Date(Date.now() - 200 * DAY),
103
+ });
104
+ const issues = await scanDocument(doc, { checkLink: null });
105
+ const outdated = issues.find((i) => i.type === 'OUTDATED_CONTENT');
106
+ expect(outdated).toBeDefined();
107
+ expect(outdated?.details?.ageDays).toBeGreaterThan(180);
108
+ });
109
+ it('does NOT flag outdated content when published within the threshold', async () => {
110
+ const doc = fixture({
111
+ updatedAt: new Date(Date.now() - 10 * DAY),
112
+ publishedAt: new Date(Date.now() - 10 * DAY),
113
+ });
114
+ const issues = await scanDocument(doc, { checkLink: null });
115
+ expect(issues.find((i) => i.type === 'OUTDATED_CONTENT')).toBeUndefined();
116
+ });
117
+ it('flags broken internal links via the injected checker', async () => {
118
+ const doc = fixture();
119
+ doc.data = {
120
+ metaDescription: 'ok',
121
+ body: `<a href="/good">a</a><a href="/bad">b</a><a href="https://external.com/x">c</a>`,
122
+ };
123
+ const checkLink = vi.fn(async (url) => !url.endsWith('/bad'));
124
+ const issues = await scanDocument(doc, {
125
+ checkLink,
126
+ siteOrigin: 'https://example.com',
127
+ });
128
+ const broken = issues.find((i) => i.type === 'BROKEN_INTERNAL_LINK');
129
+ expect(broken?.count).toBe(1);
130
+ expect(broken?.details?.urls).toEqual(['https://example.com/bad']);
131
+ // External link must NOT be probed (cross-origin)
132
+ expect(checkLink).not.toHaveBeenCalledWith('https://external.com/x');
133
+ });
134
+ it('skips link checking when no checker or origin is supplied', async () => {
135
+ const doc = fixture();
136
+ doc.data = { metaDescription: 'ok', body: '<a href="/whatever">x</a>' };
137
+ const checker = vi.fn(async () => true);
138
+ await scanDocument(doc, { checkLink: checker, siteOrigin: null });
139
+ expect(checker).not.toHaveBeenCalled();
140
+ });
141
+ it('falls back to publishedAt when computing freshness, but updatedAt otherwise', async () => {
142
+ // updatedAt recent, publishedAt very old → outdated by publishedAt rule.
143
+ const doc = fixture({
144
+ updatedAt: new Date(Date.now() - 1 * DAY),
145
+ publishedAt: new Date(Date.now() - 365 * DAY),
146
+ });
147
+ const issues = await scanDocument(doc, { checkLink: null });
148
+ expect(issues.find((i) => i.type === 'OUTDATED_CONTENT')).toBeDefined();
149
+ });
150
+ });
151
+ //# sourceMappingURL=scanner.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.test.js","sourceRoot":"","sources":["../../../src/__tests__/content-health/scanner.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=api-metrics.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-metrics.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/diagnostics/api-metrics.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,153 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { LATENCY_BUCKETS, HISTOGRAM_LENGTH, bucketStartFor, estimatePercentile, histogramBucketIndex, makeHistogramFor, mergeHistograms, normalizeRoute, shouldRecord, statusBucketOf, } from '../../diagnostics/api-metrics.js';
3
+ describe('statusBucketOf', () => {
4
+ it('maps standard HTTP families to xx buckets', () => {
5
+ expect(statusBucketOf(200)).toBe('2xx');
6
+ expect(statusBucketOf(204)).toBe('2xx');
7
+ expect(statusBucketOf(301)).toBe('3xx');
8
+ expect(statusBucketOf(304)).toBe('3xx');
9
+ expect(statusBucketOf(400)).toBe('4xx');
10
+ expect(statusBucketOf(404)).toBe('4xx');
11
+ expect(statusBucketOf(429)).toBe('4xx');
12
+ expect(statusBucketOf(500)).toBe('5xx');
13
+ expect(statusBucketOf(503)).toBe('5xx');
14
+ });
15
+ it('puts unknown statuses in the "other" bucket', () => {
16
+ expect(statusBucketOf(99)).toBe('other');
17
+ expect(statusBucketOf(700)).toBe('other');
18
+ expect(statusBucketOf(-1)).toBe('other');
19
+ });
20
+ });
21
+ describe('histogramBucketIndex', () => {
22
+ it('assigns sub-1ms to bucket 0', () => {
23
+ expect(histogramBucketIndex(0)).toBe(0);
24
+ expect(histogramBucketIndex(0.4)).toBe(0);
25
+ expect(histogramBucketIndex(0.99)).toBe(0);
26
+ });
27
+ it('uses log-2 boundaries', () => {
28
+ expect(histogramBucketIndex(1)).toBe(1); // 1 < 2
29
+ expect(histogramBucketIndex(1.5)).toBe(1);
30
+ expect(histogramBucketIndex(2)).toBe(2); // 2 < 4
31
+ expect(histogramBucketIndex(4)).toBe(3);
32
+ expect(histogramBucketIndex(127)).toBe(7); // 64-128
33
+ expect(histogramBucketIndex(128)).toBe(8); // 128-256
34
+ });
35
+ it('puts ≥1024ms in the overflow bucket', () => {
36
+ expect(histogramBucketIndex(1024)).toBe(LATENCY_BUCKETS.length);
37
+ expect(histogramBucketIndex(60_000)).toBe(LATENCY_BUCKETS.length);
38
+ });
39
+ it('handles non-finite input as bucket 0', () => {
40
+ expect(histogramBucketIndex(NaN)).toBe(0);
41
+ expect(histogramBucketIndex(-1)).toBe(0);
42
+ expect(histogramBucketIndex(Infinity)).toBe(0);
43
+ });
44
+ });
45
+ describe('makeHistogramFor + mergeHistograms', () => {
46
+ it('initial histogram has exactly one count in the right bucket', () => {
47
+ const hist = makeHistogramFor(50); // 32-64ms → bucket 6
48
+ expect(hist).toHaveLength(HISTOGRAM_LENGTH);
49
+ expect(hist[6]).toBe(1);
50
+ expect(hist.reduce((s, v) => s + v, 0)).toBe(1);
51
+ });
52
+ it('merges element-wise without mutating inputs', () => {
53
+ const a = makeHistogramFor(50);
54
+ const b = makeHistogramFor(100); // 64-128ms → bucket 7
55
+ const merged = mergeHistograms(a, b);
56
+ expect(merged[6]).toBe(1);
57
+ expect(merged[7]).toBe(1);
58
+ expect(a[7]).toBe(0); // unchanged
59
+ });
60
+ it('tolerates undefined / short prior arrays', () => {
61
+ const b = makeHistogramFor(10);
62
+ expect(() => mergeHistograms(undefined, b)).not.toThrow();
63
+ expect(() => mergeHistograms([1, 2, 3], b)).not.toThrow();
64
+ const merged = mergeHistograms([1, 2, 3], b);
65
+ expect(merged[0]).toBe(1);
66
+ expect(merged[1]).toBe(2);
67
+ expect(merged[2]).toBe(3);
68
+ });
69
+ });
70
+ describe('estimatePercentile', () => {
71
+ it('returns 0 for an empty histogram', () => {
72
+ expect(estimatePercentile(new Array(HISTOGRAM_LENGTH).fill(0), 0.95)).toBe(0);
73
+ });
74
+ it('returns the bucket boundary that crosses the p-watermark', () => {
75
+ // 90% of traffic in bucket 4 (8-16ms), 10% in bucket 7 (64-128ms).
76
+ // p50 should land in bucket 4 (upper bound = 16).
77
+ // p95 must cross into bucket 7 (upper bound = 128).
78
+ const hist = new Array(HISTOGRAM_LENGTH).fill(0);
79
+ hist[4] = 90;
80
+ hist[7] = 10;
81
+ expect(estimatePercentile(hist, 0.5)).toBe(16);
82
+ expect(estimatePercentile(hist, 0.95)).toBe(128);
83
+ });
84
+ it('estimates beyond the last bucket using the overflow heuristic', () => {
85
+ const hist = new Array(HISTOGRAM_LENGTH).fill(0);
86
+ hist[HISTOGRAM_LENGTH - 1] = 10; // overflow bucket
87
+ const result = estimatePercentile(hist, 0.95);
88
+ expect(result).toBeGreaterThan(LATENCY_BUCKETS[LATENCY_BUCKETS.length - 1]);
89
+ });
90
+ });
91
+ describe('bucketStartFor', () => {
92
+ it('snaps to the start of the UTC minute', () => {
93
+ const t = new Date('2026-05-28T14:37:42.123Z').getTime();
94
+ const start = bucketStartFor(t);
95
+ expect(start.toISOString()).toBe('2026-05-28T14:37:00.000Z');
96
+ });
97
+ it('is idempotent — calling twice returns the same boundary', () => {
98
+ const t = new Date('2026-05-28T14:37:42.123Z').getTime();
99
+ const a = bucketStartFor(t);
100
+ const b = bucketStartFor(a);
101
+ expect(a.getTime()).toBe(b.getTime());
102
+ });
103
+ });
104
+ describe('normalizeRoute', () => {
105
+ it('passes static routes through verbatim', () => {
106
+ expect(normalizeRoute('/stats')).toBe('/stats');
107
+ expect(normalizeRoute('/health')).toBe('/health');
108
+ expect(normalizeRoute('/auth/login')).toBe('/auth/login');
109
+ expect(normalizeRoute('/notifications')).toBe('/notifications');
110
+ });
111
+ it('strips the /api/cms prefix if present', () => {
112
+ expect(normalizeRoute('/api/cms/stats')).toBe('/stats');
113
+ expect(normalizeRoute('/api/cms/collections/posts/abc')).toBe('/collections/:slug/:id');
114
+ });
115
+ it('parametrises collection routes by document id', () => {
116
+ expect(normalizeRoute('/collections/posts')).toBe('/collections/:slug');
117
+ expect(normalizeRoute('/collections/posts/abc123')).toBe('/collections/:slug/:id');
118
+ expect(normalizeRoute('/collections/posts/abc123/restore')).toBe('/collections/:slug/:id/restore');
119
+ expect(normalizeRoute('/collections/posts/abc123/versions')).toBe('/collections/:slug/:id/versions');
120
+ });
121
+ it('parametrises globals, media, forms, users, api-keys', () => {
122
+ expect(normalizeRoute('/globals/site-settings')).toBe('/globals/:slug');
123
+ expect(normalizeRoute('/media/abc')).toBe('/media/:id');
124
+ expect(normalizeRoute('/forms/xyz/submit')).toBe('/forms/:id/submit');
125
+ expect(normalizeRoute('/forms/xyz')).toBe('/forms/:id');
126
+ expect(normalizeRoute('/users/u1')).toBe('/users/:id');
127
+ expect(normalizeRoute('/api-keys/k1')).toBe('/api-keys/:id');
128
+ });
129
+ it('collapses unknown long paths into the /other catch-all', () => {
130
+ expect(normalizeRoute('/some/random/path/we/never/added')).toBe('/other');
131
+ expect(normalizeRoute('/x/y/z')).toBe('/other');
132
+ });
133
+ it('strips query strings and trailing slashes', () => {
134
+ expect(normalizeRoute('/stats?from=2026')).toBe('/stats');
135
+ expect(normalizeRoute('/collections/posts/')).toBe('/collections/:slug');
136
+ });
137
+ });
138
+ describe('shouldRecord', () => {
139
+ it('skips the dashboard polling routes', () => {
140
+ expect(shouldRecord('/stats')).toBe(false);
141
+ expect(shouldRecord('/stats/api-delivery')).toBe(false);
142
+ expect(shouldRecord('/stats/content-health')).toBe(false);
143
+ expect(shouldRecord('/health')).toBe(false);
144
+ expect(shouldRecord('/notifications/stream')).toBe(false);
145
+ });
146
+ it('records normal API traffic', () => {
147
+ expect(shouldRecord('/collections/:slug')).toBe(true);
148
+ expect(shouldRecord('/collections/:slug/:id')).toBe(true);
149
+ expect(shouldRecord('/auth/login')).toBe(true);
150
+ expect(shouldRecord('/other')).toBe(true);
151
+ });
152
+ });
153
+ //# sourceMappingURL=api-metrics.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-metrics.test.js","sourceRoot":"","sources":["../../../src/__tests__/diagnostics/api-metrics.test.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"handler-factory.d.ts","sourceRoot":"","sources":["../../src/api/handler-factory.ts"],"names":[],"mappings":"AA+EA,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,kBAAkB,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;CAC5C;AAMD,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,uBAA4B,IAsFtC,SAAS,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC,CAsHnE"}
1
+ {"version":3,"file":"handler-factory.d.ts","sourceRoot":"","sources":["../../src/api/handler-factory.ts"],"names":[],"mappings":"AAgFA,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,kBAAkB,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;CAC5C;AAMD,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,uBAA4B,IAsFtC,SAAS,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC,CAmJnE"}