tapimo 0.6.0 → 0.7.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/dist/App.d.ts +8641 -8263
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +9 -1
- package/dist/App.js.map +1 -1
- package/dist/Scope.d.ts +4 -0
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +5 -0
- package/dist/Scope.js.map +1 -1
- package/dist/admin/App.d.ts +112 -11
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +2 -1
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/early-access.d.ts +2 -2
- package/dist/admin/apps/early-access.d.ts.map +1 -1
- package/dist/admin/apps/early-access.js +5 -5
- package/dist/admin/apps/early-access.js.map +1 -1
- package/dist/admin/apps/organizations.d.ts +124 -1
- package/dist/admin/apps/organizations.d.ts.map +1 -1
- package/dist/admin/apps/organizations.js +97 -6
- package/dist/admin/apps/organizations.js.map +1 -1
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.d.ts.map +1 -1
- package/dist/admin/ui.generated.js +95 -87
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/analytics/Analytics.d.ts.map +1 -1
- package/dist/analytics/Analytics.js +20 -0
- package/dist/analytics/Analytics.js.map +1 -1
- package/dist/analytics/tables/requestEvents.d.ts +18 -0
- package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
- package/dist/analytics/tables/requestEvents.js +36 -7
- package/dist/analytics/tables/requestEvents.js.map +1 -1
- package/dist/apps/data/App.d.ts +5016 -4985
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/FundingQuoteProviders.js +2 -15
- package/dist/apps/data/FundingQuoteProviders.js.map +1 -1
- package/dist/apps/data/FxOracle.d.ts +1 -1
- package/dist/apps/data/FxOracle.d.ts.map +1 -1
- package/dist/apps/data/FxOracle.js +1 -1
- package/dist/apps/data/FxOracle.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +27 -27
- package/dist/apps/data/routes/activities.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.js +21 -1
- package/dist/apps/data/routes/activities.js.map +1 -1
- package/dist/apps/data/routes/balances.d.ts +58 -58
- package/dist/apps/data/routes/blocks.d.ts +39 -39
- package/dist/apps/data/routes/coingecko.d.ts +176 -176
- package/dist/apps/data/routes/exchanges.d.ts +113 -113
- package/dist/apps/data/routes/fee-amm.d.ts +105 -91
- package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
- package/dist/apps/data/routes/fee-amm.js +76 -91
- package/dist/apps/data/routes/fee-amm.js.map +1 -1
- package/dist/apps/data/routes/funding.d.ts +72 -34
- package/dist/apps/data/routes/funding.d.ts.map +1 -1
- package/dist/apps/data/routes/funding.js +34 -26
- package/dist/apps/data/routes/funding.js.map +1 -1
- package/dist/apps/data/routes/indexer.d.ts +5 -5
- package/dist/apps/data/routes/receipts.d.ts +6 -6
- package/dist/apps/data/routes/rpc.d.ts +31 -31
- package/dist/apps/data/routes/tokens.d.ts +2 -2
- package/dist/apps/data/routes/tokens.js +1 -1
- package/dist/apps/data/routes/transactions.d.ts +4 -4
- package/dist/apps/data/routes/transfers.d.ts +99 -88
- package/dist/apps/data/routes/transfers.d.ts.map +1 -1
- package/dist/apps/data/routes/transfers.js +226 -141
- package/dist/apps/data/routes/transfers.js.map +1 -1
- package/dist/apps/data/routes/valuation.d.ts +34 -34
- package/dist/apps/data/routes/valuation.js +3 -3
- package/dist/apps/data/routes/valuation.js.map +1 -1
- package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
- package/dist/apps/data/routes/webhooks.d.ts +366 -366
- package/dist/apps/index.d.ts +1 -0
- package/dist/apps/index.d.ts.map +1 -1
- package/dist/apps/index.js +1 -0
- package/dist/apps/index.js.map +1 -1
- package/dist/apps/management/App.d.ts +460 -523
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +7 -23
- package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.js +20 -8
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +106 -151
- package/dist/apps/management/routes/billing.d.ts.map +1 -1
- package/dist/apps/management/routes/billing.js +28 -28
- package/dist/apps/management/routes/billing.js.map +1 -1
- package/dist/apps/management/routes/faucet.d.ts +40 -26
- package/dist/apps/management/routes/faucet.d.ts.map +1 -1
- package/dist/apps/management/routes/faucet.js +33 -18
- package/dist/apps/management/routes/faucet.js.map +1 -1
- package/dist/apps/management/routes/invitations.d.ts +131 -131
- package/dist/apps/management/routes/invitations.d.ts.map +1 -1
- package/dist/apps/management/routes/invitations.js +0 -3
- package/dist/apps/management/routes/invitations.js.map +1 -1
- package/dist/apps/management/routes/me.d.ts +6 -8
- package/dist/apps/management/routes/me.d.ts.map +1 -1
- package/dist/apps/management/routes/me.js +0 -4
- package/dist/apps/management/routes/me.js.map +1 -1
- package/dist/apps/management/routes/members.d.ts +45 -45
- package/dist/apps/management/routes/orgs.d.ts +94 -94
- package/dist/apps/management/routes/orgs.d.ts.map +1 -1
- package/dist/apps/management/routes/orgs.js +5 -0
- package/dist/apps/management/routes/orgs.js.map +1 -1
- package/dist/apps/management/routes/usage.d.ts +32 -32
- package/dist/apps/management/routes/verified-token-requests.d.ts +94 -94
- package/dist/apps/management/routes/webhooks.d.ts +1 -1
- package/dist/apps/mpp/App.d.ts +363 -0
- package/dist/apps/mpp/App.d.ts.map +1 -0
- package/dist/apps/mpp/App.js +72 -0
- package/dist/apps/mpp/App.js.map +1 -0
- package/dist/apps/mpp/Mpp.d.ts +178 -0
- package/dist/apps/mpp/Mpp.d.ts.map +1 -0
- package/dist/apps/mpp/Mpp.js +183 -0
- package/dist/apps/mpp/Mpp.js.map +1 -0
- package/dist/apps/mpp/index.d.ts +3 -0
- package/dist/apps/mpp/index.d.ts.map +1 -0
- package/dist/apps/mpp/index.js +3 -0
- package/dist/apps/mpp/index.js.map +1 -0
- package/dist/apps/relay/App.d.ts.map +1 -1
- package/dist/apps/relay/App.js +56 -9
- package/dist/apps/relay/App.js.map +1 -1
- package/dist/db/Db.d.ts +2 -0
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +27 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +12 -0
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +11 -0
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/enabledBillingSources.d.ts +73 -0
- package/dist/db/tables/enabledBillingSources.d.ts.map +1 -0
- package/dist/db/tables/enabledBillingSources.js +69 -0
- package/dist/db/tables/enabledBillingSources.js.map +1 -0
- package/dist/db/tables/invitations.d.ts +1 -12
- package/dist/db/tables/invitations.d.ts.map +1 -1
- package/dist/db/tables/invitations.js +3 -23
- package/dist/db/tables/invitations.js.map +1 -1
- package/dist/db/tables/organizations.d.ts +5 -2
- package/dist/db/tables/organizations.d.ts.map +1 -1
- package/dist/db/tables/organizations.js +13 -2
- package/dist/db/tables/organizations.js.map +1 -1
- package/dist/handlers/internal/utils.d.ts +1 -16
- package/dist/handlers/internal/utils.d.ts.map +1 -1
- package/dist/handlers/internal/utils.js +17 -6
- package/dist/handlers/internal/utils.js.map +1 -1
- package/dist/handlers/relay.d.ts.map +1 -1
- package/dist/handlers/relay.js +37 -5
- package/dist/handlers/relay.js.map +1 -1
- package/dist/internal/Auth.d.ts +0 -32
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +3 -51
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/FundingQuotes.d.ts +78 -32
- package/dist/internal/FundingQuotes.d.ts.map +1 -1
- package/dist/internal/FundingQuotes.js +45 -8
- package/dist/internal/FundingQuotes.js.map +1 -1
- package/dist/internal/Log.d.ts +18 -4
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +50 -3
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/MetricSink.d.ts.map +1 -1
- package/dist/internal/MetricSink.js +15 -0
- package/dist/internal/MetricSink.js.map +1 -1
- package/dist/internal/Response.d.ts +2 -2
- package/dist/internal/Response.d.ts.map +1 -1
- package/dist/internal/Response.js +12 -4
- package/dist/internal/Response.js.map +1 -1
- package/dist/internal/Viem.d.ts +1 -1
- package/package.json +7 -2
- package/src/App.test.ts +9 -4
- package/src/App.ts +14 -2
- package/src/Client.test-d.ts +109 -28
- package/src/Scope.ts +5 -0
- package/src/admin/App.test.ts +4 -0
- package/src/admin/apps/early-access.ts +6 -5
- package/src/admin/apps/organizations.test.ts +86 -2
- package/src/admin/apps/organizations.ts +155 -31
- package/src/admin/apps/scopes.test.ts +5 -0
- package/src/admin/ui/src/lib/organizations.test.ts +81 -1
- package/src/admin/ui/src/lib/organizations.ts +92 -0
- package/src/admin/ui/src/routes/api-keys.tsx +1 -1
- package/src/admin/ui/src/routes/early-access.tsx +8 -3
- package/src/admin/ui/src/routes/organizations_.$orgId.tsx +103 -0
- package/src/admin/ui.generated.ts +95 -109
- package/src/analytics/Analytics.test.ts +83 -0
- package/src/analytics/Analytics.ts +20 -0
- package/src/analytics/tables/requestEvents.ts +60 -7
- package/src/apps/composition.test.ts +11 -2
- package/src/apps/data/App.test.ts +1 -1
- package/src/apps/data/FundingQuoteProviders.test.ts +0 -5
- package/src/apps/data/FundingQuoteProviders.ts +2 -29
- package/src/apps/data/FxOracle.test.ts +2 -2
- package/src/apps/data/FxOracle.ts +2 -2
- package/src/apps/data/routes/activities.test.ts +6 -2
- package/src/apps/data/routes/activities.ts +39 -1
- package/src/apps/data/routes/balances.test.ts +6 -2
- package/src/apps/data/routes/exchanges.test.ts +7 -3
- package/src/apps/data/routes/fee-amm.test.ts +47 -23
- package/src/apps/data/routes/fee-amm.ts +98 -119
- package/src/apps/data/routes/funding.test.ts +29 -8
- package/src/apps/data/routes/funding.ts +42 -48
- package/src/apps/data/routes/receipts.test.ts +6 -2
- package/src/apps/data/routes/tokens.ts +1 -1
- package/src/apps/data/routes/transfers.test.ts +203 -35
- package/src/apps/data/routes/transfers.ts +294 -172
- package/src/apps/data/routes/valuation.test.ts +18 -3
- package/src/apps/data/routes/valuation.ts +4 -4
- package/src/apps/index.ts +1 -0
- package/src/apps/management/routes/api-keys.test.ts +2 -75
- package/src/apps/management/routes/api-keys.ts +30 -7
- package/src/apps/management/routes/billing.test.ts +106 -85
- package/src/apps/management/routes/billing.ts +32 -21
- package/src/apps/management/routes/faucet.test.ts +11 -3
- package/src/apps/management/routes/faucet.ts +46 -30
- package/src/apps/management/routes/invitations.test.ts +0 -172
- package/src/apps/management/routes/invitations.ts +0 -3
- package/src/apps/management/routes/me.test.ts +3 -145
- package/src/apps/management/routes/me.ts +0 -6
- package/src/apps/management/routes/orgs.test.ts +45 -2
- package/src/apps/management/routes/orgs.ts +6 -0
- package/src/apps/management/routes/scopes.test.ts +1 -0
- package/src/apps/mpp/App.ts +86 -0
- package/src/apps/mpp/Mpp.test.ts +193 -0
- package/src/apps/mpp/Mpp.ts +353 -0
- package/src/apps/mpp/index.ts +2 -0
- package/src/apps/mpp/internal/Adapter.ts +11 -0
- package/src/apps/mpp/internal/Error.ts +9 -0
- package/src/apps/mpp/internal/Idempotency.ts +7 -0
- package/src/apps/mpp/internal/Screening.ts +10 -0
- package/src/apps/relay/App.test.ts +88 -6
- package/src/apps/relay/App.ts +61 -9
- package/src/db/Db.test.ts +2 -2
- package/src/db/Db.ts +30 -0
- package/src/db/Schema.ts +15 -0
- package/src/db/tables/enabledBillingSources.test.ts +303 -0
- package/src/db/tables/enabledBillingSources.ts +118 -0
- package/src/db/tables/invitations.ts +4 -26
- package/src/db/tables/organizations.test.ts +103 -0
- package/src/db/tables/organizations.ts +18 -2
- package/src/handlers/internal/utils.ts +22 -6
- package/src/handlers/relay.errors.test.ts +153 -0
- package/src/handlers/relay.ts +42 -7
- package/src/internal/Auth.test.ts +3 -3
- package/src/internal/Auth.ts +3 -63
- package/src/internal/FundingQuotes.test.ts +18 -5
- package/src/internal/FundingQuotes.ts +46 -11
- package/src/internal/Log.test.ts +91 -0
- package/src/internal/Log.ts +69 -5
- package/src/internal/MetricSink.test.ts +55 -0
- package/src/internal/MetricSink.ts +14 -0
- package/src/internal/Response.test.ts +28 -0
- package/src/internal/Response.ts +12 -4
|
@@ -4,8 +4,6 @@ import { exportJWK, generateKeyPair } from 'jose'
|
|
|
4
4
|
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
|
|
5
5
|
|
|
6
6
|
import * as TestApp from '../../../../test/App.js'
|
|
7
|
-
import * as EarlyAccess from '../../../db/tables/earlyAccess.js'
|
|
8
|
-
import * as Users from '../../../db/tables/users.js'
|
|
9
7
|
import * as Me from './me.js'
|
|
10
8
|
|
|
11
9
|
/** Origin pinned for SIWE domain binding; Hono test requests use this host. */
|
|
@@ -20,40 +18,6 @@ function createApp() {
|
|
|
20
18
|
})
|
|
21
19
|
}
|
|
22
20
|
|
|
23
|
-
/** App with the early-access gate enabled; allowlist entries seed through `EarlyAccess.add`. */
|
|
24
|
-
function createGatedApp(db = TestApp.database()) {
|
|
25
|
-
return TestApp.create({
|
|
26
|
-
auth: { keys: [TestApp.key] },
|
|
27
|
-
db,
|
|
28
|
-
session: { wallet: { origin } },
|
|
29
|
-
tmp_earlyAccess: true,
|
|
30
|
-
})
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** RequestInit for a JSON mutation carrying the session cookie. */
|
|
34
|
-
function json(method: 'POST', cookie: string, body: unknown) {
|
|
35
|
-
return {
|
|
36
|
-
body: JSON.stringify(body),
|
|
37
|
-
headers: { 'content-type': 'application/json', cookie },
|
|
38
|
-
method,
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** Signs in a fresh scripted account; returns its session cookie and user id. */
|
|
43
|
-
async function session(app: TestApp.signIn.App) {
|
|
44
|
-
const account = privateKeyToAccount(generatePrivateKey())
|
|
45
|
-
const { cookie } = await TestApp.signIn(app, account)
|
|
46
|
-
const me = await app.request('/v1/me', { headers: { cookie: cookie! } })
|
|
47
|
-
const { id } = (await me.json()) as { id: string }
|
|
48
|
-
return { cookie: cookie!, userId: id }
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** Reads the session's `tmp_earlyAccess` flag from `/v1/me`. TODO: remove after GA. */
|
|
52
|
-
async function tmp_earlyAccess(app: TestApp.signIn.App, cookie: string) {
|
|
53
|
-
const response = await app.request('/v1/me', { headers: { cookie } })
|
|
54
|
-
return (await TestApp.json(response, Me.schema.User)).tmp_earlyAccess
|
|
55
|
-
}
|
|
56
|
-
|
|
57
21
|
describe('GET /me', () => {
|
|
58
22
|
test('returns the signed-in user', async () => {
|
|
59
23
|
const app = createApp()
|
|
@@ -219,13 +183,12 @@ describe('sign-in email requirement', () => {
|
|
|
219
183
|
})
|
|
220
184
|
|
|
221
185
|
describe('open sign-up', () => {
|
|
222
|
-
test('allows new users on any domain
|
|
186
|
+
test('allows new users on any domain', async () => {
|
|
223
187
|
const oidc = await createIssuer({ email: 'dev@example.com' })
|
|
224
188
|
try {
|
|
225
189
|
const app = TestApp.create({
|
|
226
190
|
auth: { keys: [TestApp.key] },
|
|
227
191
|
session: { wallet: { issuer: oidc.url, origin } },
|
|
228
|
-
tmp_earlyAccess: true,
|
|
229
192
|
})
|
|
230
193
|
const account = privateKeyToAccount(generatePrivateKey())
|
|
231
194
|
const { cookie, verifyResponse } = await TestApp.signIn(app, account, {
|
|
@@ -242,8 +205,8 @@ describe('open sign-up', () => {
|
|
|
242
205
|
}
|
|
243
206
|
})
|
|
244
207
|
|
|
245
|
-
test('allows new users without an identity token
|
|
246
|
-
const app =
|
|
208
|
+
test('allows new users without an identity token', async () => {
|
|
209
|
+
const app = createApp()
|
|
247
210
|
const account = privateKeyToAccount(generatePrivateKey())
|
|
248
211
|
const { cookie, verifyResponse } = await TestApp.signIn(app, account)
|
|
249
212
|
|
|
@@ -252,111 +215,6 @@ describe('open sign-up', () => {
|
|
|
252
215
|
})
|
|
253
216
|
})
|
|
254
217
|
|
|
255
|
-
describe('tmp_earlyAccess', () => {
|
|
256
|
-
test('defaults to true while the gate is dormant', async () => {
|
|
257
|
-
const app = createApp()
|
|
258
|
-
const { cookie } = await session(app)
|
|
259
|
-
expect(await tmp_earlyAccess(app, cookie)).toBe(true)
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
test('matches domain entries case-insensitively', async () => {
|
|
263
|
-
const db = TestApp.database()
|
|
264
|
-
const app = createGatedApp(db)
|
|
265
|
-
await EarlyAccess.add(db, { createdBy: 'test', entry: 'Example.ORG' })
|
|
266
|
-
const user = await session(app)
|
|
267
|
-
await Users.setEmail(db, user.userId, 'Dev@EXAMPLE.org')
|
|
268
|
-
|
|
269
|
-
expect(await tmp_earlyAccess(app, user.cookie)).toBe(true)
|
|
270
|
-
})
|
|
271
|
-
|
|
272
|
-
test('exact-email entries grant only that address', async () => {
|
|
273
|
-
const db = TestApp.database()
|
|
274
|
-
const app = createGatedApp(db)
|
|
275
|
-
await EarlyAccess.add(db, { createdBy: 'test', entry: 'dev@example.org' })
|
|
276
|
-
const granted = await session(app)
|
|
277
|
-
await Users.setEmail(db, granted.userId, 'dev@example.org')
|
|
278
|
-
const denied = await session(app)
|
|
279
|
-
await Users.setEmail(db, denied.userId, 'other@example.org')
|
|
280
|
-
|
|
281
|
-
expect(await tmp_earlyAccess(app, granted.cookie)).toBe(true)
|
|
282
|
-
expect(await tmp_earlyAccess(app, denied.cookie)).toBe(false)
|
|
283
|
-
})
|
|
284
|
-
|
|
285
|
-
test('stays false without a matching entry', async () => {
|
|
286
|
-
const db = TestApp.database()
|
|
287
|
-
const app = createGatedApp(db)
|
|
288
|
-
const user = await session(app)
|
|
289
|
-
await Users.setEmail(db, user.userId, 'dev@example.net')
|
|
290
|
-
|
|
291
|
-
expect(await tmp_earlyAccess(app, user.cookie)).toBe(false)
|
|
292
|
-
})
|
|
293
|
-
|
|
294
|
-
test('stays false without a verified email', async () => {
|
|
295
|
-
const app = createGatedApp()
|
|
296
|
-
const { cookie } = await session(app)
|
|
297
|
-
|
|
298
|
-
expect(await tmp_earlyAccess(app, cookie)).toBe(false)
|
|
299
|
-
})
|
|
300
|
-
|
|
301
|
-
test('flips false once the entry is removed', async () => {
|
|
302
|
-
const db = TestApp.database()
|
|
303
|
-
const app = createGatedApp(db)
|
|
304
|
-
await EarlyAccess.add(db, { createdBy: 'test', entry: 'example.org' })
|
|
305
|
-
const user = await session(app)
|
|
306
|
-
await Users.setEmail(db, user.userId, 'dev@example.org')
|
|
307
|
-
expect(await tmp_earlyAccess(app, user.cookie)).toBe(true)
|
|
308
|
-
|
|
309
|
-
await EarlyAccess.remove(db, 'example.org')
|
|
310
|
-
expect(await tmp_earlyAccess(app, user.cookie)).toBe(false)
|
|
311
|
-
})
|
|
312
|
-
|
|
313
|
-
test('invitations from early-access inviters grant access until revoked', async () => {
|
|
314
|
-
const db = TestApp.database()
|
|
315
|
-
const app = createGatedApp(db)
|
|
316
|
-
await EarlyAccess.add(db, { createdBy: 'test', entry: 'example.org' })
|
|
317
|
-
const inviter = await session(app)
|
|
318
|
-
await Users.setEmail(db, inviter.userId, 'owner@example.org')
|
|
319
|
-
const created = await app.request('/v1/orgs', json('POST', inviter.cookie, { name: 'Acme' }))
|
|
320
|
-
const org = (await created.json()) as { id: string }
|
|
321
|
-
const invited = await app.request(
|
|
322
|
-
`/v1/orgs/${org.id}/invitations`,
|
|
323
|
-
json('POST', inviter.cookie, { email: 'invitee@example.net', role: 'member' }),
|
|
324
|
-
)
|
|
325
|
-
expect(invited.status).toBe(200)
|
|
326
|
-
const invitation = (await invited.json()) as { id: string }
|
|
327
|
-
|
|
328
|
-
const invitee = await session(app)
|
|
329
|
-
await Users.setEmail(db, invitee.userId, 'invitee@example.net')
|
|
330
|
-
expect(await tmp_earlyAccess(app, invitee.cookie)).toBe(true)
|
|
331
|
-
|
|
332
|
-
// Revoking the invitation withdraws the grant.
|
|
333
|
-
const revoked = await app.request(`/v1/orgs/${org.id}/invitations/${invitation.id}`, {
|
|
334
|
-
headers: { cookie: inviter.cookie },
|
|
335
|
-
method: 'DELETE',
|
|
336
|
-
})
|
|
337
|
-
expect(revoked.status).toBe(200)
|
|
338
|
-
expect(await tmp_earlyAccess(app, invitee.cookie)).toBe(false)
|
|
339
|
-
})
|
|
340
|
-
|
|
341
|
-
test('invitations from non-early-access inviters do not grant access', async () => {
|
|
342
|
-
const db = TestApp.database()
|
|
343
|
-
const app = createGatedApp(db)
|
|
344
|
-
const inviter = await session(app)
|
|
345
|
-
await Users.setEmail(db, inviter.userId, 'owner@example.net')
|
|
346
|
-
const created = await app.request('/v1/orgs', json('POST', inviter.cookie, { name: 'Acme' }))
|
|
347
|
-
const org = (await created.json()) as { id: string }
|
|
348
|
-
const invited = await app.request(
|
|
349
|
-
`/v1/orgs/${org.id}/invitations`,
|
|
350
|
-
json('POST', inviter.cookie, { email: 'friend@example.net', role: 'member' }),
|
|
351
|
-
)
|
|
352
|
-
expect(invited.status).toBe(200)
|
|
353
|
-
|
|
354
|
-
const invitee = await session(app)
|
|
355
|
-
await Users.setEmail(db, invitee.userId, 'friend@example.net')
|
|
356
|
-
expect(await tmp_earlyAccess(app, invitee.cookie)).toBe(false)
|
|
357
|
-
})
|
|
358
|
-
})
|
|
359
|
-
|
|
360
218
|
/** Local OIDC issuer standing in for the wallet: the SDK's own provider (discovery, JWKS, token mint). */
|
|
361
219
|
async function createIssuer(options: { email: string }) {
|
|
362
220
|
// Extractable: the provider takes key material as JWK strings.
|
|
@@ -35,11 +35,6 @@ export namespace schema {
|
|
|
35
35
|
z.describe('Opaque user id (`usr_…`).'),
|
|
36
36
|
z.meta({ examples: ['usr_1a2b3c4d5e6f7g8h9j0k1m2n'] }),
|
|
37
37
|
),
|
|
38
|
-
// TODO: remove after GA.
|
|
39
|
-
tmp_earlyAccess: z.boolean().check(
|
|
40
|
-
z.describe('Whether the user has early access; gated features require it while the platform is invite-only.'), // prettier-ignore
|
|
41
|
-
z.meta({ examples: [true] }),
|
|
42
|
-
),
|
|
43
38
|
updatedAt: z
|
|
44
39
|
.string()
|
|
45
40
|
.check(
|
|
@@ -99,7 +94,6 @@ export function me() {
|
|
|
99
94
|
createdAt: user.createdAt,
|
|
100
95
|
...(user.email ? { email: user.email } : {}),
|
|
101
96
|
id: user.id,
|
|
102
|
-
tmp_earlyAccess: await Auth.tmp_earlyAccess(c),
|
|
103
97
|
updatedAt: user.updatedAt,
|
|
104
98
|
}),
|
|
105
99
|
200,
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
|
|
2
2
|
|
|
3
3
|
import * as TestApp from '../../../../test/App.js'
|
|
4
|
+
import * as EarlyAccess from '../../../db/tables/earlyAccess.js'
|
|
5
|
+
import * as EnabledBillingSources from '../../../db/tables/enabledBillingSources.js'
|
|
4
6
|
import * as Organizations from '../../../db/tables/organizations.js'
|
|
5
7
|
import * as Projects from '../../../db/tables/projects.js'
|
|
8
|
+
import * as Users from '../../../db/tables/users.js'
|
|
6
9
|
import type * as Log from '../../../internal/Log.js'
|
|
7
10
|
import * as Orgs from './orgs.js'
|
|
8
11
|
|
|
@@ -196,7 +199,8 @@ describe('GET /orgs', () => {
|
|
|
196
199
|
|
|
197
200
|
describe('POST /orgs', () => {
|
|
198
201
|
test('creates a session-owned organization', async () => {
|
|
199
|
-
const
|
|
202
|
+
const db = TestApp.database()
|
|
203
|
+
const app = createApp(db)
|
|
200
204
|
const cookie = await session(app)
|
|
201
205
|
|
|
202
206
|
const response = await app.request('/v1/orgs', json('POST', cookie, { name: 'Acme' }))
|
|
@@ -204,10 +208,48 @@ describe('POST /orgs', () => {
|
|
|
204
208
|
const body = await TestApp.json(response, Orgs.schema.Organization)
|
|
205
209
|
expect(body.id.startsWith('org_')).toBe(true)
|
|
206
210
|
expect(body.name).toBe('Acme')
|
|
211
|
+
expect(await EnabledBillingSources.listByOrg(db, body.id)).toEqual([])
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
test.each([
|
|
215
|
+
{ email: 'Dev@Example.NET', entry: 'dev@example.net', kind: 'email' },
|
|
216
|
+
{ email: 'Dev@Example.ORG', entry: 'example.org', kind: 'domain' },
|
|
217
|
+
])('enables Stripe for a session matching an early-access $kind', async ({ email, entry }) => {
|
|
218
|
+
const db = TestApp.database()
|
|
219
|
+
const app = createApp(db)
|
|
220
|
+
await EarlyAccess.add(db, { createdBy: 'test', entry })
|
|
221
|
+
const account = privateKeyToAccount(generatePrivateKey())
|
|
222
|
+
const { cookie } = await TestApp.signIn(app, account)
|
|
223
|
+
const user = await Users.getByAddress(db, account.address)
|
|
224
|
+
if (!user) throw new Error('sign-in did not create a user')
|
|
225
|
+
await Users.setEmail(db, user.id, email)
|
|
226
|
+
|
|
227
|
+
const response = await app.request('/v1/orgs', json('POST', cookie!, { name: 'Acme' }))
|
|
228
|
+
expect(response.status).toBe(200)
|
|
229
|
+
const org = await TestApp.json(response, Orgs.schema.Organization)
|
|
230
|
+
const sources = await EnabledBillingSources.listByOrg(db, org.id)
|
|
231
|
+
expect(sources.map(({ createdBy, source }) => ({ createdBy, source }))).toEqual([
|
|
232
|
+
{ createdBy: user.id, source: 'stripe' },
|
|
233
|
+
])
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
test('does not enable Stripe without an early-access grant', async () => {
|
|
237
|
+
const db = TestApp.database()
|
|
238
|
+
const app = createApp(db)
|
|
239
|
+
const account = privateKeyToAccount(generatePrivateKey())
|
|
240
|
+
const { cookie } = await TestApp.signIn(app, account)
|
|
241
|
+
const user = await Users.getByAddress(db, account.address)
|
|
242
|
+
if (!user) throw new Error('sign-in did not create a user')
|
|
243
|
+
await Users.setEmail(db, user.id, 'dev@example.net')
|
|
244
|
+
|
|
245
|
+
const response = await app.request('/v1/orgs', json('POST', cookie!, { name: 'Acme' }))
|
|
246
|
+
const org = await TestApp.json(response, Orgs.schema.Organization)
|
|
247
|
+
expect(await EnabledBillingSources.listByOrg(db, org.id)).toEqual([])
|
|
207
248
|
})
|
|
208
249
|
|
|
209
250
|
test('creates a memberless org as the super admin', async () => {
|
|
210
|
-
const
|
|
251
|
+
const db = TestApp.database()
|
|
252
|
+
const app = createApp(db)
|
|
211
253
|
const response = await app.request('/v1/orgs', {
|
|
212
254
|
body: JSON.stringify({ name: 'Ops' }),
|
|
213
255
|
headers: { 'content-type': 'application/json', 'tempo-api-key': secret },
|
|
@@ -216,6 +258,7 @@ describe('POST /orgs', () => {
|
|
|
216
258
|
expect(response.status).toBe(200)
|
|
217
259
|
const org = await TestApp.json(response, Orgs.schema.Organization)
|
|
218
260
|
expect(org.name).toBe('Ops')
|
|
261
|
+
expect(await EnabledBillingSources.listByOrg(db, org.id)).toEqual([])
|
|
219
262
|
|
|
220
263
|
// Memberless: invisible to sessions until an owner is seated.
|
|
221
264
|
const cookie = await session(app)
|
|
@@ -5,6 +5,7 @@ import * as ApiKeys from '../../../ApiKeys.js'
|
|
|
5
5
|
import type * as App from '../../../App.js'
|
|
6
6
|
import * as Auth from '../../../internal/Auth.js'
|
|
7
7
|
import * as Db from '../../../db/Db.js'
|
|
8
|
+
import * as EarlyAccess from '../../../db/tables/earlyAccess.js'
|
|
8
9
|
import * as Memberships from '../../../db/tables/memberships.js'
|
|
9
10
|
import * as OpenApi from '../../../internal/OpenApi.js'
|
|
10
11
|
import * as Organizations from '../../../db/tables/organizations.js'
|
|
@@ -189,6 +190,11 @@ export function orgs() {
|
|
|
189
190
|
? await Organizations.create(db, { createdBy: 'super_admin', name: body.name })
|
|
190
191
|
: {
|
|
191
192
|
...(await Organizations.createOwned(db, {
|
|
193
|
+
enabledBillingSources:
|
|
194
|
+
principal.email &&
|
|
195
|
+
(await EarlyAccess.matches(db, principal.email.trim().toLowerCase()))
|
|
196
|
+
? ['stripe']
|
|
197
|
+
: [],
|
|
192
198
|
name: body.name,
|
|
193
199
|
userId: principal.id,
|
|
194
200
|
})),
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Hono } from 'hono'
|
|
2
|
+
import type * 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 OpenApi from '../../internal/OpenApi.js'
|
|
7
|
+
import * as Metadata from '../metadata.js'
|
|
8
|
+
import * as Mpp from './Mpp.js'
|
|
9
|
+
|
|
10
|
+
/** Mounts MPP credential verification and broadcast routes. */
|
|
11
|
+
export function mpp() {
|
|
12
|
+
const app = new Hono<App.Environment>()
|
|
13
|
+
.post(
|
|
14
|
+
'/v1/mpp/verify',
|
|
15
|
+
Auth.policy({ apiKey: { scopes: ['mpp:write'] }, mpp: false, public: false }),
|
|
16
|
+
OpenApi.validate('json', Mpp.schema.RelayInput, bodyValidation),
|
|
17
|
+
OpenApi.describeRoute({
|
|
18
|
+
operationId: 'verifyMppCredential',
|
|
19
|
+
responses: responses({
|
|
20
|
+
description: 'The MPP credential validation result.',
|
|
21
|
+
schema: Mpp.schema.VerifyResponse,
|
|
22
|
+
}),
|
|
23
|
+
summary: 'Verify MPP credential',
|
|
24
|
+
tags: ['MPP Relay'],
|
|
25
|
+
}),
|
|
26
|
+
(c) => {
|
|
27
|
+
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
28
|
+
if (OpenApi.narrowValidation) return OpenApi.validationError(c, bodyValidation)
|
|
29
|
+
return Mpp.unavailable<Mpp.VerifyResponse>(c)
|
|
30
|
+
},
|
|
31
|
+
)
|
|
32
|
+
.post(
|
|
33
|
+
'/v1/mpp/broadcast',
|
|
34
|
+
Auth.policy({ apiKey: { scopes: ['mpp:write'] }, mpp: false, public: false }),
|
|
35
|
+
OpenApi.validate('json', Mpp.schema.RelayInput, bodyValidation),
|
|
36
|
+
OpenApi.describeRoute({
|
|
37
|
+
operationId: 'broadcastMppCredential',
|
|
38
|
+
responses: responses({
|
|
39
|
+
description: 'The MPP credential broadcast result.',
|
|
40
|
+
schema: Mpp.schema.BroadcastResponse,
|
|
41
|
+
}),
|
|
42
|
+
summary: 'Broadcast MPP credential',
|
|
43
|
+
tags: ['MPP Relay'],
|
|
44
|
+
}),
|
|
45
|
+
(c) => {
|
|
46
|
+
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
47
|
+
if (OpenApi.narrowValidation) return OpenApi.validationError(c, bodyValidation)
|
|
48
|
+
return Mpp.unavailable<Mpp.BroadcastResponse>(c)
|
|
49
|
+
},
|
|
50
|
+
)
|
|
51
|
+
return Metadata.attach(app, openapi)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const bodyValidation = {
|
|
55
|
+
code: 'body_invalid',
|
|
56
|
+
message: 'Check the MPP credential and challenge, then try again.',
|
|
57
|
+
} as const
|
|
58
|
+
|
|
59
|
+
function responses<schema extends z.ZodMiniType>(options: { description: string; schema: schema }) {
|
|
60
|
+
return {
|
|
61
|
+
...OpenApi.responses({
|
|
62
|
+
errors: { 403: 'The API key does not grant MPP relay access.' },
|
|
63
|
+
success: options,
|
|
64
|
+
}),
|
|
65
|
+
503: {
|
|
66
|
+
content: { 'application/json': { schema: OpenApi.resolver(Mpp.schema.Unavailable) } },
|
|
67
|
+
description: 'MPP relay settlement is temporarily unavailable.',
|
|
68
|
+
headers: {
|
|
69
|
+
'Retry-After': {
|
|
70
|
+
description: 'How many seconds to wait before retrying the relay.',
|
|
71
|
+
schema: { type: 'integer' as const },
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const openapi = {
|
|
79
|
+
tags: [
|
|
80
|
+
{
|
|
81
|
+
name: 'MPP Relay',
|
|
82
|
+
description: 'Validate and broadcast MPP payment credentials through Tempo.',
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
'x-tagGroups': [{ name: 'Payments', tags: ['MPP Relay'] }],
|
|
86
|
+
} satisfies App.create.Metadata
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import * as TestApp from '../../../test/App.js'
|
|
2
|
+
import * as Mpp from './Mpp.js'
|
|
3
|
+
|
|
4
|
+
const key = {
|
|
5
|
+
id: 'key_mpp',
|
|
6
|
+
orgId: 'org_test',
|
|
7
|
+
scopes: ['mpp:write'],
|
|
8
|
+
token: 'secret_mpp',
|
|
9
|
+
} satisfies TestApp.kvStore.Key
|
|
10
|
+
|
|
11
|
+
const input = {
|
|
12
|
+
challenge: {
|
|
13
|
+
description: 'Tempo API payment',
|
|
14
|
+
id: 'ch_01j3j1k2l3m4n5p6q7r8s9t0u',
|
|
15
|
+
intent: 'charge',
|
|
16
|
+
method: 'tempo',
|
|
17
|
+
opaque: 'eyJhbGciOiJIUzI1NiJ9',
|
|
18
|
+
realm: 'merchant.example',
|
|
19
|
+
request: { amount: '1', currency: 'USD' },
|
|
20
|
+
},
|
|
21
|
+
payload: { signature: `0x${'aa'.repeat(65)}`, type: 'transaction' },
|
|
22
|
+
} satisfies Mpp.RelayInput
|
|
23
|
+
|
|
24
|
+
const errorCodes = [
|
|
25
|
+
'unsupported',
|
|
26
|
+
'policy_denied',
|
|
27
|
+
'invalid_payment',
|
|
28
|
+
'insufficient_funds',
|
|
29
|
+
'expired',
|
|
30
|
+
'already_used',
|
|
31
|
+
'screen_rejected',
|
|
32
|
+
'simulation_failed',
|
|
33
|
+
'broadcast_failed',
|
|
34
|
+
'temporarily_unavailable',
|
|
35
|
+
'unknown',
|
|
36
|
+
] as const
|
|
37
|
+
|
|
38
|
+
const errorDescriptions = [
|
|
39
|
+
'Method, intent, network, or asset is unsupported.',
|
|
40
|
+
'Payment violates relay or account policy.',
|
|
41
|
+
'Payload, signature, parties, or payment requirements are invalid.',
|
|
42
|
+
'Sender lacks funds.',
|
|
43
|
+
'Challenge or payment authorization expired.',
|
|
44
|
+
'Credential or transaction already used.',
|
|
45
|
+
'Originator or recipient failed screening.',
|
|
46
|
+
'Transaction simulation failed before broadcast.',
|
|
47
|
+
'Transaction broadcast or confirmation failed.',
|
|
48
|
+
'Retry may succeed.',
|
|
49
|
+
'Unexpected relay failure.',
|
|
50
|
+
] as const
|
|
51
|
+
|
|
52
|
+
describe('MPP relay', () => {
|
|
53
|
+
test.each(['/v1/mpp/verify', '/v1/mpp/broadcast'])('%s requires the MPP scope', async (path) => {
|
|
54
|
+
const app = TestApp.create({ auth: { keys: [key] } })
|
|
55
|
+
const response = await app.fetch(
|
|
56
|
+
new Request(`http://tempo-api.test${path}`, {
|
|
57
|
+
body: JSON.stringify(input),
|
|
58
|
+
headers: { 'content-type': 'application/json', authorization: 'Bearer secret_mpp' },
|
|
59
|
+
method: 'POST',
|
|
60
|
+
}),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
expect(response.status).toBe(503)
|
|
64
|
+
expect(response.headers.get('retry-after')).toBe('60')
|
|
65
|
+
expect(await response.json()).toMatchInlineSnapshot(`
|
|
66
|
+
{
|
|
67
|
+
"error": {
|
|
68
|
+
"code": "temporarily_unavailable",
|
|
69
|
+
"message": "MPP relay is not enabled yet.",
|
|
70
|
+
},
|
|
71
|
+
"success": false,
|
|
72
|
+
}
|
|
73
|
+
`)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test('rejects unscoped API keys', async () => {
|
|
77
|
+
const app = TestApp.create({ auth: { keys: [key, TestApp.key] } })
|
|
78
|
+
const response = await app.fetch(
|
|
79
|
+
new Request('http://tempo-api.test/v1/mpp/verify', {
|
|
80
|
+
body: JSON.stringify(input),
|
|
81
|
+
headers: {
|
|
82
|
+
'content-type': 'application/json',
|
|
83
|
+
authorization: `Bearer ${TestApp.key.token}`,
|
|
84
|
+
},
|
|
85
|
+
method: 'POST',
|
|
86
|
+
}),
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
expect(response.status).toBe(403)
|
|
90
|
+
expect(await response.json()).toMatchObject({ error: { code: 'api_key_forbidden' } })
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
test('requires API-key authentication', async () => {
|
|
94
|
+
const app = TestApp.create()
|
|
95
|
+
const response = await app.fetch(
|
|
96
|
+
new Request('http://tempo-api.test/v1/mpp/verify', {
|
|
97
|
+
body: JSON.stringify(input),
|
|
98
|
+
headers: { 'content-type': 'application/json' },
|
|
99
|
+
method: 'POST',
|
|
100
|
+
}),
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
expect(response.status).toBe(401)
|
|
104
|
+
expect(await response.json()).toMatchObject({ error: { code: 'api_key_missing' } })
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
test.each(['/v1/mpp/verify', '/v1/mpp/broadcast'])(
|
|
108
|
+
'%s rejects malformed credential input before the disabled handler',
|
|
109
|
+
async (path) => {
|
|
110
|
+
const app = TestApp.create({ auth: { keys: [key] } })
|
|
111
|
+
const response = await app.fetch(
|
|
112
|
+
new Request(`http://tempo-api.test${path}`, {
|
|
113
|
+
body: JSON.stringify({}),
|
|
114
|
+
headers: { 'content-type': 'application/json', authorization: 'Bearer secret_mpp' },
|
|
115
|
+
method: 'POST',
|
|
116
|
+
}),
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
expect(response.status).toBe(400)
|
|
120
|
+
expect(await response.json()).toMatchObject({ error: { code: 'body_invalid' } })
|
|
121
|
+
},
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
test.each(['/v1/mpp/verify', '/v1/mpp/broadcast'])(
|
|
125
|
+
'%s requires a credential payload',
|
|
126
|
+
async (path) => {
|
|
127
|
+
const app = TestApp.create({ auth: { keys: [key] } })
|
|
128
|
+
const response = await app.fetch(
|
|
129
|
+
new Request(`http://tempo-api.test${path}`, {
|
|
130
|
+
body: JSON.stringify({ challenge: input.challenge }),
|
|
131
|
+
headers: { 'content-type': 'application/json', authorization: 'Bearer secret_mpp' },
|
|
132
|
+
method: 'POST',
|
|
133
|
+
}),
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
expect(response.status).toBe(400)
|
|
137
|
+
expect(await response.json()).toMatchObject({ error: { code: 'body_invalid' } })
|
|
138
|
+
},
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
test('publishes the MPP endpoints', async () => {
|
|
142
|
+
const app = TestApp.create()
|
|
143
|
+
const spec = (await (await app.request('/openapi.json')).json()) as {
|
|
144
|
+
paths: Record<string, { post?: { responses?: Record<string, unknown> } }>
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
for (const path of ['/v1/mpp/verify', '/v1/mpp/broadcast']) {
|
|
148
|
+
expect(spec.paths[path]?.post?.responses?.['200']).toBeDefined()
|
|
149
|
+
expect(spec.paths[path]?.post?.responses?.['503']).toBeDefined()
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const response = JSON.stringify(spec.paths['/v1/mpp/verify']?.post?.responses?.['200'])
|
|
153
|
+
for (const description of errorDescriptions) expect(response).toContain(description)
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
describe('MPP relay schemas', () => {
|
|
158
|
+
test('parses public relay contracts', () => {
|
|
159
|
+
expect(Mpp.schema.RelayInput.parse(input)).toEqual(input)
|
|
160
|
+
expect(Mpp.schema.VerifyResponse.parse({ success: true })).toEqual({ success: true })
|
|
161
|
+
expect(
|
|
162
|
+
Mpp.schema.BroadcastResponse.parse({
|
|
163
|
+
receipt: {
|
|
164
|
+
method: 'tempo',
|
|
165
|
+
reference: `0x${'22'.repeat(32)}`,
|
|
166
|
+
timestamp: '2026-07-20T18:30:00.000Z',
|
|
167
|
+
},
|
|
168
|
+
success: true,
|
|
169
|
+
}),
|
|
170
|
+
).toMatchInlineSnapshot(`
|
|
171
|
+
{
|
|
172
|
+
"receipt": {
|
|
173
|
+
"method": "tempo",
|
|
174
|
+
"reference": "0x2222222222222222222222222222222222222222222222222222222222222222",
|
|
175
|
+
"timestamp": "2026-07-20T18:30:00.000Z",
|
|
176
|
+
},
|
|
177
|
+
"success": true,
|
|
178
|
+
}
|
|
179
|
+
`)
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
test.each(errorCodes)('accepts the %s error code', (code) => {
|
|
183
|
+
expect(Mpp.schema.RelayError.parse({ code })).toEqual({ code })
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
test('publishes the relay error code type', () => {
|
|
187
|
+
expectTypeOf<Mpp.RelayErrorCode>().toEqualTypeOf<(typeof errorCodes)[number]>()
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
test('rejects an unknown error code', () => {
|
|
191
|
+
expect(Mpp.schema.RelayError.safeParse({ code: 'unexpected' }).success).toBe(false)
|
|
192
|
+
})
|
|
193
|
+
})
|