diff --git a/src/token-processor/util/metadata-helpers.ts b/src/token-processor/util/metadata-helpers.ts index 9fb2591d..53a942a1 100644 --- a/src/token-processor/util/metadata-helpers.ts +++ b/src/token-processor/util/metadata-helpers.ts @@ -318,6 +318,9 @@ function parseJsonMetadata(url: string, content?: string): RawMetadata { try { const result = JSON5.parse(content); if (RawMetadataCType.Check(result)) { + if (result.name === undefined) { + throw new MetadataParseError(`Metadata does not contain a 'name' value: ${url}`); + } return result; } else { throw new MetadataParseError(`Invalid raw metadata JSON schema: ${url}`); @@ -356,15 +359,6 @@ export function getFetchableDecentralizedStorageUrl(uri: string): URL { throw new MetadataParseError(`Unsupported uri protocol: ${uri}`); } -function isUriFromDecentralizedStorage(uri: string): boolean { - return ( - uri.startsWith('ipfs:') || - uri.startsWith('ipns:') || - uri.startsWith('ar:') || - uri.startsWith('https://cloudflare-ipfs.com') - ); -} - export function parseDataUrl( s: string ): diff --git a/tests/token-queue/metadata-helpers.test.ts b/tests/token-queue/metadata-helpers.test.ts index 34be6a0a..1eb84244 100644 --- a/tests/token-queue/metadata-helpers.test.ts +++ b/tests/token-queue/metadata-helpers.test.ts @@ -1,6 +1,6 @@ import { MockAgent, setGlobalDispatcher } from 'undici'; import { ENV } from '../../src/env'; -import { MetadataHttpError } from '../../src/token-processor/util/errors'; +import { MetadataHttpError, MetadataParseError } from '../../src/token-processor/util/errors'; import { getFetchableDecentralizedStorageUrl, getMetadataFromUri, @@ -100,6 +100,27 @@ describe('Metadata Helpers', () => { ).resolves.not.toThrow(); }); + test('throws when metadata does not contain a name', async () => { + const crashPunks1 = { + sip: 16, + image: 'ipfs://Qmb84UcaMr1MUwNbYBnXWHM3kEaDcYrKuPWwyRLVTNKELC/294.png', + }; + const agent = new MockAgent(); + agent.disableNetConnect(); + agent + .get('http://test.io') + .intercept({ + path: '/1.json', + method: 'GET', + }) + .reply(200, crashPunks1); + setGlobalDispatcher(agent); + + await expect(getMetadataFromUri('http://test.io/1.json', 'ABCD.test', 1n)).rejects.toThrow( + MetadataParseError + ); + }); + test('fetches typed raw metadata', async () => { const json = { version: 1, diff --git a/tests/token-queue/process-token-job.test.ts b/tests/token-queue/process-token-job.test.ts index 9bfdf232..76e91411 100644 --- a/tests/token-queue/process-token-job.test.ts +++ b/tests/token-queue/process-token-job.test.ts @@ -17,6 +17,7 @@ import { RetryableJobError } from '../../src/token-processor/queue/errors'; import { TooManyRequestsHttpError } from '../../src/token-processor/util/errors'; import { cycleMigrations } from '@hirosystems/api-toolkit'; import { insertAndEnqueueTestContractWithTokens } from '../helpers'; +import { InvalidTokenError } from '../../src/pg/errors'; describe('ProcessTokenJob', () => { let db: PgStore; @@ -683,7 +684,7 @@ describe('ProcessTokenJob', () => { ); }); - test('SIP-016 non-compliant metadata is ignored', async () => { + test('SIP-016 non-compliant metadata throws error', async () => { const metadata = { id: '62624cc0065e986192fb9f33', media: 'https://sf-stage-s3.s3.us-west-1.amazonaws.com/riyasen_suit.png', @@ -718,11 +719,12 @@ describe('ProcessTokenJob', () => { await new ProcessTokenJob({ db, job: tokenJob }).work(); - const bundle = await db.getTokenMetadataBundle({ - contractPrincipal: 'ABCD.test-nft', - tokenNumber: 1, - }); - expect(bundle?.metadataLocale).toBeUndefined(); + await expect( + db.getTokenMetadataBundle({ + contractPrincipal: 'ABCD.test-nft', + tokenNumber: 1, + }) + ).rejects.toThrow(InvalidTokenError); }); });