import { nanoid } from 'nanoid' import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts' import type * as Analytics from '../../../analytics/Analytics.js' import * as TestApp from '../../../../test/App.js' import * as SponsoredTransactions from '../../../db/tables/sponsoredTransactions.js' import * as Viem from '../../../internal/Viem.js' import * as Keys from './api-keys.js' import * as Orgs from './orgs.js' import * as Projects from './projects.js' import * as Usage from './usage.js' /** Origin pinned for SIWE domain binding; Hono test requests use this host. */ const origin = 'http://localhost' /** Super admin secret configured on the test app. */ const secret = 'tempo:sk:b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1' function createApp( options: { analytics?: Analytics.Analytics | undefined db?: TestApp.create.Options['db'] | undefined } = {}, ) { return TestApp.create({ ...(options.analytics === undefined ? {} : { analytics: options.analytics }), auth: { superAdmin: { secret } }, db: options.db ?? TestApp.database(), session: { wallet: { origin } }, }) } /** RequestInit presenting the given token. */ function as(token: string) { return { headers: { 'tempo-api-key': token } } as const } /** RequestInit for a JSON mutation carrying the session cookie. */ function json(method: 'POST', cookie: string, body: unknown) { return { body: JSON.stringify(body), headers: { 'content-type': 'application/json', cookie }, method, } } /** RequestInit for a JSON mutation authenticated as the super admin. */ function superAdminJson(method: 'POST', body: unknown) { return { body: JSON.stringify(body), headers: { 'content-type': 'application/json', 'tempo-api-key': secret }, method, } } /** Signs in a fresh scripted account; returns its session cookie. */ async function session(app: TestApp.signIn.App) { const account = privateKeyToAccount(generatePrivateKey()) const { cookie } = await TestApp.signIn(app, account) return cookie! } /** Creates an org and a project owned by the session. */ async function fixture(app: ReturnType, cookie: string) { const org = await TestApp.json( await app.request('/v1/orgs', json('POST', cookie, { name: 'Acme' })), Orgs.schema.Organization, ) const project = await TestApp.json( await app.request(`/v1/orgs/${org.id}/projects`, json('POST', cookie, { name: 'Checkout' })), Projects.schema.Project, ) return { org, project } } /** Signs in and creates an org owned by that session; returns the cookie and org id. */ async function seeded(app: ReturnType) { const cookie = await session(app) const response = await app.request('/v1/orgs', { body: JSON.stringify({ name: `Test Org ${nanoid(10)}` }), headers: { 'content-type': 'application/json', cookie }, method: 'POST', }) const { id } = (await response.json()) as { id: string } return { cookie, orgId: id } } /** Baseline mainnet production sponsorship row attributed to the org. */ function sponsorship(orgId: string) { return { apiKeyId: 'key_1', billable: true, chainId: Viem.chainId.mainnet, environment: 'production', orgId, projectId: 'prj_1', transaction: `0x76${'cc'.repeat(16)}`, transactionHash: `0x${'aa'.repeat(32)}`, } as const } /** Fake analytics store recording read queries; every query returns no rows. */ function analyticsStore() { const query = vi.fn((_sql: string) => Promise.resolve([] as never[])) const store = { insert: () => Promise.resolve(), migrate: () => Promise.resolve(), query, } satisfies Analytics.Analytics return { query, store } } /** Usage response mapped from a store with no rows over the default window. */ const emptyUsage = { byKey: [], byRoute: [], byStatus: [], from: '2026-01-02T00:00:00.000Z', interval: 'day', series: [], to: '2026-02-01T00:00:00.000Z', totals: { averageDurationMs: 0, errors: 0, requests: 0 }, } afterEach(() => vi.useRealTimers()) test('publishes generator-ready OpenAPI contracts', async () => { const spec = await (await createApp().request('/openapi.json')).json() const requests = spec.paths['/v1/orgs/{orgId}/usage/requests'].get const sponsorships = spec.paths['/v1/orgs/{orgId}/usage/sponsorships'].get expect({ components: [ 'RequestUsage', 'RequestUsageByKey', 'RequestUsageByRoute', 'RequestUsageByStatus', 'RequestUsageSeriesPoint', 'RequestUsageTotals', 'SponsorshipUsage', 'SponsorshipUsageBucket', ].filter((name) => spec.components.schemas[name]), requests: { errors: { 400: requests.responses[400].content['application/json'].schema, 404: requests.responses[404].content['application/json'].schema, 501: requests.responses[501].content['application/json'].schema, }, operationId: requests.operationId, response: requests.responses[200].content['application/json'].schema, }, sponsorships: { errors: { 400: sponsorships.responses[400].content['application/json'].schema, 404: sponsorships.responses[404].content['application/json'].schema, }, operationId: sponsorships.operationId, response: sponsorships.responses[200].content['application/json'].schema, }, timestamps: { requestFrom: spec.components.schemas.RequestUsage.properties.from.format, requestPoint: spec.components.schemas.RequestUsageSeriesPoint.properties.time.format, requestTo: spec.components.schemas.RequestUsage.properties.to.format, sponsorship: spec.components.schemas.SponsorshipUsageBucket.properties.timestamp.format, }, }).toMatchInlineSnapshot(` { "components": [ "RequestUsage", "RequestUsageByKey", "RequestUsageByRoute", "RequestUsageByStatus", "RequestUsageSeriesPoint", "RequestUsageTotals", "SponsorshipUsage", "SponsorshipUsageBucket", ], "requests": { "errors": { "400": { "$ref": "#/components/schemas/ApiKeyMalformedOrParamInvalidOrQueryInvalidError", }, "404": { "$ref": "#/components/schemas/OrganizationNotFoundError", }, "501": { "$ref": "#/components/schemas/AnalyticsUnconfiguredError", }, }, "operationId": "getRequestUsage", "response": { "$ref": "#/components/schemas/RequestUsage", }, }, "sponsorships": { "errors": { "400": { "$ref": "#/components/schemas/ApiKeyMalformedOrParamInvalidOrQueryInvalidError", }, "404": { "$ref": "#/components/schemas/OrganizationNotFoundError", }, }, "operationId": "getSponsorshipUsage", "response": { "$ref": "#/components/schemas/SponsorshipUsage", }, }, "timestamps": { "requestFrom": "date-time", "requestPoint": "date-time", "requestTo": "date-time", "sponsorship": "date-time", }, } `) }) describe('GET /orgs/:orgId/usage/requests', () => { test('scopes to one project with environment-filtered attribution and legacy key ids', async () => { vi.useFakeTimers({ now: new Date('2026-02-01T00:00:00.000Z') }) const { query, store } = analyticsStore() const app = createApp({ analytics: store }) const cookie = await session(app) const { org, project } = await fixture(app, cookie) const minted = await app.request( `/v1/orgs/${org.id}/projects/${project.id}/api-keys`, superAdminJson('POST', { environment: 'sandbox', scopes: ['data:read'] }), ) const key = await TestApp.json(minted, Keys.schema.createApiKey.Response) const mintedProduction = await app.request( `/v1/orgs/${org.id}/projects/${project.id}/api-keys`, superAdminJson('POST', { environment: 'production', scopes: ['data:read'] }), ) const productionKey = await TestApp.json(mintedProduction, Keys.schema.createApiKey.Response) const response = await app.request( `/v1/orgs/${org.id}/usage/requests?environment=sandbox&projectId=${project.id}`, { headers: { cookie } }, ) expect(response.status).toBe(200) expect(await TestApp.json(response, Usage.schema.getRequestUsage.Response)).toEqual(emptyUsage) // Series, totals, byKey, byRoute, and byStatus each issue one query. expect(query).toHaveBeenCalledTimes(5) const sql = query.mock.calls[0]![0] expect(sql).toContain(`org_id = '${org.id}'`) expect(sql).toContain(`project_id = '${project.id}'`) expect(sql).toContain(`key_environment = 'sandbox'`) // Legacy rows without a project id attribute via environment-scoped key ids. expect(sql).toContain(`project_id IS NULL AND key_id IN ('${key.id}')`) expect(sql).not.toContain(productionKey.id) expect(sql).toContain(`parseDateTime64BestEffort('2026-01-02T00:00:00.000Z', 3, 'UTC')`) expect(sql).toContain(`parseDateTime64BestEffort('2026-02-01T00:00:00.000Z', 3, 'UTC')`) }) test('covers the whole organization when no project is given', async () => { vi.useFakeTimers({ now: new Date('2026-02-01T00:00:00.000Z') }) const { query, store } = analyticsStore() const app = createApp({ analytics: store }) const cookie = await session(app) const { org, project } = await fixture(app, cookie) const minted = await app.request( `/v1/orgs/${org.id}/projects/${project.id}/api-keys`, superAdminJson('POST', { environment: 'sandbox', scopes: ['data:read'] }), ) const key = await TestApp.json(minted, Keys.schema.createApiKey.Response) const response = await app.request(`/v1/orgs/${org.id}/usage/requests?environment=sandbox`, { headers: { cookie }, }) expect(response.status).toBe(200) const sql = query.mock.calls[0]![0] expect(sql).toContain(`org_id = '${org.id}'`) expect(sql).toContain(`key_environment = 'sandbox'`) expect(sql).toContain(`key_id IN ('${key.id}')`) // Org-wide reads must not narrow to a project. expect(sql).not.toContain(`project_id = '${project.id}'`) }) test('rejects inverted time ranges before reading analytics', async () => { const { query, store } = analyticsStore() const app = createApp({ analytics: store }) const cookie = await session(app) const { org } = await fixture(app, cookie) const response = await app.request( `/v1/orgs/${org.id}/usage/requests?from=2026-02-01T00:00:00Z&to=2026-01-01T00:00:00Z`, { headers: { cookie } }, ) expect(response.status).toBe(400) expect(query).not.toHaveBeenCalled() }) test('returns 501 when analytics is not configured', async () => { const app = createApp() const cookie = await session(app) const { org } = await fixture(app, cookie) const response = await app.request(`/v1/orgs/${org.id}/usage/requests`, { headers: { cookie }, }) expect(response.status).toBe(501) }) }) describe('GET /orgs/:orgId/usage/sponsorships', () => { test('behavior: buckets sponsorships by day with committed fees', async () => { const db = TestApp.database() const app = createApp({ db }) const { cookie, orgId } = await seeded(app) // Fake only `Date` so seeded rows land on known days; real timers keep // the pg driver working. vi.useFakeTimers({ now: new Date('2026-06-01T08:00:00Z'), toFake: ['Date'] }) try { // Day one: a finalized row (actual fee) plus a pending row (fee cap). const finalized = await SponsoredTransactions.upsert(db, { ...sponsorship(orgId), signPayload: `0x${'21'.repeat(32)}` }) // prettier-ignore await SponsoredTransactions.finalize(db, finalized.id, { feeAmount: '100000', finalizedAt: new Date().toISOString() }) // prettier-ignore await SponsoredTransactions.upsert(db, { ...sponsorship(orgId), feeMax: '250000', signPayload: `0x${'22'.repeat(32)}` }) // prettier-ignore // Day two: a failed row counts but contributes no fees. vi.setSystemTime(new Date('2026-06-02T09:00:00Z')) const failed = await SponsoredTransactions.upsert(db, { ...sponsorship(orgId), feeMax: '900000', signPayload: `0x${'23'.repeat(32)}` }) // prettier-ignore await SponsoredTransactions.fail(db, failed.id, new Date().toISOString()) } finally { vi.useRealTimers() } const response = await app.request( `/v1/orgs/${orgId}/usage/sponsorships?from=2026-06-01T00:00:00Z&to=2026-06-03T00:00:00Z`, { headers: { cookie } }, ) expect(response.status).toBe(200) expect(await response.json()).toMatchInlineSnapshot(` { "data": [ { "count": 2, "failed": 0, "feeTotal": { "amount": "0.35", "currency": "usd", }, "timestamp": "2026-06-01T00:00:00.000Z", }, { "count": 1, "failed": 1, "feeTotal": { "amount": "0", "currency": "usd", }, "timestamp": "2026-06-02T00:00:00.000Z", }, ], } `) // The same window at month interval folds into one bucket. const monthly = await app.request( `/v1/orgs/${orgId}/usage/sponsorships?from=2026-06-01T00:00:00Z&to=2026-06-03T00:00:00Z&interval=month`, { headers: { cookie } }, ) expect(await monthly.json()).toMatchInlineSnapshot(` { "data": [ { "count": 3, "failed": 1, "feeTotal": { "amount": "0.35", "currency": "usd", }, "timestamp": "2026-06-01T00:00:00.000Z", }, ], } `) }) test('behavior: defaults to the trailing 30 days', async () => { const db = TestApp.database() const app = createApp({ db }) const { cookie, orgId } = await seeded(app) await SponsoredTransactions.upsert(db, { ...sponsorship(orgId), feeMax: '500000', signPayload: `0x${'31'.repeat(32)}` }) // prettier-ignore const response = await app.request(`/v1/orgs/${orgId}/usage/sponsorships`, { headers: { cookie }, }) expect(response.status).toBe(200) const body = (await response.json()) as { data: { timestamp: string }[] } // The bucket timestamp tracks the test run's date; snapshot the rest. expect(body.data.map(({ timestamp: _, ...rest }) => rest)).toMatchInlineSnapshot(` [ { "count": 1, "failed": 0, "feeTotal": { "amount": "0.5", "currency": "usd", }, }, ] `) }) test('behavior: filters by environment and project', async () => { const db = TestApp.database() const app = createApp({ db }) const { cookie, orgId } = await seeded(app) vi.useFakeTimers({ now: new Date('2026-06-01T08:00:00Z'), toFake: ['Date'] }) try { await SponsoredTransactions.upsert(db, { ...sponsorship(orgId), feeMax: '100000', signPayload: `0x${'41'.repeat(32)}` }) // prettier-ignore await SponsoredTransactions.upsert(db, { ...sponsorship(orgId), billable: false, environment: 'sandbox', feeMax: '300000', projectId: 'prj_2', signPayload: `0x${'42'.repeat(32)}` }) // prettier-ignore } finally { vi.useRealTimers() } const window = 'from=2026-06-01T00:00:00Z&to=2026-06-02T00:00:00Z' const production = await app.request( `/v1/orgs/${orgId}/usage/sponsorships?${window}&environment=production`, { headers: { cookie } }, ) expect(await production.json()).toMatchInlineSnapshot(` { "data": [ { "count": 1, "failed": 0, "feeTotal": { "amount": "0.1", "currency": "usd", }, "timestamp": "2026-06-01T00:00:00.000Z", }, ], } `) const project = await app.request( `/v1/orgs/${orgId}/usage/sponsorships?${window}&projectId=prj_2`, { headers: { cookie } }, ) expect(await project.json()).toMatchInlineSnapshot(` { "data": [ { "count": 1, "failed": 0, "feeTotal": { "amount": "0.3", "currency": "usd", }, "timestamp": "2026-06-01T00:00:00.000Z", }, ], } `) }) test('behavior: rejects inverted or oversized windows', async () => { const app = createApp() const { cookie, orgId } = await seeded(app) const inverted = await app.request( `/v1/orgs/${orgId}/usage/sponsorships?from=2026-06-02T00:00:00Z&to=2026-06-01T00:00:00Z`, { headers: { cookie } }, ) expect(inverted.status).toBe(400) expect(((await inverted.json()) as { error: { code: string; message: string } }).error) .toMatchInlineSnapshot(` { "code": "query_invalid", "message": "\`from\` must be before \`to\`", } `) const oversized = await app.request( `/v1/orgs/${orgId}/usage/sponsorships?from=2025-01-01T00:00:00Z&to=2026-06-01T00:00:00Z`, { headers: { cookie } }, ) expect(oversized.status).toBe(400) // Hour intervals cap tighter: 31 days keeps the bucket count bounded. const hourly = await app.request( `/v1/orgs/${orgId}/usage/sponsorships?from=2026-04-01T00:00:00Z&to=2026-06-01T00:00:00Z&interval=hour`, { headers: { cookie } }, ) expect(hourly.status).toBe(400) expect( ((await hourly.json()) as { error: { message: string } }).error.message, ).toMatchInlineSnapshot(`"The window may span at most 31 days at \`hour\` interval"`) const bogus = await app.request(`/v1/orgs/${orgId}/usage/sponsorships?bogus=1`, { headers: { cookie }, }) expect(bogus.status).toBe(400) }) test('behavior: foreign sessions read 404', async () => { const app = createApp() const { orgId } = await seeded(app) const other = await session(app) const response = await app.request(`/v1/orgs/${orgId}/usage/sponsorships`, { headers: { cookie: other }, }) expect(response.status).toBe(404) }) test('behavior: the super admin reads any org', async () => { const app = createApp() const { orgId } = await seeded(app) const response = await app.request(`/v1/orgs/${orgId}/usage/sponsorships`, as(secret)) expect(response.status).toBe(200) expect(((await response.json()) as { data: unknown[] }).data).toStrictEqual([]) }) })