import { testClient } from 'hono/testing' import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts' import type * as z from 'zod/mini' import * as TestApp from '../../../../test/App.js' import type * as Db from '../../../db/Db.js' import * as Memberships from '../../../db/tables/memberships.js' import * as VerifiedTokenRequests from '../../../db/tables/verifiedTokenRequests.js' import * as VerifiedTokens from '../../../internal/VerifiedTokens.js' import type { schema } from './verified-token-requests.js' /** Origin pinned for SIWE domain binding; Hono test requests use this host. */ const origin = 'http://localhost' /** RPC request options carrying a session cookie. */ function as(cookie: string) { return { headers: { cookie } } as const } function createApp(db: Db.Source = TestApp.database()) { const app = TestApp.create({ db, session: { wallet: { origin } } }) return { app, client: testClient(app) } } /** Signs in a fresh scripted account and resolves its user id. */ async function session({ app, client }: ReturnType) { const { cookie } = await TestApp.signIn(app, privateKeyToAccount(generatePrivateKey())) if (!cookie) throw new Error('Sign-in did not establish a session') const me = await client.v1.me.$get(undefined, as(cookie)) expect(me.status).toBe(200) if (me.status !== 200) throw new Error(`Failed to resolve session user: ${me.status}`) const { id: userId } = await me.json() return { cookie, userId } } /** Creates an owner and organization through their public write boundaries. */ async function setup(context: ReturnType) { const owner = await session(context) const response = await context.client.v1.orgs.$post({ json: { name: 'Acme' } }, as(owner.cookie)) expect(response.status).toBe(200) if (response.status !== 200) throw new Error(`Failed to create organization: ${response.status}`) const org = await response.json() return { org, owner } } const token = { address: '0x20c00000000000000000000000000000000000aa', chainId: 42431, currency: 'USD', decimals: 6, name: 'Test USD', symbol: 'tUSD', } satisfies Partial> test('publishes generator-ready OpenAPI contracts', async () => { const { app } = createApp() const spec = await (await app.request('/openapi.json')).json() const collection = spec.paths['/v1/orgs/{orgId}/verified-token-requests'] const detail = spec.paths['/v1/orgs/{orgId}/verified-token-requests/{requestId}'].delete expect({ components: [ 'CancelVerifiedTokenRequestResponse', 'CreateVerifiedTokenRequestInput', 'VerifiedTokenRequest', 'VerifiedTokenRequestList', ].filter((name) => spec.components.schemas[name]), create: { errors: { 400: collection.post.responses[400].content['application/json'].schema, 404: collection.post.responses[404].content['application/json'].schema, 409: collection.post.responses[409].content['application/json'].schema, }, operationId: collection.post.operationId, request: collection.post.requestBody.content['application/json'].schema, response: collection.post.responses[200].content['application/json'].schema, }, delete: { errors: { 400: detail.responses[400].content['application/json'].schema, 404: detail.responses[404].content['application/json'].schema, 409: detail.responses[409].content['application/json'].schema, }, operationId: detail.operationId, response: detail.responses[200].content['application/json'].schema, }, list: { errors: { 400: collection.get.responses[400].content['application/json'].schema, 404: collection.get.responses[404].content['application/json'].schema, }, operationId: collection.get.operationId, response: collection.get.responses[200].content['application/json'].schema, }, timestamps: { createdAt: spec.components.schemas.VerifiedTokenRequest.properties.createdAt.format, reviewedAt: spec.components.schemas.VerifiedTokenRequest.properties.reviewedAt.anyOf[0].format, updatedAt: spec.components.schemas.VerifiedTokenRequest.properties.updatedAt.format, }, }).toMatchInlineSnapshot(` { "components": [ "CancelVerifiedTokenRequestResponse", "CreateVerifiedTokenRequestInput", "VerifiedTokenRequest", "VerifiedTokenRequestList", ], "create": { "errors": { "400": { "$ref": "#/components/schemas/ApiKeyMalformedOrBodyInvalidOrParamInvalidError", }, "404": { "$ref": "#/components/schemas/OrganizationNotFoundError", }, "409": { "$ref": "#/components/schemas/RequestLimitReachedOrRequestPendingExistsOrVerifiedSymbolExistsOrVerifiedTokenExistsError", }, }, "operationId": "createVerifiedTokenRequest", "request": { "$ref": "#/components/schemas/CreateVerifiedTokenRequestInput", }, "response": { "$ref": "#/components/schemas/VerifiedTokenRequest", }, }, "delete": { "errors": { "400": { "$ref": "#/components/schemas/ApiKeyMalformedOrParamInvalidError", }, "404": { "$ref": "#/components/schemas/OrganizationNotFoundOrVerifiedTokenRequestNotFoundError", }, "409": { "$ref": "#/components/schemas/RequestNotPendingError", }, }, "operationId": "cancelVerifiedTokenRequest", "response": { "$ref": "#/components/schemas/CancelVerifiedTokenRequestResponse", }, }, "list": { "errors": { "400": { "$ref": "#/components/schemas/ApiKeyMalformedOrParamInvalidError", }, "404": { "$ref": "#/components/schemas/OrganizationNotFoundError", }, }, "operationId": "listVerifiedTokenRequests", "response": { "$ref": "#/components/schemas/VerifiedTokenRequestList", }, }, "timestamps": { "createdAt": "date-time", "reviewedAt": "date-time", "updatedAt": "date-time", }, } `) }) describe('POST /v1/orgs/:orgId/verified-token-requests', () => { test('members submit requests', async () => { const db = TestApp.database() const context = createApp(db) const requests = context.client.v1.orgs[':orgId{org_[A-Za-z0-9_-]+}']['verified-token-requests'] const { org } = await setup(context) const member = await session(context) await Memberships.create(db, { orgId: org.id, role: 'member', userId: member.userId }) // Checksummed input normalizes to the stored lowercase address. const created = await requests.$post( { json: { ...token, address: '0x20C00000000000000000000000000000000000AA', note: 'We are the issuer.' }, // prettier-ignore param: { orgId: org.id }, }, as(member.cookie), ) expect(created.status).toBe(200) if (created.status !== 200) throw new Error(`Failed to create request: ${created.status}`) const request = await created.json() expect(request.id).toMatch(/^vtr_/) expect(request).toMatchObject({ address: token.address, note: 'We are the issuer.', orgId: org.id, requestedBy: member.userId, reviewNote: null, reviewedAt: null, status: 'pending', }) }) test('rejects tokens already on the verified list', async () => { const db = TestApp.database() const context = createApp(db) const requests = context.client.v1.orgs[':orgId{org_[A-Za-z0-9_-]+}']['verified-token-requests'] const { org, owner } = await setup(context) await VerifiedTokens.create(db, { ...token, name: 'Listed USD' }) const byAddress = await requests.$post( { json: token, param: { orgId: org.id } }, as(owner.cookie), ) expect(byAddress.status).toBe(409) const addressBody = (await byAddress.json()) as { error?: { code?: string } } expect(addressBody.error?.code).toBe('verified_token_exists') // Same symbol (case-insensitive), different address. const bySymbol = await requests.$post( { json: { ...token, address: '0x20c00000000000000000000000000000000000bb', symbol: 'TUSD' }, param: { orgId: org.id }, }, as(owner.cookie), ) expect(bySymbol.status).toBe(409) const symbolBody = (await bySymbol.json()) as { error?: { code?: string } } expect(symbolBody.error?.code).toBe('verified_symbol_exists') }) test('rejects a token with a pending request from any org', async () => { const context = createApp() const requests = context.client.v1.orgs[':orgId{org_[A-Za-z0-9_-]+}']['verified-token-requests'] const first = await setup(context) const second = await setup(context) const created = await requests.$post( { json: token, param: { orgId: first.org.id } }, as(first.owner.cookie), ) expect(created.status).toBe(200) const duplicate = await requests.$post( { json: token, param: { orgId: second.org.id } }, as(second.owner.cookie), ) expect(duplicate.status).toBe(409) const body = (await duplicate.json()) as { error?: { code?: string } } expect(body.error?.code).toBe('request_pending_exists') }) test('caps pending requests per organization', async () => { const context = createApp() const requests = context.client.v1.orgs[':orgId{org_[A-Za-z0-9_-]+}']['verified-token-requests'] const { org, owner } = await setup(context) for (let index = 0; index < 10; index++) { const created = await requests.$post( { json: { ...token, address: `0x20c${(index + 1).toString(16).padStart(37, '0')}` as `0x${string}`, symbol: `tUSD${index}`, }, param: { orgId: org.id }, }, as(owner.cookie), ) expect(created.status).toBe(200) } const overflow = await requests.$post( { json: { ...token, address: `0x20c${'f'.padStart(37, '0')}` as `0x${string}`, symbol: 'tOVER' }, // prettier-ignore param: { orgId: org.id }, }, as(owner.cookie), ) expect(overflow.status).toBe(409) const body = (await overflow.json()) as { error?: { code?: string } } expect(body.error?.code).toBe('request_limit_reached') }) test('accepts an SVG logo and echoes it back', async () => { const context = createApp() const requests = context.client.v1.orgs[':orgId{org_[A-Za-z0-9_-]+}']['verified-token-requests'] const { org, owner } = await setup(context) const created = await requests.$post( { json: { ...token, logo: '' }, param: { orgId: org.id }, }, as(owner.cookie), ) expect(created.status).toBe(200) if (created.status !== 200) throw new Error(`Failed to create request: ${created.status}`) // The org sees its own uploaded logo (the console list previews it). expect((await created.json()).logo).toBe('') const invalid = await requests.$post( { json: { ...token, address: '0x20c00000000000000000000000000000000000bb', logo: 'not svg', symbol: 'tEUR' }, // prettier-ignore param: { orgId: org.id }, }, as(owner.cookie), ) expect(invalid.status).toBe(400) const oversized = await requests.$post( { json: { ...token, address: '0x20c00000000000000000000000000000000000cc', logo: `${'a'.repeat(256 * 1024)}`, symbol: 'tGBP', }, param: { orgId: org.id }, }, as(owner.cookie), ) expect(oversized.status).toBe(400) }) test('caps free-text field lengths', async () => { const context = createApp() const requests = context.client.v1.orgs[':orgId{org_[A-Za-z0-9_-]+}']['verified-token-requests'] const { org, owner } = await setup(context) const oversized = [ { name: 'x'.repeat(101) }, { note: 'x'.repeat(501) }, { symbol: 'x'.repeat(21) }, ] for (const fields of oversized) { const response = await requests.$post( { json: { ...token, ...fields }, param: { orgId: org.id } }, as(owner.cookie), ) expect(response.status).toBe(400) const body = (await response.json()) as { error?: { code?: string } } expect(body.error?.code).toBe('body_invalid') } }) }) describe('GET /v1/orgs/:orgId/verified-token-requests', () => { test('lists only the organization requests', async () => { const context = createApp() const requests = context.client.v1.orgs[':orgId{org_[A-Za-z0-9_-]+}']['verified-token-requests'] const { org, owner } = await setup(context) const foreign = await setup(context) const created = await requests.$post( { json: token, param: { orgId: org.id } }, as(owner.cookie), ) expect(created.status).toBe(200) if (created.status !== 200) throw new Error(`Failed to create request: ${created.status}`) const request = await created.json() const listed = await requests.$get({ param: { orgId: org.id } }, as(owner.cookie)) expect(listed.status).toBe(200) if (listed.status !== 200) throw new Error(`Failed to list requests: ${listed.status}`) expect((await listed.json()).data.map(({ id }) => id)).toEqual([request.id]) // A non-member resolves neither the org nor its requests. const denied = await requests.$get({ param: { orgId: org.id } }, as(foreign.owner.cookie)) expect(denied.status).toBe(404) const empty = await requests.$get({ param: { orgId: foreign.org.id } }, as(foreign.owner.cookie)) // prettier-ignore expect(empty.status).toBe(200) if (empty.status !== 200) throw new Error(`Failed to list requests: ${empty.status}`) expect((await empty.json()).data).toEqual([]) }) test('review fields surface without reviewer identity', async () => { const db = TestApp.database() const context = createApp(db) const requests = context.client.v1.orgs[':orgId{org_[A-Za-z0-9_-]+}']['verified-token-requests'] const { org, owner } = await setup(context) const created = await requests.$post( { json: token, param: { orgId: org.id } }, as(owner.cookie), ) expect(created.status).toBe(200) if (created.status !== 200) throw new Error(`Failed to create request: ${created.status}`) const request = await created.json() await VerifiedTokenRequests.review(db, request.id, { reviewNote: 'Logo unreachable.', reviewedBy: 'admin@tempo.xyz', status: 'denied', }) const listed = await requests.$get({ param: { orgId: org.id } }, as(owner.cookie)) expect(listed.status).toBe(200) if (listed.status !== 200) throw new Error(`Failed to list requests: ${listed.status}`) const [row] = (await listed.json()).data expect(row).toMatchObject({ reviewNote: 'Logo unreachable.', status: 'denied' }) expect(row?.reviewedAt).toBeTruthy() // Reviewer identity stays on the admin surface. expect(row && 'reviewedBy' in row).toBe(false) }) test('requires a session', async () => { const context = createApp() const requests = context.client.v1.orgs[':orgId{org_[A-Za-z0-9_-]+}']['verified-token-requests'] const response = await requests.$get({ param: { orgId: 'org_1a2b3c4d5e6f7g8h9j0k1m2n' } }) expect(response.status).toBe(401) }) }) describe('DELETE /v1/orgs/:orgId/verified-token-requests/:requestId', () => { test('members cancel pending requests', async () => { const db = TestApp.database() const context = createApp(db) const requests = context.client.v1.orgs[':orgId{org_[A-Za-z0-9_-]+}']['verified-token-requests'] const { org, owner } = await setup(context) const member = await session(context) await Memberships.create(db, { orgId: org.id, role: 'member', userId: member.userId }) const created = await requests.$post( { json: token, param: { orgId: org.id } }, as(owner.cookie), ) expect(created.status).toBe(200) if (created.status !== 200) throw new Error(`Failed to create request: ${created.status}`) const request = await created.json() const canceled = await requests[':requestId{vtr_[A-Za-z0-9_-]+}'].$delete( { param: { orgId: org.id, requestId: request.id } }, as(member.cookie), ) expect(canceled.status).toBe(200) if (canceled.status !== 200) throw new Error(`Failed to cancel request: ${canceled.status}`) expect(await canceled.json()).toEqual({ id: request.id }) const repeat = await requests[':requestId{vtr_[A-Za-z0-9_-]+}'].$delete( { param: { orgId: org.id, requestId: request.id } }, as(member.cookie), ) expect(repeat.status).toBe(404) }) test('is org-scoped', async () => { const context = createApp() const requests = context.client.v1.orgs[':orgId{org_[A-Za-z0-9_-]+}']['verified-token-requests'] const { org, owner } = await setup(context) const foreign = await setup(context) const created = await requests.$post( { json: token, param: { orgId: org.id } }, as(owner.cookie), ) expect(created.status).toBe(200) if (created.status !== 200) throw new Error(`Failed to create request: ${created.status}`) const request = await created.json() const crossCancel = await requests[':requestId{vtr_[A-Za-z0-9_-]+}'].$delete( { param: { orgId: foreign.org.id, requestId: request.id } }, as(foreign.owner.cookie), ) expect(crossCancel.status).toBe(404) }) test('rejects reviewed requests with 409', async () => { const db = TestApp.database() const context = createApp(db) const requests = context.client.v1.orgs[':orgId{org_[A-Za-z0-9_-]+}']['verified-token-requests'] const { org, owner } = await setup(context) const created = await requests.$post( { json: token, param: { orgId: org.id } }, as(owner.cookie), ) expect(created.status).toBe(200) if (created.status !== 200) throw new Error(`Failed to create request: ${created.status}`) const request = await created.json() await VerifiedTokenRequests.review(db, request.id, { reviewedBy: 'admin@tempo.xyz', status: 'denied', }) const canceled = await requests[':requestId{vtr_[A-Za-z0-9_-]+}'].$delete( { param: { orgId: org.id, requestId: request.id } }, as(owner.cookie), ) expect(canceled.status).toBe(409) const body = (await canceled.json()) as { error?: { code?: string } } expect(body.error?.code).toBe('request_not_pending') }) })