/** * `addToSet` keeps the legacy csv-set shape under claims: it rewrites THIS * signer's own comma-joined vote, ignoring other signers' claims. fetch is * stubbed with a tiny meta-core. */ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { MetaCoreClient, ClaimSigner, verifyClaim, resetSignerForTests } from '../src/meta-core-client.js'; const KEY = '01'.repeat(32); const own = new ClaimSigner(KEY); const other = new ClaimSigner('02'.repeat(32)); /** Claims meta-core holds for the cid, and every batch posted. */ let stored: Array>; let posted: Array>>; let claimReads: string[]; function json(body: unknown, status = 200): Response { return new Response(JSON.stringify(body), { status, headers: { 'Content-Type': 'application/json' } }); } beforeEach(() => { process.env.METAMESH_SIGNING_KEY = KEY; resetSignerForTests(); stored = []; posted = []; claimReads = []; vi.stubGlobal('fetch', vi.fn(async (url: string, init?: RequestInit) => { const u = new URL(url); if (init?.method === 'GET' && u.pathname.endsWith('/claims')) { claimReads.push(u.pathname + u.search); const key = u.searchParams.get('key'); return json({ claims: stored.filter((c) => c.key === key) }); } if (init?.method === 'POST' && u.pathname === '/api/claims') { const batch = JSON.parse(String(init.body)) as Array>; posted.push(batch); return json(batch.map(() => ({ status: 'accepted' }))); } return json({ error: 'no route' }, 404); })); }); afterEach(() => { vi.unstubAllGlobals(); delete process.env.METAMESH_SIGNING_KEY; resetSignerForTests(); }); const client = () => new MetaCoreClient('http://core'); const claimOf = (s: ClaimSigner, value: string, ts: number) => ({ cid: 'bafycid', uid: s.uid, key: 'genres', value, ts, sig: 'x' }); function postedValue(): string { expect(posted).toHaveLength(1); const [c] = posted[0]; expect(c.key).toBe('genres'); expect(c.uid).toBe(own.uid); expect(verifyClaim({ ...(c as never), sig: Buffer.from(String(c.sig), 'base64') })).toBe(true); return String(c.value); } describe('addToSet (csv-set over this signer\'s own claim)', () => { it('writes the first member when this signer has no claim yet', async () => { stored = [claimOf(other, 'Comedy', 5)]; expect(await client().addToSet('bafycid', 'genres', 'Drama')).toBe(true); expect(claimReads).toEqual(['/api/meta/bafycid/claims?key=genres']); expect(postedValue()).toBe('Drama'); }); it('appends to its own newest value and ignores other signers', async () => { stored = [claimOf(own, 'Action', 1), claimOf(own, 'Drama, Thriller', 9), claimOf(other, 'Comedy,Horror', 20)]; expect(await client().addToSet('bafycid', 'genres', 'Sci-Fi/Fantasy')).toBe(true); expect(postedValue()).toBe('Drama,Thriller,Sci-Fi/Fantasy'); }); it('writes nothing when the member is already in its own value', async () => { stored = [claimOf(own, 'Drama,Thriller', 9)]; expect(await client().addToSet('bafycid', 'genres', 'Thriller')).toBe(true); expect(posted).toHaveLength(0); }); it('refuses to write when its own claims cannot be read', async () => { vi.stubGlobal('fetch', vi.fn(async (url: string, init?: RequestInit) => { if (init?.method === 'GET') return json({ error: 'nope' }, 500); return json([{ status: 'accepted' }]); })); expect(await client().addToSet('bafycid', 'genres', 'Drama')).toBe(false); }); });