Files
Starlight_Lancher/apps/telemetry-dashboard/test/server/d1-admin-api.test.ts

234 lines
8.3 KiB
TypeScript

import { env } from 'cloudflare:test'
import { beforeEach, describe, expect, it } from 'vitest'
import { D1TelemetryAdminApi } from '../../server/utils/d1-admin-api'
import { parseErrorQuery } from '../../server/utils/validation'
const NOW = new Date('2026-08-14T10:00:00.000Z')
const ANALYTICS_URL = 'https://api.cloudflare.com/client/v4/graphql'
const FIXTURE_ACCOUNT_TAG = 'a7659e62e4d157aba4a45e4829b24e91'
async function gzip(input: string): Promise<ArrayBuffer> {
return new Response(
new Blob([input]).stream().pipeThrough(new CompressionStream('gzip')),
).arrayBuffer()
}
async function seed(): Promise<void> {
await env.DB.batch([
env.DB.prepare(
`INSERT INTO installations
(installation_hash, first_seen_at, last_seen_at, first_seen_day, app_version, platform, arch)
VALUES ('fixture-hash-a', 1, 2, '2026-08-13', '9.4.0-fixture', 'windows-fixture', 'x86_64-fixture')`,
),
env.DB.prepare(
`INSERT INTO installations
(installation_hash, first_seen_at, last_seen_at, first_seen_day, app_version, platform, arch)
VALUES ('fixture-hash-b', 1, 2, '2026-08-14', '9.3.2-fixture', 'linux-fixture', 'aarch64-fixture')`,
),
env.DB.prepare(
`INSERT INTO daily_active (day, installation_hash, app_version, platform, arch)
VALUES ('2026-08-14', 'fixture-hash-a', '9.4.0-fixture', 'windows-fixture', 'x86_64-fixture')`,
),
env.DB.prepare(
`INSERT INTO daily_active (day, installation_hash, app_version, platform, arch)
VALUES ('2026-08-14', 'fixture-hash-b', '9.3.2-fixture', 'linux-fixture', 'aarch64-fixture')`,
),
env.DB.prepare(
`INSERT INTO error_reports
(event_id, installation_hash, day, occurred_at, fingerprint, app_version, platform, arch,
error_type, message, occurrence_count, object_key, created_at)
VALUES ('fixture-event-a', 'fixture-hash-a', '2026-08-14', '2026-08-14T08:00:00Z',
'fixture-render-01', '9.4.0-fixture', 'windows-fixture', 'x86_64-fixture',
'RenderFixtureError', 'Fixture render failure', 4,
'errors/2026-08-14/fixture-render-01/fixture-event-a.json.gz', 1)`,
),
env.DB.prepare(
`INSERT INTO error_context_reservations
(event_id, day, fingerprint, app_version, object_key, created_at)
VALUES ('fixture-event-a', '2026-08-14', 'fixture-render-01', '9.4.0-fixture',
'errors/2026-08-14/fixture-render-01/fixture-event-a.json.gz', 1)`,
),
env.DB.prepare(
`INSERT INTO error_daily
(day, fingerprint, app_version, occurrence_count, installation_count,
latest_error_type, latest_message, has_sample)
VALUES ('2026-08-14', 'fixture-render-01', '9.4.0-fixture', 4, 0,
'RenderFixtureError', 'Fixture render failure', 1)`,
),
env.DB.prepare(
`INSERT INTO error_daily_installations
(day, fingerprint, app_version, installation_hash)
VALUES ('2026-08-14', 'fixture-render-01', '9.4.0-fixture', 'fixture-hash-a')`,
),
env.DB.prepare(
`INSERT INTO daily_totals (day, new_installations, active_installations, error_occurrences, distinct_error_groups)
VALUES ('2026-08-13', 1, 0, 0, 0)`,
),
env.DB.prepare(`INSERT INTO platforms (platform) VALUES ('windows-fixture')`),
])
await env.ERROR_CONTEXTS.put(
'errors/2026-08-14/fixture-render-01/fixture-event-a.json.gz',
await gzip(
JSON.stringify({
occurred_at: '2026-08-14T08:00:00Z',
fingerprint: 'fixture-render-01',
app: {
version: '9.4.0-fixture',
platform: 'windows-fixture',
arch: 'x86_64-fixture',
},
error_type: 'RenderFixtureError',
message: 'Fixture render failure',
stack: 'Fixture stack',
route: '/fixture',
command: 'fixture-command',
context: 'synthetic fixture context',
installation_hash: 'must-not-leak',
object_key: 'must-not-leak',
}),
),
{ httpMetadata: { contentEncoding: 'gzip', contentType: 'application/json' } },
)
}
function api(analytics?: 'configured' | 'partial' | 'unconfigured'): D1TelemetryAdminApi {
const fetcher: typeof fetch = async (input, init) => {
const url = String(input)
if (url === 'https://fixture.invalid/health') {
return new Response(JSON.stringify({ status: 'ok' }))
}
if (url === ANALYTICS_URL) {
const query = JSON.parse(String(init?.body ?? '{}')).query as string
if (analytics === 'partial' && query.includes('workersInvocationsAdaptive')) {
return new Response(
JSON.stringify({ data: null, errors: [{ message: 'fixture analytics failure' }] }),
)
}
if (query.includes('workersInvocationsAdaptive')) {
return new Response(
JSON.stringify({
data: {
viewer: {
accounts: [{ workersInvocationsAdaptive: [{ sum: { requests: 12_345 } }] }],
},
},
}),
)
}
if (query.includes('d1AnalyticsAdaptiveGroups')) {
return new Response(
JSON.stringify({
data: {
viewer: {
accounts: [
{
d1AnalyticsAdaptiveGroups: [{ sum: { rowsRead: 45_012, rowsWritten: 3_201 } }],
},
],
},
},
}),
)
}
if (query.includes('r2OperationsAdaptiveGroups')) {
return new Response(
JSON.stringify({
data: {
viewer: {
accounts: [{ r2OperationsAdaptiveGroups: [{ sum: { requests: 8_412 } }] }],
},
},
}),
)
}
return new Response(
JSON.stringify({ data: null, errors: [{ message: 'unknown fixture query' }] }),
)
}
return new Response('not found', { status: 404 })
}
return new D1TelemetryAdminApi(env.DB, env.ERROR_CONTEXTS, {
storeErrorContext: true,
healthUrl: 'https://fixture.invalid/health',
fetcher,
now: () => NOW,
cacheTtlMs: 0,
analytics:
analytics === 'unconfigured'
? undefined
: { accountTag: FIXTURE_ACCOUNT_TAG, apiToken: 'fixture-analytics-token' },
})
}
describe('D1TelemetryAdminApi', () => {
beforeEach(seed)
it('maps D1 aggregates to stable overview and DAU/WAU/MAU DTOs', async () => {
const overview = await api().overview('30d')
expect(overview.metrics.totalInstallations.value).toBe(2)
expect(overview.metrics.dau.value).toBe(2)
expect(overview.metrics.wau.value).toBe(2)
expect(overview.metrics.mau.value).toBe(2)
expect(overview.metrics.errorOccurrences.value).toBe(4)
expect(JSON.stringify(overview)).not.toContain('installation_hash')
})
it('supports bound search, filters, sorting, and server pagination', async () => {
const result = await api().errors(
parseErrorQuery({
range: '30d',
search: 'render',
platform: 'windows-fixture',
page: '1',
pageSize: '25',
sort: 'occurrences',
direction: 'desc',
}),
)
expect(result.total).toBe(1)
expect(result.items[0]).toMatchObject({
fingerprint: 'fixture-render-01',
occurrenceCount: 4,
hasSample: true,
})
expect(JSON.stringify(result)).not.toMatch(/installation_hash|object_key/)
})
it('reads only a D1-registered sample and never returns internal keys', async () => {
await env.ERROR_CONTEXTS.put(
'errors/unregistered.json.gz',
await gzip('{"message":"unregistered"}'),
)
const registered = await api().errorSample('fixture-render-01')
expect(registered).toMatchObject({
fingerprint: 'fixture-render-01',
message: 'Fixture render failure',
})
expect(JSON.stringify(registered)).not.toMatch(/installation_hash|object_key|must-not-leak/)
expect(await api().errorSample('fixture-unregistered')).toBeNull()
})
it('reports the unconfigured state when analytics credentials are absent', async () => {
const system = await api('unconfigured').system()
expect(system.accountUsage).toMatchObject({ status: 'unavailable', label: '未配置' })
expect(system.accountUsage.detail).toContain('尚未配置 Cloudflare Analytics API')
})
it('aggregates Workers, D1, and R2 usage into one account usage check', async () => {
const system = await api('configured').system()
expect(system.accountUsage.status).toBe('available')
expect(system.accountUsage.detail).toContain('Workers 12,345/100,000 请求')
expect(system.accountUsage.detail).toContain('写 3,201/100,000 行')
expect(system.accountUsage.detail).toContain('R2 8,412/1,000,000 操作')
})
it('degrades when one analytics dataset fails but others respond', async () => {
const system = await api('partial').system()
expect(system.accountUsage).toMatchObject({ status: 'degraded', label: '部分可用' })
expect(system.accountUsage.detail).toContain('fixture analytics failure')
expect(system.accountUsage.detail).toContain('D1')
expect(system.accountUsage.detail).toContain('R2')
})
})