tapimo 0.2.0 → 0.4.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.
- package/README.md +36 -8
- package/dist/ApiKey.d.ts +1 -0
- package/dist/ApiKey.d.ts.map +1 -1
- package/dist/ApiKey.js +5 -2
- package/dist/ApiKey.js.map +1 -1
- package/dist/ApiKeys.d.ts +28 -1
- package/dist/ApiKeys.d.ts.map +1 -1
- package/dist/ApiKeys.js +34 -1
- package/dist/ApiKeys.js.map +1 -1
- package/dist/App.d.ts +7532 -4850
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +11 -3
- package/dist/App.js.map +1 -1
- package/dist/Scope.d.ts +11 -2
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +14 -3
- package/dist/Scope.js.map +1 -1
- package/dist/admin/App.d.ts +63 -53
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +7 -0
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/scopes.d.ts +8 -0
- package/dist/admin/apps/scopes.d.ts.map +1 -1
- package/dist/admin/apps/scopes.js +3 -0
- package/dist/admin/apps/scopes.js.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +57 -57
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.js +13 -13
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/analytics/Analytics.d.ts +151 -0
- package/dist/analytics/Analytics.d.ts.map +1 -0
- package/dist/analytics/Analytics.js +212 -0
- package/dist/analytics/Analytics.js.map +1 -0
- package/dist/analytics/tables/requestEvents.d.ts +215 -0
- package/dist/analytics/tables/requestEvents.d.ts.map +1 -0
- package/dist/analytics/tables/requestEvents.js +264 -0
- package/dist/analytics/tables/requestEvents.js.map +1 -0
- package/dist/apps/data/App.d.ts +1049 -624
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +36 -0
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/FundingRouteProviders.d.ts +54 -0
- package/dist/apps/data/FundingRouteProviders.d.ts.map +1 -0
- package/dist/apps/data/FundingRouteProviders.js +355 -0
- package/dist/apps/data/FundingRouteProviders.js.map +1 -0
- package/dist/apps/data/index.d.ts +1 -0
- package/dist/apps/data/index.d.ts.map +1 -1
- package/dist/apps/data/index.js +1 -0
- package/dist/apps/data/index.js.map +1 -1
- package/dist/apps/data/routes/balances.d.ts +45 -45
- package/dist/apps/data/routes/coingecko.d.ts +176 -176
- package/dist/apps/data/routes/exchanges.d.ts +455 -455
- package/dist/apps/data/routes/fee-amm.d.ts +101 -101
- package/dist/apps/data/routes/funding.d.ts +843 -0
- package/dist/apps/data/routes/funding.d.ts.map +1 -0
- package/dist/apps/data/routes/funding.js +443 -0
- package/dist/apps/data/routes/funding.js.map +1 -0
- package/dist/apps/data/routes/indexer.d.ts +5 -5
- package/dist/apps/data/routes/receipts.d.ts.map +1 -1
- package/dist/apps/data/routes/receipts.js +2 -8
- package/dist/apps/data/routes/receipts.js.map +1 -1
- package/dist/apps/data/routes/rpc.d.ts +29 -36
- package/dist/apps/data/routes/rpc.d.ts.map +1 -1
- package/dist/apps/data/routes/rpc.js +29 -1
- package/dist/apps/data/routes/rpc.js.map +1 -1
- package/dist/apps/data/routes/tokenlist.d.ts +36 -36
- package/dist/apps/data/routes/transfers.d.ts +56 -56
- package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
- package/dist/apps/data/routes/webhooks.d.ts +45 -45
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +8 -8
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/docs/App.d.ts +5 -3
- package/dist/apps/docs/App.d.ts.map +1 -1
- package/dist/apps/docs/App.js +28 -96
- package/dist/apps/docs/App.js.map +1 -1
- package/dist/apps/docs/reference.d.ts +21 -0
- package/dist/apps/docs/reference.d.ts.map +1 -0
- package/dist/apps/docs/reference.js +112 -0
- package/dist/apps/docs/reference.js.map +1 -0
- package/dist/apps/index.d.ts +3 -2
- package/dist/apps/index.d.ts.map +1 -1
- package/dist/apps/index.js +3 -2
- package/dist/apps/index.js.map +1 -1
- package/dist/apps/{platform → management}/App.d.ts +3015 -738
- package/dist/apps/management/App.d.ts.map +1 -0
- package/dist/apps/{platform → management}/App.js +20 -10
- package/dist/apps/management/App.js.map +1 -0
- package/dist/apps/management/Billing.d.ts +279 -0
- package/dist/apps/management/Billing.d.ts.map +1 -0
- package/dist/apps/management/Billing.js +477 -0
- package/dist/apps/management/Billing.js.map +1 -0
- package/dist/apps/management/index.d.ts +3 -0
- package/dist/apps/management/index.d.ts.map +1 -0
- package/dist/apps/management/index.js +3 -0
- package/dist/apps/management/index.js.map +1 -0
- package/dist/apps/{platform → management}/routes/api-keys.d.ts +341 -49
- package/dist/apps/management/routes/api-keys.d.ts.map +1 -0
- package/dist/apps/{platform → management}/routes/api-keys.js +116 -14
- package/dist/apps/management/routes/api-keys.js.map +1 -0
- package/dist/apps/management/routes/billing.d.ts +1520 -0
- package/dist/apps/management/routes/billing.d.ts.map +1 -0
- package/dist/apps/management/routes/billing.js +795 -0
- package/dist/apps/management/routes/billing.js.map +1 -0
- package/dist/apps/{platform → management}/routes/invitations.d.ts +202 -84
- package/dist/apps/management/routes/invitations.d.ts.map +1 -0
- package/dist/apps/{platform → management}/routes/invitations.js +59 -1
- package/dist/apps/management/routes/invitations.js.map +1 -0
- package/dist/apps/{platform → management}/routes/me.d.ts +17 -17
- package/dist/apps/management/routes/me.d.ts.map +1 -0
- package/dist/apps/management/routes/me.js.map +1 -0
- package/dist/apps/management/routes/members.d.ts.map +1 -0
- package/dist/apps/management/routes/members.js.map +1 -0
- package/dist/apps/{platform → management}/routes/orgs.d.ts +50 -36
- package/dist/apps/management/routes/orgs.d.ts.map +1 -0
- package/dist/apps/{platform → management}/routes/orgs.js +14 -3
- package/dist/apps/management/routes/orgs.js.map +1 -0
- package/dist/apps/{platform → management}/routes/projects.d.ts +97 -97
- package/dist/apps/{platform → management}/routes/projects.d.ts.map +1 -1
- package/dist/apps/management/routes/projects.js.map +1 -0
- package/dist/apps/management/routes/scopes.d.ts +107 -0
- package/dist/apps/management/routes/scopes.d.ts.map +1 -0
- package/dist/apps/management/routes/scopes.js +66 -0
- package/dist/apps/management/routes/scopes.js.map +1 -0
- package/dist/apps/management/routes/usage.d.ts +588 -0
- package/dist/apps/management/routes/usage.d.ts.map +1 -0
- package/dist/apps/management/routes/usage.js +391 -0
- package/dist/apps/management/routes/usage.js.map +1 -0
- package/dist/apps/mcp.d.ts +76 -0
- package/dist/apps/mcp.d.ts.map +1 -0
- package/dist/apps/mcp.js +188 -0
- package/dist/apps/mcp.js.map +1 -0
- package/dist/apps/relay/App.d.ts +4 -1
- package/dist/apps/relay/App.d.ts.map +1 -1
- package/dist/apps/relay/App.js +118 -10
- package/dist/apps/relay/App.js.map +1 -1
- package/dist/apps/relay/Sponsorships.d.ts.map +1 -1
- package/dist/apps/relay/Sponsorships.js +2 -1
- package/dist/apps/relay/Sponsorships.js.map +1 -1
- package/dist/cli/Cli.d.ts +8 -1
- package/dist/cli/Cli.d.ts.map +1 -1
- package/dist/cli/Cli.js +11 -3
- package/dist/cli/Cli.js.map +1 -1
- package/dist/cli/commands/admin.d.ts +4 -0
- package/dist/cli/commands/admin.d.ts.map +1 -1
- package/dist/cli/commands/admin.js +80 -1
- package/dist/cli/commands/admin.js.map +1 -1
- package/dist/db/Db.d.ts +7 -0
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +126 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +79 -0
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +88 -2
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/billingSettings.d.ts +55 -0
- package/dist/db/tables/billingSettings.d.ts.map +1 -0
- package/dist/db/tables/billingSettings.js +70 -0
- package/dist/db/tables/billingSettings.js.map +1 -0
- package/dist/db/tables/invitations.js +2 -2
- package/dist/db/tables/invitations.js.map +1 -1
- package/dist/db/tables/organizations.d.ts +6 -2
- package/dist/db/tables/organizations.d.ts.map +1 -1
- package/dist/db/tables/organizations.js +10 -4
- package/dist/db/tables/organizations.js.map +1 -1
- package/dist/db/tables/projects.js +2 -2
- package/dist/db/tables/projects.js.map +1 -1
- package/dist/db/tables/requestUsage.d.ts +121 -0
- package/dist/db/tables/requestUsage.d.ts.map +1 -0
- package/dist/db/tables/requestUsage.js +150 -0
- package/dist/db/tables/requestUsage.js.map +1 -0
- package/dist/db/tables/sponsoredTransactions.d.ts +119 -0
- package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
- package/dist/db/tables/sponsoredTransactions.js +155 -2
- package/dist/db/tables/sponsoredTransactions.js.map +1 -1
- package/dist/db/tables/stripeCustomers.d.ts +81 -0
- package/dist/db/tables/stripeCustomers.d.ts.map +1 -0
- package/dist/db/tables/stripeCustomers.js +120 -0
- package/dist/db/tables/stripeCustomers.js.map +1 -0
- package/dist/db/tables/users.js +2 -2
- package/dist/db/tables/users.js.map +1 -1
- package/dist/handlers/internal/sponsorship.d.ts +5 -2
- package/dist/handlers/internal/sponsorship.d.ts.map +1 -1
- package/dist/handlers/internal/sponsorship.js +19 -5
- package/dist/handlers/internal/sponsorship.js.map +1 -1
- package/dist/handlers/internal/utils.d.ts +10 -3
- package/dist/handlers/internal/utils.d.ts.map +1 -1
- package/dist/handlers/internal/utils.js +14 -6
- package/dist/handlers/internal/utils.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/Auth.d.ts +18 -4
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +149 -77
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/Cache.d.ts.map +1 -1
- package/dist/internal/Cache.js +4 -1
- package/dist/internal/Cache.js.map +1 -1
- package/dist/internal/EdgeCache.d.ts +10 -0
- package/dist/internal/EdgeCache.d.ts.map +1 -1
- package/dist/internal/EdgeCache.js +39 -8
- package/dist/internal/EdgeCache.js.map +1 -1
- package/dist/internal/Fees.d.ts +27 -0
- package/dist/internal/Fees.d.ts.map +1 -0
- package/dist/internal/Fees.js +48 -0
- package/dist/internal/Fees.js.map +1 -0
- package/dist/internal/FundingRoutes.d.ts +915 -0
- package/dist/internal/FundingRoutes.d.ts.map +1 -0
- package/dist/internal/FundingRoutes.js +625 -0
- package/dist/internal/FundingRoutes.js.map +1 -0
- package/dist/internal/Id.d.ts +15 -0
- package/dist/internal/Id.d.ts.map +1 -0
- package/dist/internal/Id.js +22 -0
- package/dist/internal/Id.js.map +1 -0
- package/dist/internal/Log.d.ts +2 -0
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +3 -0
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/OpenApi.d.ts +10 -1
- package/dist/internal/OpenApi.d.ts.map +1 -1
- package/dist/internal/OpenApi.js +8 -1
- package/dist/internal/OpenApi.js.map +1 -1
- package/dist/internal/RateLimit.d.ts +25 -0
- package/dist/internal/RateLimit.d.ts.map +1 -1
- package/dist/internal/RateLimit.js +51 -0
- package/dist/internal/RateLimit.js.map +1 -1
- package/dist/internal/Response.d.ts +1 -1
- package/dist/internal/Response.d.ts.map +1 -1
- package/dist/internal/Schema.d.ts +8 -0
- package/dist/internal/Schema.d.ts.map +1 -1
- package/dist/internal/Schema.js +7 -0
- package/dist/internal/Schema.js.map +1 -1
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +3 -3
- package/dist/internal/Webhooks.js.map +1 -1
- package/package.json +8 -7
- package/src/ApiKey.ts +9 -2
- package/src/ApiKeys.test.ts +51 -0
- package/src/ApiKeys.ts +50 -1
- package/src/App.test.ts +33 -86718
- package/src/App.ts +26 -8
- package/src/Client.test-d.ts +353 -2
- package/src/Scope.ts +16 -3
- package/src/admin/apps/scopes.test.ts +6 -0
- package/src/admin/apps/scopes.ts +3 -0
- package/src/admin/ui/src/components/Page.tsx +2 -3
- package/src/admin/ui/src/routes/__root.tsx +2 -2
- package/src/admin/ui/src/routes/api-keys.tsx +2 -2
- package/src/admin/ui.generated.ts +13 -13
- package/src/analytics/Analytics.test.ts +527 -0
- package/src/analytics/Analytics.ts +318 -0
- package/src/analytics/tables/requestEvents.ts +523 -0
- package/src/apps/composition.test.ts +26 -13
- package/src/apps/data/App.test.ts +72 -0
- package/src/apps/data/App.ts +37 -0
- package/src/apps/data/FundingRouteProviders.test.ts +281 -0
- package/src/apps/data/FundingRouteProviders.ts +459 -0
- package/src/apps/data/index.ts +1 -0
- package/src/apps/data/routes/funding.test.ts +314 -0
- package/src/apps/data/routes/funding.ts +698 -0
- package/src/apps/data/routes/receipts.ts +2 -9
- package/src/apps/data/routes/rpc.test.ts +78 -2
- package/src/apps/data/routes/rpc.ts +31 -1
- package/src/apps/data/routes/webhooks.test.ts +1 -1
- package/src/apps/data/routes/webhooks.ts +11 -8
- package/src/apps/docs/App.ts +30 -101
- package/src/apps/docs/reference.ts +132 -0
- package/src/apps/index.ts +3 -2
- package/src/apps/{platform → management}/App.ts +56 -17
- package/src/apps/management/Billing.test.ts +530 -0
- package/src/apps/management/Billing.ts +682 -0
- package/src/apps/management/index.ts +2 -0
- package/src/apps/{platform → management}/routes/api-keys.test.ts +172 -9
- package/src/apps/{platform → management}/routes/api-keys.ts +137 -21
- package/src/apps/management/routes/billing.test.ts +1100 -0
- package/src/apps/management/routes/billing.ts +966 -0
- package/src/apps/{platform → management}/routes/invitations.test.ts +55 -7
- package/src/apps/{platform → management}/routes/invitations.ts +69 -1
- package/src/apps/management/routes/me.test.ts +351 -0
- package/src/apps/{platform → management}/routes/orgs.ts +26 -4
- package/src/apps/management/routes/scopes.test.ts +29 -0
- package/src/apps/management/routes/scopes.ts +85 -0
- package/src/apps/management/routes/usage.test.ts +422 -0
- package/src/apps/management/routes/usage.ts +527 -0
- package/src/apps/mcp.test.ts +206 -0
- package/src/apps/mcp.ts +250 -0
- package/src/apps/relay/App.test.ts +457 -8
- package/src/apps/relay/App.ts +120 -12
- package/src/apps/relay/Sponsorships.test.ts +1 -1
- package/src/apps/relay/Sponsorships.ts +2 -1
- package/src/cli/Cli.test.ts +25 -2
- package/src/cli/Cli.ts +17 -2
- package/src/cli/commands/admin.test.ts +83 -0
- package/src/cli/commands/admin.ts +89 -1
- package/src/db/Db.ts +137 -0
- package/src/db/Schema.ts +104 -2
- package/src/db/tables/billingSettings.test.ts +87 -0
- package/src/db/tables/billingSettings.ts +108 -0
- package/src/db/tables/invitations.ts +2 -2
- package/src/db/tables/organizations.test.ts +1 -1
- package/src/db/tables/organizations.ts +15 -5
- package/src/db/tables/projects.ts +2 -2
- package/src/db/tables/requestUsage.ts +231 -0
- package/src/db/tables/sponsoredTransactions.test.ts +108 -1
- package/src/db/tables/sponsoredTransactions.ts +230 -2
- package/src/db/tables/stripeCustomers.test.ts +109 -0
- package/src/db/tables/stripeCustomers.ts +162 -0
- package/src/db/tables/users.ts +2 -2
- package/src/handlers/internal/sponsorship.ts +34 -7
- package/src/handlers/internal/utils.ts +19 -7
- package/src/index.ts +1 -0
- package/src/internal/Auth.test.ts +175 -25
- package/src/internal/Auth.ts +176 -87
- package/src/internal/Cache.ts +5 -1
- package/src/internal/EdgeCache.test.ts +27 -4
- package/src/internal/EdgeCache.ts +47 -9
- package/src/internal/Fees.test.ts +32 -0
- package/src/internal/Fees.ts +51 -0
- package/src/internal/FundingRoutes.test.ts +213 -0
- package/src/internal/FundingRoutes.ts +898 -0
- package/src/internal/Id.test.ts +17 -0
- package/src/internal/Id.ts +24 -0
- package/src/internal/Log.test.ts +1 -0
- package/src/internal/Log.ts +5 -0
- package/src/internal/OpenApi.ts +12 -1
- package/src/internal/RateLimit.test.ts +84 -0
- package/src/internal/RateLimit.ts +70 -0
- package/src/internal/Response.ts +1 -1
- package/src/internal/Schema.ts +8 -0
- package/src/internal/Webhooks.test.ts +3 -3
- package/src/internal/Webhooks.ts +3 -3
- package/dist/apps/platform/App.d.ts.map +0 -1
- package/dist/apps/platform/App.js.map +0 -1
- package/dist/apps/platform/index.d.ts +0 -2
- package/dist/apps/platform/index.d.ts.map +0 -1
- package/dist/apps/platform/index.js +0 -2
- package/dist/apps/platform/index.js.map +0 -1
- package/dist/apps/platform/routes/api-keys.d.ts.map +0 -1
- package/dist/apps/platform/routes/api-keys.js.map +0 -1
- package/dist/apps/platform/routes/invitations.d.ts.map +0 -1
- package/dist/apps/platform/routes/invitations.js.map +0 -1
- package/dist/apps/platform/routes/me.d.ts.map +0 -1
- package/dist/apps/platform/routes/me.js.map +0 -1
- package/dist/apps/platform/routes/members.d.ts.map +0 -1
- package/dist/apps/platform/routes/members.js.map +0 -1
- package/dist/apps/platform/routes/orgs.d.ts.map +0 -1
- package/dist/apps/platform/routes/orgs.js.map +0 -1
- package/dist/apps/platform/routes/projects.js.map +0 -1
- package/src/apps/platform/index.ts +0 -1
- package/src/apps/platform/routes/me.test.ts +0 -121
- /package/dist/apps/{platform → management}/routes/me.js +0 -0
- /package/dist/apps/{platform → management}/routes/members.d.ts +0 -0
- /package/dist/apps/{platform → management}/routes/members.js +0 -0
- /package/dist/apps/{platform → management}/routes/projects.js +0 -0
- /package/src/apps/{platform → management}/routes/me.ts +0 -0
- /package/src/apps/{platform → management}/routes/members.test.ts +0 -0
- /package/src/apps/{platform → management}/routes/members.ts +0 -0
- /package/src/apps/{platform → management}/routes/orgs.test.ts +0 -0
- /package/src/apps/{platform → management}/routes/projects.test.ts +0 -0
- /package/src/apps/{platform → management}/routes/projects.ts +0 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Hono } from 'hono'
|
|
2
|
+
import * as z from 'zod/mini'
|
|
3
|
+
|
|
4
|
+
import type * as App from '../../../App.js'
|
|
5
|
+
import * as Auth from '../../../internal/Auth.js'
|
|
6
|
+
import * as Cache from '../../../internal/Cache.js'
|
|
7
|
+
import * as OpenApi from '../../../internal/OpenApi.js'
|
|
8
|
+
import * as Response from '../../../internal/Response.js'
|
|
9
|
+
import * as Scope from '../../../Scope.js'
|
|
10
|
+
|
|
11
|
+
/** Zod schemas owned by the scope-catalog resource. */
|
|
12
|
+
export namespace schema {
|
|
13
|
+
/** Schemas for the getScopes operation. */
|
|
14
|
+
export namespace getScopes {
|
|
15
|
+
/** A single issuable API-key scope. */
|
|
16
|
+
export const Entry = z
|
|
17
|
+
.object({
|
|
18
|
+
description: z
|
|
19
|
+
.string()
|
|
20
|
+
.check(
|
|
21
|
+
z.describe('Human-readable explanation of what the scope grants.'),
|
|
22
|
+
z.meta({ examples: ['Read webhook subscriptions.'] }),
|
|
23
|
+
),
|
|
24
|
+
scope: z
|
|
25
|
+
.enum(Scope.catalog.map((entry) => entry.scope))
|
|
26
|
+
.check(
|
|
27
|
+
z.describe('Scope identifier used in API-key grants and route policies.'),
|
|
28
|
+
z.meta({ examples: ['data:read'] }),
|
|
29
|
+
),
|
|
30
|
+
selfServe: z
|
|
31
|
+
.boolean()
|
|
32
|
+
.check(
|
|
33
|
+
z.describe('Whether a session may self-mint this scope (else super-admin-only).'),
|
|
34
|
+
z.meta({ examples: [true] }),
|
|
35
|
+
),
|
|
36
|
+
})
|
|
37
|
+
.check(z.describe('A single issuable API-key scope.'))
|
|
38
|
+
|
|
39
|
+
/** Response body for the scope catalog. */
|
|
40
|
+
export const Response = z
|
|
41
|
+
.object({
|
|
42
|
+
data: z.array(Entry).check(z.describe('Issuable API-key scopes.')),
|
|
43
|
+
})
|
|
44
|
+
.check(z.describe('Response body for the scope catalog.'))
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Mounts the `/scopes` resource: the issuable API-key scope vocabulary, served
|
|
50
|
+
* from {@link Scope.catalog}. Public catalog data: anonymous callers are served
|
|
51
|
+
* within the public quota; the session lane lets signed-in consoles skip it.
|
|
52
|
+
*/
|
|
53
|
+
export function scopes() {
|
|
54
|
+
return new Hono<App.Environment>().get(
|
|
55
|
+
'/v1/scopes',
|
|
56
|
+
Auth.policy({ public: true, session: true }),
|
|
57
|
+
OpenApi.describeRoute({
|
|
58
|
+
description: 'See which scopes can be granted to API keys.',
|
|
59
|
+
operationId: 'getScopes',
|
|
60
|
+
responses: OpenApi.responses({
|
|
61
|
+
success: { description: 'Issuable scope catalog.', schema: schema.getScopes.Response },
|
|
62
|
+
}),
|
|
63
|
+
summary: 'List scopes',
|
|
64
|
+
tags: ['API Keys'],
|
|
65
|
+
}),
|
|
66
|
+
Cache.response<App.Environment>({
|
|
67
|
+
cacheControl: Cache.policies.stable,
|
|
68
|
+
name: 'tempo-api:scopes:v1',
|
|
69
|
+
key: (c) => {
|
|
70
|
+
const url = new URL(c.req.url)
|
|
71
|
+
url.search = ''
|
|
72
|
+
return url.toString()
|
|
73
|
+
},
|
|
74
|
+
}),
|
|
75
|
+
(c) => {
|
|
76
|
+
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
77
|
+
return c.json(
|
|
78
|
+
Response.validated(schema.getScopes.Response, {
|
|
79
|
+
data: Scope.catalog.map((entry) => ({ ...entry })),
|
|
80
|
+
}),
|
|
81
|
+
200,
|
|
82
|
+
)
|
|
83
|
+
},
|
|
84
|
+
)
|
|
85
|
+
}
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
import { nanoid } from 'nanoid'
|
|
2
|
+
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
|
|
3
|
+
|
|
4
|
+
import type * as Analytics from '../../../analytics/Analytics.js'
|
|
5
|
+
import * as TestApp from '../../../../test/App.js'
|
|
6
|
+
import * as SponsoredTransactions from '../../../db/tables/sponsoredTransactions.js'
|
|
7
|
+
import * as Viem from '../../../internal/Viem.js'
|
|
8
|
+
import * as Keys from './api-keys.js'
|
|
9
|
+
import * as Orgs from './orgs.js'
|
|
10
|
+
import * as Projects from './projects.js'
|
|
11
|
+
import * as Usage from './usage.js'
|
|
12
|
+
|
|
13
|
+
/** Origin pinned for SIWE domain binding; Hono test requests use this host. */
|
|
14
|
+
const origin = 'http://localhost'
|
|
15
|
+
|
|
16
|
+
/** Super admin secret configured on the test app. */
|
|
17
|
+
const secret = 'tempo:sk:b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1'
|
|
18
|
+
|
|
19
|
+
function createApp(
|
|
20
|
+
options: {
|
|
21
|
+
analytics?: Analytics.Analytics | undefined
|
|
22
|
+
db?: TestApp.create.Options['db'] | undefined
|
|
23
|
+
} = {},
|
|
24
|
+
) {
|
|
25
|
+
return TestApp.create({
|
|
26
|
+
...(options.analytics === undefined ? {} : { analytics: options.analytics }),
|
|
27
|
+
auth: { superAdmin: { secret } },
|
|
28
|
+
db: options.db ?? TestApp.database(),
|
|
29
|
+
session: { wallet: { origin } },
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** RequestInit presenting the given token. */
|
|
34
|
+
function as(token: string) {
|
|
35
|
+
return { headers: { 'tempo-api-key': token } } as const
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** RequestInit for a JSON mutation carrying the session cookie. */
|
|
39
|
+
function json(method: 'POST', cookie: string, body: unknown) {
|
|
40
|
+
return {
|
|
41
|
+
body: JSON.stringify(body),
|
|
42
|
+
headers: { 'content-type': 'application/json', cookie },
|
|
43
|
+
method,
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** RequestInit for a JSON mutation authenticated as the super admin. */
|
|
48
|
+
function superAdminJson(method: 'POST', body: unknown) {
|
|
49
|
+
return {
|
|
50
|
+
body: JSON.stringify(body),
|
|
51
|
+
headers: { 'content-type': 'application/json', 'tempo-api-key': secret },
|
|
52
|
+
method,
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Signs in a fresh scripted account; returns its session cookie. */
|
|
57
|
+
async function session(app: TestApp.signIn.App) {
|
|
58
|
+
const account = privateKeyToAccount(generatePrivateKey())
|
|
59
|
+
const { cookie } = await TestApp.signIn(app, account)
|
|
60
|
+
return cookie!
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Creates an org and a project owned by the session. */
|
|
64
|
+
async function fixture(app: ReturnType<typeof createApp>, cookie: string) {
|
|
65
|
+
const org = await TestApp.json(
|
|
66
|
+
await app.request('/v1/orgs', json('POST', cookie, { name: 'Acme' })),
|
|
67
|
+
Orgs.schema.Organization,
|
|
68
|
+
)
|
|
69
|
+
const project = await TestApp.json(
|
|
70
|
+
await app.request(`/v1/orgs/${org.id}/projects`, json('POST', cookie, { name: 'Checkout' })),
|
|
71
|
+
Projects.schema.Project,
|
|
72
|
+
)
|
|
73
|
+
return { org, project }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Signs in and creates an org owned by that session; returns the cookie and org id. */
|
|
77
|
+
async function seeded(app: ReturnType<typeof createApp>) {
|
|
78
|
+
const cookie = await session(app)
|
|
79
|
+
const response = await app.request('/v1/orgs', {
|
|
80
|
+
body: JSON.stringify({ name: `Test Org ${nanoid(10)}` }),
|
|
81
|
+
headers: { 'content-type': 'application/json', cookie },
|
|
82
|
+
method: 'POST',
|
|
83
|
+
})
|
|
84
|
+
const { id } = (await response.json()) as { id: string }
|
|
85
|
+
return { cookie, orgId: id }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Baseline mainnet production sponsorship row attributed to the org. */
|
|
89
|
+
function sponsorship(orgId: string) {
|
|
90
|
+
return {
|
|
91
|
+
apiKeyId: 'key_1',
|
|
92
|
+
billable: true,
|
|
93
|
+
chainId: Viem.chainId.mainnet,
|
|
94
|
+
environment: 'production',
|
|
95
|
+
orgId,
|
|
96
|
+
projectId: 'prj_1',
|
|
97
|
+
transaction: `0x76${'cc'.repeat(16)}`,
|
|
98
|
+
transactionHash: `0x${'aa'.repeat(32)}`,
|
|
99
|
+
} as const
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Fake analytics store recording read queries; every query returns no rows. */
|
|
103
|
+
function analyticsStore() {
|
|
104
|
+
const query = vi.fn((_sql: string) => Promise.resolve([] as never[]))
|
|
105
|
+
const store = {
|
|
106
|
+
insert: () => Promise.resolve(),
|
|
107
|
+
migrate: () => Promise.resolve(),
|
|
108
|
+
query,
|
|
109
|
+
} satisfies Analytics.Analytics
|
|
110
|
+
return { query, store }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Usage response mapped from a store with no rows over the default window. */
|
|
114
|
+
const emptyUsage = {
|
|
115
|
+
byKey: [],
|
|
116
|
+
byRoute: [],
|
|
117
|
+
byStatus: [],
|
|
118
|
+
from: '2026-01-02T00:00:00.000Z',
|
|
119
|
+
interval: 'day',
|
|
120
|
+
series: [],
|
|
121
|
+
to: '2026-02-01T00:00:00.000Z',
|
|
122
|
+
totals: { averageDurationMs: 0, errors: 0, requests: 0 },
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
afterEach(() => vi.useRealTimers())
|
|
126
|
+
|
|
127
|
+
describe('GET /orgs/:orgId/usage/requests', () => {
|
|
128
|
+
test('scopes to one project with environment-filtered attribution and legacy key ids', async () => {
|
|
129
|
+
vi.useFakeTimers({ now: new Date('2026-02-01T00:00:00.000Z') })
|
|
130
|
+
const { query, store } = analyticsStore()
|
|
131
|
+
const app = createApp({ analytics: store })
|
|
132
|
+
const cookie = await session(app)
|
|
133
|
+
const { org, project } = await fixture(app, cookie)
|
|
134
|
+
const minted = await app.request(
|
|
135
|
+
`/v1/orgs/${org.id}/projects/${project.id}/api-keys`,
|
|
136
|
+
superAdminJson('POST', { environment: 'sandbox', scopes: ['data:read'] }),
|
|
137
|
+
)
|
|
138
|
+
const key = await TestApp.json(minted, Keys.schema.createApiKey.Response)
|
|
139
|
+
const mintedProduction = await app.request(
|
|
140
|
+
`/v1/orgs/${org.id}/projects/${project.id}/api-keys`,
|
|
141
|
+
superAdminJson('POST', { environment: 'production', scopes: ['data:read'] }),
|
|
142
|
+
)
|
|
143
|
+
const productionKey = await TestApp.json(mintedProduction, Keys.schema.createApiKey.Response)
|
|
144
|
+
|
|
145
|
+
const response = await app.request(
|
|
146
|
+
`/v1/orgs/${org.id}/usage/requests?environment=sandbox&projectId=${project.id}`,
|
|
147
|
+
{ headers: { cookie } },
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
expect(response.status).toBe(200)
|
|
151
|
+
expect(await TestApp.json(response, Usage.schema.getRequestUsage.Response)).toEqual(emptyUsage)
|
|
152
|
+
// Series, totals, byKey, byRoute, and byStatus each issue one query.
|
|
153
|
+
expect(query).toHaveBeenCalledTimes(5)
|
|
154
|
+
const sql = query.mock.calls[0]![0]
|
|
155
|
+
expect(sql).toContain(`org_id = '${org.id}'`)
|
|
156
|
+
expect(sql).toContain(`project_id = '${project.id}'`)
|
|
157
|
+
expect(sql).toContain(`key_environment = 'sandbox'`)
|
|
158
|
+
// Legacy rows without a project id attribute via environment-scoped key ids.
|
|
159
|
+
expect(sql).toContain(`project_id IS NULL AND key_id IN ('${key.id}')`)
|
|
160
|
+
expect(sql).not.toContain(productionKey.id)
|
|
161
|
+
expect(sql).toContain(`parseDateTime64BestEffort('2026-01-02T00:00:00.000Z', 3, 'UTC')`)
|
|
162
|
+
expect(sql).toContain(`parseDateTime64BestEffort('2026-02-01T00:00:00.000Z', 3, 'UTC')`)
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
test('covers the whole organization when no project is given', async () => {
|
|
166
|
+
vi.useFakeTimers({ now: new Date('2026-02-01T00:00:00.000Z') })
|
|
167
|
+
const { query, store } = analyticsStore()
|
|
168
|
+
const app = createApp({ analytics: store })
|
|
169
|
+
const cookie = await session(app)
|
|
170
|
+
const { org, project } = await fixture(app, cookie)
|
|
171
|
+
const minted = await app.request(
|
|
172
|
+
`/v1/orgs/${org.id}/projects/${project.id}/api-keys`,
|
|
173
|
+
superAdminJson('POST', { environment: 'sandbox', scopes: ['data:read'] }),
|
|
174
|
+
)
|
|
175
|
+
const key = await TestApp.json(minted, Keys.schema.createApiKey.Response)
|
|
176
|
+
|
|
177
|
+
const response = await app.request(`/v1/orgs/${org.id}/usage/requests?environment=sandbox`, {
|
|
178
|
+
headers: { cookie },
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
expect(response.status).toBe(200)
|
|
182
|
+
const sql = query.mock.calls[0]![0]
|
|
183
|
+
expect(sql).toContain(`org_id = '${org.id}'`)
|
|
184
|
+
expect(sql).toContain(`key_environment = 'sandbox'`)
|
|
185
|
+
expect(sql).toContain(`key_id IN ('${key.id}')`)
|
|
186
|
+
// Org-wide reads must not narrow to a project.
|
|
187
|
+
expect(sql).not.toContain(`project_id = '${project.id}'`)
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
test('rejects inverted time ranges before reading analytics', async () => {
|
|
191
|
+
const { query, store } = analyticsStore()
|
|
192
|
+
const app = createApp({ analytics: store })
|
|
193
|
+
const cookie = await session(app)
|
|
194
|
+
const { org } = await fixture(app, cookie)
|
|
195
|
+
|
|
196
|
+
const response = await app.request(
|
|
197
|
+
`/v1/orgs/${org.id}/usage/requests?from=2026-02-01T00:00:00Z&to=2026-01-01T00:00:00Z`,
|
|
198
|
+
{ headers: { cookie } },
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
expect(response.status).toBe(400)
|
|
202
|
+
expect(query).not.toHaveBeenCalled()
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
test('returns 501 when analytics is not configured', async () => {
|
|
206
|
+
const app = createApp()
|
|
207
|
+
const cookie = await session(app)
|
|
208
|
+
const { org } = await fixture(app, cookie)
|
|
209
|
+
|
|
210
|
+
const response = await app.request(`/v1/orgs/${org.id}/usage/requests`, {
|
|
211
|
+
headers: { cookie },
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
expect(response.status).toBe(501)
|
|
215
|
+
})
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
describe('GET /orgs/:orgId/usage/sponsorships', () => {
|
|
219
|
+
test('behavior: buckets sponsorships by day with committed fees', async () => {
|
|
220
|
+
const db = TestApp.database()
|
|
221
|
+
const app = createApp({ db })
|
|
222
|
+
const { cookie, orgId } = await seeded(app)
|
|
223
|
+
|
|
224
|
+
// Fake only `Date` so seeded rows land on known days; real timers keep
|
|
225
|
+
// the pg driver working.
|
|
226
|
+
vi.useFakeTimers({ now: new Date('2026-06-01T08:00:00Z'), toFake: ['Date'] })
|
|
227
|
+
try {
|
|
228
|
+
// Day one: a finalized row (actual fee) plus a pending row (fee cap).
|
|
229
|
+
const finalized = await SponsoredTransactions.upsert(db, { ...sponsorship(orgId), signPayload: `0x${'21'.repeat(32)}` }) // prettier-ignore
|
|
230
|
+
await SponsoredTransactions.finalize(db, finalized.id, { feeAmount: '100000', finalizedAt: new Date().toISOString() }) // prettier-ignore
|
|
231
|
+
await SponsoredTransactions.upsert(db, { ...sponsorship(orgId), feeMax: '250000', signPayload: `0x${'22'.repeat(32)}` }) // prettier-ignore
|
|
232
|
+
// Day two: a failed row counts but contributes no fees.
|
|
233
|
+
vi.setSystemTime(new Date('2026-06-02T09:00:00Z'))
|
|
234
|
+
const failed = await SponsoredTransactions.upsert(db, { ...sponsorship(orgId), feeMax: '900000', signPayload: `0x${'23'.repeat(32)}` }) // prettier-ignore
|
|
235
|
+
await SponsoredTransactions.fail(db, failed.id, new Date().toISOString())
|
|
236
|
+
} finally {
|
|
237
|
+
vi.useRealTimers()
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const response = await app.request(
|
|
241
|
+
`/v1/orgs/${orgId}/usage/sponsorships?from=2026-06-01T00:00:00Z&to=2026-06-03T00:00:00Z`,
|
|
242
|
+
{ headers: { cookie } },
|
|
243
|
+
)
|
|
244
|
+
expect(response.status).toBe(200)
|
|
245
|
+
expect(await response.json()).toMatchInlineSnapshot(`
|
|
246
|
+
{
|
|
247
|
+
"data": [
|
|
248
|
+
{
|
|
249
|
+
"count": 2,
|
|
250
|
+
"feeTotal": {
|
|
251
|
+
"amount": "0.35",
|
|
252
|
+
"currency": "usd",
|
|
253
|
+
},
|
|
254
|
+
"timestamp": "2026-06-01T00:00:00.000Z",
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
"count": 1,
|
|
258
|
+
"feeTotal": {
|
|
259
|
+
"amount": "0",
|
|
260
|
+
"currency": "usd",
|
|
261
|
+
},
|
|
262
|
+
"timestamp": "2026-06-02T00:00:00.000Z",
|
|
263
|
+
},
|
|
264
|
+
],
|
|
265
|
+
}
|
|
266
|
+
`)
|
|
267
|
+
|
|
268
|
+
// The same window at month interval folds into one bucket.
|
|
269
|
+
const monthly = await app.request(
|
|
270
|
+
`/v1/orgs/${orgId}/usage/sponsorships?from=2026-06-01T00:00:00Z&to=2026-06-03T00:00:00Z&interval=month`,
|
|
271
|
+
{ headers: { cookie } },
|
|
272
|
+
)
|
|
273
|
+
expect(await monthly.json()).toMatchInlineSnapshot(`
|
|
274
|
+
{
|
|
275
|
+
"data": [
|
|
276
|
+
{
|
|
277
|
+
"count": 3,
|
|
278
|
+
"feeTotal": {
|
|
279
|
+
"amount": "0.35",
|
|
280
|
+
"currency": "usd",
|
|
281
|
+
},
|
|
282
|
+
"timestamp": "2026-06-01T00:00:00.000Z",
|
|
283
|
+
},
|
|
284
|
+
],
|
|
285
|
+
}
|
|
286
|
+
`)
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
test('behavior: defaults to the trailing 30 days', async () => {
|
|
290
|
+
const db = TestApp.database()
|
|
291
|
+
const app = createApp({ db })
|
|
292
|
+
const { cookie, orgId } = await seeded(app)
|
|
293
|
+
await SponsoredTransactions.upsert(db, { ...sponsorship(orgId), feeMax: '500000', signPayload: `0x${'31'.repeat(32)}` }) // prettier-ignore
|
|
294
|
+
|
|
295
|
+
const response = await app.request(`/v1/orgs/${orgId}/usage/sponsorships`, {
|
|
296
|
+
headers: { cookie },
|
|
297
|
+
})
|
|
298
|
+
expect(response.status).toBe(200)
|
|
299
|
+
const body = (await response.json()) as { data: { timestamp: string }[] }
|
|
300
|
+
// The bucket timestamp tracks the test run's date; snapshot the rest.
|
|
301
|
+
expect(body.data.map(({ timestamp: _, ...rest }) => rest)).toMatchInlineSnapshot(`
|
|
302
|
+
[
|
|
303
|
+
{
|
|
304
|
+
"count": 1,
|
|
305
|
+
"feeTotal": {
|
|
306
|
+
"amount": "0.5",
|
|
307
|
+
"currency": "usd",
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
]
|
|
311
|
+
`)
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
test('behavior: filters by environment and project', async () => {
|
|
315
|
+
const db = TestApp.database()
|
|
316
|
+
const app = createApp({ db })
|
|
317
|
+
const { cookie, orgId } = await seeded(app)
|
|
318
|
+
vi.useFakeTimers({ now: new Date('2026-06-01T08:00:00Z'), toFake: ['Date'] })
|
|
319
|
+
try {
|
|
320
|
+
await SponsoredTransactions.upsert(db, { ...sponsorship(orgId), feeMax: '100000', signPayload: `0x${'41'.repeat(32)}` }) // prettier-ignore
|
|
321
|
+
await SponsoredTransactions.upsert(db, { ...sponsorship(orgId), billable: false, environment: 'sandbox', feeMax: '300000', projectId: 'prj_2', signPayload: `0x${'42'.repeat(32)}` }) // prettier-ignore
|
|
322
|
+
} finally {
|
|
323
|
+
vi.useRealTimers()
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const window = 'from=2026-06-01T00:00:00Z&to=2026-06-02T00:00:00Z'
|
|
327
|
+
const production = await app.request(
|
|
328
|
+
`/v1/orgs/${orgId}/usage/sponsorships?${window}&environment=production`,
|
|
329
|
+
{ headers: { cookie } },
|
|
330
|
+
)
|
|
331
|
+
expect(await production.json()).toMatchInlineSnapshot(`
|
|
332
|
+
{
|
|
333
|
+
"data": [
|
|
334
|
+
{
|
|
335
|
+
"count": 1,
|
|
336
|
+
"feeTotal": {
|
|
337
|
+
"amount": "0.1",
|
|
338
|
+
"currency": "usd",
|
|
339
|
+
},
|
|
340
|
+
"timestamp": "2026-06-01T00:00:00.000Z",
|
|
341
|
+
},
|
|
342
|
+
],
|
|
343
|
+
}
|
|
344
|
+
`)
|
|
345
|
+
|
|
346
|
+
const project = await app.request(
|
|
347
|
+
`/v1/orgs/${orgId}/usage/sponsorships?${window}&projectId=prj_2`,
|
|
348
|
+
{ headers: { cookie } },
|
|
349
|
+
)
|
|
350
|
+
expect(await project.json()).toMatchInlineSnapshot(`
|
|
351
|
+
{
|
|
352
|
+
"data": [
|
|
353
|
+
{
|
|
354
|
+
"count": 1,
|
|
355
|
+
"feeTotal": {
|
|
356
|
+
"amount": "0.3",
|
|
357
|
+
"currency": "usd",
|
|
358
|
+
},
|
|
359
|
+
"timestamp": "2026-06-01T00:00:00.000Z",
|
|
360
|
+
},
|
|
361
|
+
],
|
|
362
|
+
}
|
|
363
|
+
`)
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
test('behavior: rejects inverted or oversized windows', async () => {
|
|
367
|
+
const app = createApp()
|
|
368
|
+
const { cookie, orgId } = await seeded(app)
|
|
369
|
+
|
|
370
|
+
const inverted = await app.request(
|
|
371
|
+
`/v1/orgs/${orgId}/usage/sponsorships?from=2026-06-02T00:00:00Z&to=2026-06-01T00:00:00Z`,
|
|
372
|
+
{ headers: { cookie } },
|
|
373
|
+
)
|
|
374
|
+
expect(inverted.status).toBe(400)
|
|
375
|
+
expect(((await inverted.json()) as { error: { code: string; message: string } }).error)
|
|
376
|
+
.toMatchInlineSnapshot(`
|
|
377
|
+
{
|
|
378
|
+
"code": "query_invalid",
|
|
379
|
+
"message": "\`from\` must be before \`to\`",
|
|
380
|
+
}
|
|
381
|
+
`)
|
|
382
|
+
|
|
383
|
+
const oversized = await app.request(
|
|
384
|
+
`/v1/orgs/${orgId}/usage/sponsorships?from=2025-01-01T00:00:00Z&to=2026-06-01T00:00:00Z`,
|
|
385
|
+
{ headers: { cookie } },
|
|
386
|
+
)
|
|
387
|
+
expect(oversized.status).toBe(400)
|
|
388
|
+
|
|
389
|
+
// Hour intervals cap tighter: 31 days keeps the bucket count bounded.
|
|
390
|
+
const hourly = await app.request(
|
|
391
|
+
`/v1/orgs/${orgId}/usage/sponsorships?from=2026-04-01T00:00:00Z&to=2026-06-01T00:00:00Z&interval=hour`,
|
|
392
|
+
{ headers: { cookie } },
|
|
393
|
+
)
|
|
394
|
+
expect(hourly.status).toBe(400)
|
|
395
|
+
expect(
|
|
396
|
+
((await hourly.json()) as { error: { message: string } }).error.message,
|
|
397
|
+
).toMatchInlineSnapshot(`"The window may span at most 31 days at \`hour\` interval"`)
|
|
398
|
+
|
|
399
|
+
const bogus = await app.request(`/v1/orgs/${orgId}/usage/sponsorships?bogus=1`, {
|
|
400
|
+
headers: { cookie },
|
|
401
|
+
})
|
|
402
|
+
expect(bogus.status).toBe(400)
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
test('behavior: foreign sessions read 404', async () => {
|
|
406
|
+
const app = createApp()
|
|
407
|
+
const { orgId } = await seeded(app)
|
|
408
|
+
const other = await session(app)
|
|
409
|
+
const response = await app.request(`/v1/orgs/${orgId}/usage/sponsorships`, {
|
|
410
|
+
headers: { cookie: other },
|
|
411
|
+
})
|
|
412
|
+
expect(response.status).toBe(404)
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
test('behavior: the super admin reads any org', async () => {
|
|
416
|
+
const app = createApp()
|
|
417
|
+
const { orgId } = await seeded(app)
|
|
418
|
+
const response = await app.request(`/v1/orgs/${orgId}/usage/sponsorships`, as(secret))
|
|
419
|
+
expect(response.status).toBe(200)
|
|
420
|
+
expect(((await response.json()) as { data: unknown[] }).data).toStrictEqual([])
|
|
421
|
+
})
|
|
422
|
+
})
|