diff --git a/src/types/database-generated.types.ts b/src/types/database-generated.types.ts index a850a35..a5979c1 100644 --- a/src/types/database-generated.types.ts +++ b/src/types/database-generated.types.ts @@ -700,6 +700,7 @@ export type Database = { owner_id: string | null path_tokens: string[] | null updated_at: string | null + user_metadata: Json | null version: string | null } Insert: { @@ -713,6 +714,7 @@ export type Database = { owner_id?: string | null path_tokens?: string[] | null updated_at?: string | null + user_metadata?: Json | null version?: string | null } Update: { @@ -726,6 +728,7 @@ export type Database = { owner_id?: string | null path_tokens?: string[] | null updated_at?: string | null + user_metadata?: Json | null version?: string | null } Relationships: [ @@ -747,6 +750,7 @@ export type Database = { key: string owner_id: string | null upload_signature: string + user_metadata: Json | null version: string } Insert: { @@ -757,6 +761,7 @@ export type Database = { key: string owner_id?: string | null upload_signature: string + user_metadata?: Json | null version: string } Update: { @@ -767,6 +772,7 @@ export type Database = { key?: string owner_id?: string | null upload_signature?: string + user_metadata?: Json | null version?: string } Relationships: [ diff --git a/test/parsing/parseUriEvent.test.ts b/test/parsing/parseUriEvent.test.ts new file mode 100644 index 0000000..771469b --- /dev/null +++ b/test/parsing/parseUriEvent.test.ts @@ -0,0 +1,226 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { parseUriEvent } from "../../src/parsing/parseUriEvent"; +import { supabase } from "../../src/clients/supabaseClient"; +import { mockMerkleTree } from "../test-utils/mockMerkleTree"; +import { mockGeoJson } from "../test-utils/mockGeoJson"; + +// Mock dependencies +vi.mock("@/clients/supabaseClient", () => ({ + supabase: { + rpc: vi.fn(() => ({ + throwOnError: () => ({ data: "mock-claim-id" }), + })), + }, +})); + +describe("parseUriEvent", () => { + const mockBlock = { + timestamp: "1234567890", + blockNumber: 123456, + }; + + const mockContext = { + getData: vi.fn(), + block: mockBlock, + chain_id: 1, + }; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("should return unparsed result for invalid URIs", async () => { + const mockEvent = { + address: "0x1234567890123456789012345678901234567890", + params: { + value: "ipfs://null", + id: BigInt(1), + }, + }; + + const result = await parseUriEvent({ + event: mockEvent, + context: mockContext, + }); + + expect(result).toEqual([ + { + metadata: { + uri: "ipfs://null", + parsed: false, + }, + }, + ]); + }); + + it("should return unparsed result when getData fails", async () => { + const mockEvent = { + address: "0x1234567890123456789012345678901234567890", + params: { + value: "ipfs://valid", + id: BigInt(1), + }, + }; + + mockContext.getData.mockResolvedValueOnce(null); + + const result = await parseUriEvent({ + event: mockEvent, + context: mockContext, + }); + + expect(result).toEqual([ + { + metadata: { + uri: "ipfs://valid", + parsed: false, + }, + }, + ]); + }); + + it("should successfully parse valid metadata without allowlist", async () => { + const mockEvent = { + address: "0x1234567890123456789012345678901234567890", + params: { + value: "ipfs://valid", + id: BigInt(1), + }, + }; + + const mockMetadata = { + name: "Test Hypercert", + description: "Test Description", + image: "ipfs://image", + hypercert: { + contributors: { value: ["contributor1"] }, + impact_scope: { value: ["scope1"] }, + work_scope: { value: ["work1"] }, + impact_timeframe: { value: [1000, 2000] }, + work_timeframe: { value: [1000, 2000] }, + rights: { value: ["right1"] }, + }, + }; + + mockContext.getData.mockResolvedValueOnce(mockMetadata); + + const result = await parseUriEvent({ + event: mockEvent, + context: mockContext, + }); + + expect(result[0].metadata).toMatchObject({ + name: "Test Hypercert", + description: "Test Description", + image: "ipfs://image", + contributors: ["contributor1"], + parsed: true, + uri: "ipfs://valid", + }); + }); + + it("should handle metadata with allowlist", async () => { + const mockEvent = { + address: "0x1234567890123456789012345678901234567890", + params: { + value: "ipfs://valid", + id: BigInt(1), + }, + }; + + const mockMetadata = { + name: "Test Hypercert", + description: "Test Description", + image: "ipfs://image", + allowList: "ipfs://allowlist", + hypercert: { + contributors: { value: ["contributor1"] }, + impact_scope: { value: ["scope1"] }, + work_scope: { value: ["work1"] }, + impact_timeframe: { value: [1000, 2000] }, + work_timeframe: { value: [1000, 2000] }, + rights: { value: ["right1"] }, + }, + }; + + // Mock allowlist data + const mockAllowlistData = mockMerkleTree; + + console.log("mockAllowlistData", mockAllowlistData); + + mockContext.getData + .mockResolvedValueOnce(mockMetadata) + .mockResolvedValueOnce(mockAllowlistData); + + const result = await parseUriEvent({ + event: mockEvent, + context: mockContext, + }); + + expect(result[0].metadata.allow_list_uri).toBe("ipfs://allowlist"); + expect(result[0].allow_list).toBeDefined(); + expect(result[0].allow_list?.parsed).toBe(true); + expect(result[0].hypercert_allow_list).toBeDefined(); + expect(supabase.rpc).toHaveBeenCalled(); + }); + + it("should throw error for invalid event data", async () => { + const mockEvent = { + address: "invalid-address", + params: { + value: "ipfs://valid", + id: BigInt(1), + }, + }; + + await expect( + parseUriEvent({ + event: mockEvent, + context: mockContext, + }), + ).rejects.toThrow(); + }); + + describe("should support different trait_types in metadata.properties", () => { + it("should successfully parse valid metadata with GeoJSON", async () => { + const mockEvent = { + address: "0x1234567890123456789012345678901234567890", + params: { + value: "ipfs://valid", + id: BigInt(1), + }, + }; + + const mockMetadata = { + name: "Test Hypercert", + description: "Test Description", + image: "ipfs://image", + hypercert: { + contributors: { value: ["contributor1"] }, + impact_scope: { value: ["scope1"] }, + work_scope: { value: ["work1"] }, + impact_timeframe: { value: [1000, 2000] }, + work_timeframe: { value: [1000, 2000] }, + rights: { value: ["right1"] }, + }, + properties: [mockGeoJson], + }; + + mockContext.getData.mockResolvedValueOnce(mockMetadata); + + const result = await parseUriEvent({ + event: mockEvent, + context: mockContext, + }); + + expect(result[0].metadata).toMatchObject({ + name: "Test Hypercert", + description: "Test Description", + image: "ipfs://image", + contributors: ["contributor1"], + parsed: true, + uri: "ipfs://valid", + }); + }); + }); +}); diff --git a/test/test-utils/mockGeoJson.ts b/test/test-utils/mockGeoJson.ts new file mode 100644 index 0000000..ca1d5bf --- /dev/null +++ b/test/test-utils/mockGeoJson.ts @@ -0,0 +1,5 @@ +export const mockGeoJson = { + trait_type: "GeoJSON", + value: + '{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"MultiPolygon","coordinates":[[[[126.30769314,8.9152762],[126.30784606,8.91580754],[126.30770658,8.91596653],[126.3076288,8.91707414],[126.30783265,8.91750717],[126.30782192,8.91772975],[126.30778168,8.91795763],[126.30752956,8.91827826],[126.30739008,8.91869692],[126.30757515,8.91906789],[126.30747859,8.92022054],[126.30793725,8.92079819],[126.30859439,8.92081144],[126.30859439,8.92103402],[126.30876874,8.92129105],[126.30901282,8.92162227],[126.30935614,8.92204093],[126.31048803,8.92412363],[126.31056911,8.92432947],[126.31051815,8.92449376],[126.31041086,8.92460769],[126.31024724,8.92465539],[126.31013459,8.92465274],[126.30864865,8.92435862],[126.30622125,8.92377038],[126.30609518,8.92371208],[126.30593157,8.92361404],[126.30512422,8.92272108],[126.30507326,8.92263364],[126.30512422,8.92211958],[126.3051135,8.92146244],[126.30499011,8.92077881],[126.30483455,8.92051913],[126.30409157,8.92015611],[126.30428469,8.91981164],[126.30446172,8.91963676],[126.30426055,8.91938503],[126.30393064,8.91922074],[126.30360878,8.91906175],[126.30350417,8.91861394],[126.30345796,8.91852915],[126.30325947,8.91853445],[126.3030288,8.91756993],[126.30308781,8.9173958],[126.30340968,8.91714142],[126.30363498,8.9166936],[126.30769314,8.9152762]]]]},"properties":{"name":"ForestBench - Cagwait"}}]}', +}; diff --git a/test/test-utils/mockMerkleTree.ts b/test/test-utils/mockMerkleTree.ts new file mode 100644 index 0000000..7839fba --- /dev/null +++ b/test/test-utils/mockMerkleTree.ts @@ -0,0 +1,8 @@ +const stringContent = + '{"format":"standard-v1","tree":["0xe582f894ed4599f64aed0c4f6ea1ed2d2f7bca47a11c984e63d823a38d4f43b6"],"values":[{"value":["0x59266D85D94666D037C1e32dAa8FaC9E95CdaFEf",100],"treeIndex":0}],"leafEncoding":["address","uint256"]}'; + +export const mockMerkleTree = stringContent; +export const incorrectMerkleTree = stringContent.replace( + '"leafEncoding":["address","uint256"]', + '"leafEncoding":[null,null]', +); diff --git a/test/test-utils/mockMetadata.ts b/test/test-utils/mockMetadata.ts new file mode 100644 index 0000000..4430e4f --- /dev/null +++ b/test/test-utils/mockMetadata.ts @@ -0,0 +1,72 @@ +const jsonContent = `{ + "name": "Example Hypercert", + "description": "This is where the description of the hypercert will go.", + "external_url": "https://hypercerts.xyz", + "image": "ipfs://bafybeifs7abhcooeelyjxmnlrcd5kuupfl5czhtyub2imzxzccrhzz3bem", + "version": "1.0.0", + "properties": [ + { + "trait_type": "Example Property 1", + "value": "Some text here" + }, + { + "trait_type": "Example Property 2", + "value": "More text here" + } + ], + "hypercert": { + "impact_scope": { + "name": "Impact Scope", + "value": [ + "all" + ], + "display_value": "All" + }, + "work_scope": { + "name": "Work Scope", + "value": [ + "art design", + "metadata standards" + ], + "display_value": "Art Design & Metadata Standards" + }, + "work_timeframe": { + "name": "Work Timeframe", + "value": [ + 1663819200, + 1673163072 + ], + "display_value": "2022-09-22 \u2192 2023-01-08" + }, + "impact_timeframe": { + "name": "Impact Timeframe", + "value": [ + 1673163072, + 0 + ], + "display_value": "2023-01-08 \u2192 Indefinite" + }, + "contributors": { + "name": "Contributors", + "value": [ + "0x799B774204A348E1182fE01074C51444bA70A149" + ], + "display_value": "0x799...149" + }, + "rights": { + "name": "Rights", + "value": [ + "public display", + "-transfers" + ], + "display_value": "Public display" + } + } +}`; + +const mockMetadata = JSON.parse(jsonContent); + +const incorrectMetadata = JSON.parse(jsonContent); +incorrectMetadata.hypercert = ""; + +export { mockMetadata, incorrectMetadata }; diff --git a/vitest.config.ts b/vitest.config.ts index e4abe64..7af9b87 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -21,10 +21,10 @@ export default defineConfig({ // If you want a coverage reports even if your tests are failing, include the reportOnFailure option reportOnFailure: true, thresholds: { - lines: 15, - branches: 35, - functions: 50, - statements: 15, + lines: 20, + branches: 44, + functions: 58, + statements: 21, }, include: ["src/**/*.ts"], exclude: [