import * as TestAdmin from '../../../test/Admin.js' import * as TestApp from '../../../test/App.js' import * as Organizations from '../../db/tables/organizations.js' import * as VerifiedTokenRequests from '../../db/tables/verifiedTokenRequests.js' import * as Verified from './verified-tokens.js' /** A token used for write round-trips. */ const novel = { address: '0x20c00000000000000000000000000000000000ff', currency: 'GBP', decimals: 8, name: 'Test GBP', symbol: 'tGBP', } as const const chainId = '42431' as const /** Write-enabled admin client backed by a fresh schema-isolated database. */ function writes() { return TestAdmin.setup() } describe('authentication', () => { test('denied identity → 401', async () => { const { client } = TestAdmin.setup({ identify: () => null }) const response = await client['verified-tokens'].$post({ json: { ...novel, chainId: 42431 }, }) expect(response.status).toBe(401) }) }) describe('list', () => { test('empty list → data [] and null version', async () => { const { client } = writes() const response = await client['verified-tokens'].$get({ query: { chainId } }) expect(response.status).toBe(200) const body = await TestApp.json(response, Verified.schema.listVerifiedTokens.Response) expect(body.data).toEqual([]) expect(body.version).toBeNull() }) test('reflects created tokens with a version', async () => { const { client } = writes() await client['verified-tokens'].$post({ json: { ...novel, chainId: 42431 } }) const response = await client['verified-tokens'].$get({ query: { chainId } }) const body = await TestApp.json(response, Verified.schema.listVerifiedTokens.Response) expect(body.data).toHaveLength(1) expect(body.data[0]?.address).toBe(novel.address) expect(body.version).toBeTruthy() }) }) describe('write lifecycle', () => { test('create → patch → delete', async () => { const { client } = writes() const created = await client['verified-tokens'].$post({ json: { ...novel, chainId: 42431 }, }) expect(created.status).toBe(200) const createdBody = await TestApp.json(created, Verified.schema.createVerifiedToken.Response) expect(createdBody.data.address).toBe(novel.address) expect(createdBody.version).toBeTruthy() const patched = await client['verified-tokens'][':address'].$patch({ json: { name: 'Renamed GBP' }, param: { address: novel.address }, query: { chainId }, }) const patchedBody = await TestApp.json(patched, Verified.schema.patchVerifiedToken.Response) expect(patchedBody.data.name).toBe('Renamed GBP') const deleted = await client['verified-tokens'][':address'].$delete({ param: { address: novel.address }, query: { chainId }, }) expect(deleted.status).toBe(200) // Prove the delete took effect: re-creating the same address now succeeds // instead of conflicting. const recreated = await client['verified-tokens'].$post({ json: { ...novel, chainId: 42431 }, }) expect(recreated.status).toBe(200) }) test('rejects a duplicate address with 409', async () => { const { client } = writes() await client['verified-tokens'].$post({ json: { ...novel, chainId: 42431 } }) const duplicate = await client['verified-tokens'].$post({ json: { ...novel, chainId: 42431 }, }) expect(duplicate.status).toBe(409) const body = (await duplicate.json()) as { error?: { code?: string } } expect(body.error?.code).toBe('duplicate_address') }) test('returns 404 patching an unknown address', async () => { const { client } = writes() const response = await client['verified-tokens'][':address'].$patch({ json: { name: 'x' }, param: { address: novel.address }, query: { chainId }, }) expect(response.status).toBe(404) const body = (await response.json()) as { error?: { code?: string } } expect(body.error?.code).toBe('verified_token_not_found') }) test('rejects a stale If-Match with 412', async () => { const { client } = writes() await client['verified-tokens'].$post({ json: { ...novel, chainId: 42431 } }) const response = await client['verified-tokens'][':address'].$patch( { json: { name: 'x' }, param: { address: novel.address }, query: { chainId } }, { headers: { 'if-match': '"stale"' } }, ) expect(response.status).toBe(412) const body = (await response.json()) as { error?: { code?: string } } expect(body.error?.code).toBe('version_mismatch') }) test('replaces the whole list via PUT', async () => { const { client } = writes() const response = await client['verified-tokens'].$put({ json: { tokens: [novel] }, query: { chainId }, }) expect(response.status).toBe(200) const body = await TestApp.json(response, Verified.schema.replaceVerifiedTokens.Response) expect(body.data.map((token) => token.address)).toEqual([novel.address]) }) test('requires an explicit chainId on query-addressed mutations', async () => { const { client } = writes() const response = await client['verified-tokens'].$put({ json: { tokens: [novel] }, // Cast past the typed client to exercise runtime validation. query: {} as never, }) expect(response.status).toBe(400) const body = (await response.json()) as { error?: { code?: string } } expect(body.error?.code).toBe('query_invalid') }) }) describe('create with inline logo', () => { const svg = '' /** Write-enabled setup with an in-memory asset store capturing every put. */ function setup() { const stored: { key: string; content: { body: ArrayBuffer; contentType: string } }[] = [] const { client } = TestAdmin.setup({ assets: { get: async (key) => stored.find((entry) => entry.key === key)?.content, put: async (key, content) => void stored.push({ content, key }), }, }) return { client, stored } } /** Count of tokens currently in the chain's verified list. */ async function count(client: ReturnType['client']) { const response = await client['verified-tokens'].$get({ query: { chainId } }) const body = await TestApp.json(response, Verified.schema.listVerifiedTokens.Response) return body.data.length } test('uploads the logo and creates the token', async () => { const { client, stored } = setup() const response = await client['verified-tokens'].$post({ json: { ...novel, chainId: 42431, logo: svg }, }) expect(response.status).toBe(200) expect(stored).toHaveLength(1) expect(stored[0]?.key).toBe(`42431/icons/${novel.address}`) expect(new TextDecoder().decode(stored[0]?.content.body)).toBe(svg) expect(await count(client)).toBe(1) }) test('a duplicate is rejected before the logo is uploaded', async () => { const { client, stored } = setup() await client['verified-tokens'].$post({ json: { ...novel, chainId: 42431 } }) const response = await client['verified-tokens'].$post({ json: { ...novel, chainId: 42431, logo: svg }, }) expect(response.status).toBe(409) // The upload must not run for a duplicate, so an existing icon is never overwritten. expect(stored).toHaveLength(0) }) test('a failed upload leaves no token', async () => { const { client } = TestAdmin.setup({ assets: { get: async () => undefined, put: async () => { throw new Error('upload failed') }, }, }) const response = await client['verified-tokens'].$post({ json: { ...novel, chainId: 42431, logo: svg }, }) expect(response.status).toBe(502) expect(await count(client)).toBe(0) }) test('a logo without an asset writer is rejected, creating no token', async () => { const { client } = writes() const response = await client['verified-tokens'].$post({ json: { ...novel, chainId: 42431, logo: svg }, }) expect(response.status).toBe(404) const body = (await response.json()) as { error?: { code?: string } } expect(body.error?.code).toBe('assets_not_enabled') expect(await count(client)).toBe(0) }) test('a non-SVG logo is rejected with 415, creating no token', async () => { const { client, stored } = setup() const response = await client['verified-tokens'].$post({ json: { ...novel, chainId: 42431, logo: 'not svg' }, }) expect(response.status).toBe(415) expect(stored).toHaveLength(0) expect(await count(client)).toBe(0) }) }) describe('logoUri', () => { test('round-trips through create and patch', async () => { const { client } = writes() const logoUri = 'https://example.com/logo.svg' const created = await client['verified-tokens'].$post({ json: { ...novel, chainId: 42431, logoUri }, }) expect(created.status).toBe(200) const createdBody = await TestApp.json(created, Verified.schema.createVerifiedToken.Response) expect(createdBody.data.logoUri).toBe(logoUri) const patched = await client['verified-tokens'][':address'].$patch({ json: { logoUri: 'https://example.com/logo-v2.svg' }, param: { address: novel.address }, query: { chainId }, }) const patchedBody = await TestApp.json(patched, Verified.schema.patchVerifiedToken.Response) expect(patchedBody.data.logoUri).toBe('https://example.com/logo-v2.svg') // Untouched fields survive the patch. expect(patchedBody.data.symbol).toBe(novel.symbol) }) test('rejects a non-HTTPS logoUri with 400', async () => { const { client } = writes() const response = await client['verified-tokens'].$post({ json: { ...novel, chainId: 42431, logoUri: 'javascript:alert(1)' }, }) expect(response.status).toBe(400) const body = (await response.json()) as { error?: { code?: string } } expect(body.error?.code).toBe('body_invalid') }) }) describe('PUT /verified-tokens/:address/logo', () => { const svg = '' /** Setup with an in-memory asset store capturing every put. */ function uploads() { const stored: { key: string; content: { body: ArrayBuffer; contentType: string } }[] = [] const { client } = TestAdmin.setup({ assets: { get: async (key) => stored.find((entry) => entry.key === key)?.content, put: async (key, content) => { stored.push({ content, key }) }, }, }) return { client, stored } } test('stores the SVG under the main API icon key', async () => { const { client, stored } = uploads() const response = await client['verified-tokens'][':address'].logo.$put( { param: { address: novel.address }, query: { chainId } }, { headers: { 'content-type': 'image/svg+xml' }, init: { body: svg } }, ) expect(response.status).toBe(200) const body = await TestApp.json(response, Verified.schema.uploadVerifiedTokenLogo.Response) const key = `42431/icons/${novel.address}` expect(body).toEqual({ key, path: `/assets/${key}` }) expect(stored).toHaveLength(1) expect(stored[0]?.key).toBe(key) expect(stored[0]?.content.contentType).toBe('image/svg+xml') expect(new TextDecoder().decode(stored[0]?.content.body)).toBe(svg) }) test('404 when no asset writer is configured', async () => { const { client } = writes() const response = await client['verified-tokens'][':address'].logo.$put( { param: { address: novel.address }, query: { chainId } }, { headers: { 'content-type': 'image/svg+xml' }, init: { body: svg } }, ) expect(response.status).toBe(404) const body = (await response.json()) as { error?: { code?: string } } expect(body.error?.code).toBe('assets_not_enabled') }) test('rejects a non-SVG content type with 415', async () => { const { client, stored } = uploads() const response = await client['verified-tokens'][':address'].logo.$put( { param: { address: novel.address }, query: { chainId } }, { headers: { 'content-type': 'image/png' }, init: { body: svg } }, ) expect(response.status).toBe(415) expect(stored).toHaveLength(0) }) test('rejects an empty body with 400', async () => { const { client, stored } = uploads() const response = await client['verified-tokens'][':address'].logo.$put( { param: { address: novel.address }, query: { chainId } }, { headers: { 'content-type': 'image/svg+xml' } }, ) expect(response.status).toBe(400) expect(stored).toHaveLength(0) }) test('rejects an oversized body with 413', async () => { const { client, stored } = uploads() const response = await client['verified-tokens'][':address'].logo.$put( { param: { address: novel.address }, query: { chainId } }, { headers: { 'content-type': 'image/svg+xml' }, init: { body: 'x'.repeat(256 * 1024 + 1) }, }, ) expect(response.status).toBe(413) expect(stored).toHaveLength(0) }) }) /** A stored verification request seeded through the repo write boundary. */ const requestInput = { address: '0x20c00000000000000000000000000000000000aa', chainId: 42431, currency: 'USD', decimals: 6, name: 'Requested USD', orgId: 'org_1a2b3c4d5e6f7g8h9j0k1m2n', requestedBy: 'usr_1a2b3c4d5e6f7g8h9j0k1m2n', symbol: 'rUSD', } as const describe('GET /verified-tokens/requests', () => { test('denied identity → 401', async () => { const { client } = TestAdmin.setup({ identify: () => null }) const response = await client['verified-tokens'].requests.$get({ query: {} }) expect(response.status).toBe(401) }) test('lists the queue by status with org names', async () => { const { client, db } = TestAdmin.setup() const org = await Organizations.create(db, { name: 'Acme' }) const pending = await VerifiedTokenRequests.create(db, { ...requestInput, logo: '', orgId: org.id, }) const denied = await VerifiedTokenRequests.create(db, { ...requestInput, address: '0x20c00000000000000000000000000000000000bb', symbol: 'rEUR', }) await VerifiedTokenRequests.review(db, denied!.id, { reviewedBy: 'admin@tempo.xyz', status: 'denied', }) const queue = await client['verified-tokens'].requests.$get({ query: {} }) expect(queue.status).toBe(200) const queueBody = await TestApp.json(queue, Verified.schema.listVerifiedTokenRequests.Response) expect(queueBody.data.map(({ id, orgName }) => ({ id, orgName }))).toEqual([ { id: pending!.id, orgName: 'Acme' }, ]) // The uploaded SVG surfaces to reviewers. expect(queueBody.data[0]?.logo).toBe('') const reviewed = await client['verified-tokens'].requests.$get({ query: { status: 'denied' } }) const reviewedBody = await TestApp.json(reviewed, Verified.schema.listVerifiedTokenRequests.Response) // prettier-ignore expect(reviewedBody.data.map(({ id }) => id)).toEqual([denied!.id]) }) }) describe('POST /verified-tokens/requests/:requestId/approve', () => { test('publishes the token and stamps the review', async () => { const { client, db } = TestAdmin.setup() const org = await Organizations.create(db, { name: 'Acme' }) const request = await VerifiedTokenRequests.create(db, { ...requestInput, orgId: org.id }) const approved = await client['verified-tokens'].requests[':requestId'].approve.$post({ param: { requestId: request!.id }, }) expect(approved.status).toBe(200) const body = await TestApp.json(approved, Verified.schema.reviewVerifiedTokenRequest.Response) expect(body.data).toMatchObject({ orgName: 'Acme', reviewedBy: TestAdmin.identity.email, status: 'approved', }) expect(Date.parse(body.data.reviewedAt!)).not.toBeNaN() const listed = await client['verified-tokens'].$get({ query: { chainId } }) const listedBody = await TestApp.json(listed, Verified.schema.listVerifiedTokens.Response) expect(listedBody.data.map(({ address }) => address)).toEqual([requestInput.address]) const repeat = await client['verified-tokens'].requests[':requestId'].approve.$post({ param: { requestId: request!.id }, }) expect(repeat.status).toBe(409) const repeatBody = (await repeat.json()) as { error?: { code?: string } } expect(repeatBody.error?.code).toBe('request_not_pending') }) test('publishes an uploaded logo to the asset store', async () => { const svg = '' const stored: { key: string; content: { body: ArrayBuffer; contentType: string } }[] = [] const { client, db } = TestAdmin.setup({ assets: { get: async (key) => stored.find((entry) => entry.key === key)?.content, put: async (key, content) => void stored.push({ content, key }), }, }) const request = await VerifiedTokenRequests.create(db, { ...requestInput, logo: svg }) const approved = await client['verified-tokens'].requests[':requestId'].approve.$post({ param: { requestId: request!.id }, }) expect(approved.status).toBe(200) expect(stored).toHaveLength(1) expect(stored[0]?.key).toBe(`42431/icons/${requestInput.address}`) expect(new TextDecoder().decode(stored[0]?.content.body)).toBe(svg) const listed = await client['verified-tokens'].$get({ query: { chainId } }) const listedBody = await TestApp.json(listed, Verified.schema.listVerifiedTokens.Response) expect(listedBody.data.map(({ address }) => address)).toEqual([requestInput.address]) }) test('rejects an uploaded logo without an asset writer, keeping the request pending', async () => { const { client, db } = TestAdmin.setup() const request = await VerifiedTokenRequests.create(db, { ...requestInput, logo: '', }) const approved = await client['verified-tokens'].requests[':requestId'].approve.$post({ param: { requestId: request!.id }, }) expect(approved.status).toBe(404) const body = (await approved.json()) as { error?: { code?: string } } expect(body.error?.code).toBe('assets_not_enabled') const record = await VerifiedTokenRequests.get(db, request!.id) expect(record?.status).toBe('pending') }) test('converges when the token is already verified', async () => { const { client, db } = TestAdmin.setup() const request = await VerifiedTokenRequests.create(db, requestInput) await client['verified-tokens'].$post({ json: { ...novel, address: requestInput.address, chainId: 42431, symbol: requestInput.symbol }, // prettier-ignore }) const approved = await client['verified-tokens'].requests[':requestId'].approve.$post({ param: { requestId: request!.id }, }) expect(approved.status).toBe(200) const body = await TestApp.json(approved, Verified.schema.reviewVerifiedTokenRequest.Response) expect(body.data.status).toBe('approved') // The pre-existing entry survives untouched; nothing was re-created. const listed = await client['verified-tokens'].$get({ query: { chainId } }) const listedBody = await TestApp.json(listed, Verified.schema.listVerifiedTokens.Response) expect(listedBody.data).toHaveLength(1) }) test('rejects a symbol conflict and keeps the request pending', async () => { const { client, db } = TestAdmin.setup() // Same symbol already verified under a different address. await client['verified-tokens'].$post({ json: { ...novel, chainId: 42431, symbol: requestInput.symbol }, }) const request = await VerifiedTokenRequests.create(db, requestInput) const approved = await client['verified-tokens'].requests[':requestId'].approve.$post({ param: { requestId: request!.id }, }) expect(approved.status).toBe(409) const body = (await approved.json()) as { error?: { code?: string } } expect(body.error?.code).toBe('duplicate_symbol') const record = await VerifiedTokenRequests.get(db, request!.id) expect(record?.status).toBe('pending') }) }) describe('POST /verified-tokens/requests/:requestId/deny', () => { test('records the review note', async () => { const { client, db } = TestAdmin.setup() const request = await VerifiedTokenRequests.create(db, requestInput) const denied = await client['verified-tokens'].requests[':requestId'].deny.$post({ json: { reviewNote: 'Logo unreachable.' }, param: { requestId: request!.id }, }) expect(denied.status).toBe(200) const body = await TestApp.json(denied, Verified.schema.reviewVerifiedTokenRequest.Response) expect(body.data).toMatchObject({ reviewNote: 'Logo unreachable.', reviewedBy: TestAdmin.identity.email, status: 'denied', }) // Nothing was published. const listed = await client['verified-tokens'].$get({ query: { chainId } }) const listedBody = await TestApp.json(listed, Verified.schema.listVerifiedTokens.Response) expect(listedBody.data).toEqual([]) }) test('unknown request id → 404', async () => { const { client } = TestAdmin.setup() const response = await client['verified-tokens'].requests[':requestId'].deny.$post({ json: {}, param: { requestId: 'vtr_1a2b3c4d5e6f7g8h9j0k1m2n' }, }) expect(response.status).toBe(404) const body = (await response.json()) as { error?: { code?: string } } expect(body.error?.code).toBe('verified_token_request_not_found') }) }) describe('GET /verified-tokens/:address/logo', () => { const svg = '' /** Setup with an in-memory asset store backing both put and get. */ function store() { const objects = new Map() const { client } = TestAdmin.setup({ assets: { get: async (key) => objects.get(key), put: async (key, content) => void objects.set(key, content), }, }) return { client, objects } } test('serves a previously uploaded SVG', async () => { const { client } = store() await client['verified-tokens'][':address'].logo.$put( { param: { address: novel.address }, query: { chainId } }, { headers: { 'content-type': 'image/svg+xml' }, init: { body: svg } }, ) const response = await client['verified-tokens'][':address'].logo.$get({ param: { address: novel.address }, query: { chainId }, }) expect(response.status).toBe(200) expect(response.headers.get('content-type')).toBe('image/svg+xml') expect(await response.text()).toBe(svg) }) test('404 when the logo is absent', async () => { const { client } = store() const response = await client['verified-tokens'][':address'].logo.$get({ param: { address: novel.address }, query: { chainId }, }) expect(response.status).toBe(404) }) test('404 when no asset reader is configured', async () => { const { client } = writes() const response = await client['verified-tokens'][':address'].logo.$get({ param: { address: novel.address }, query: { chainId }, }) expect(response.status).toBe(404) }) })