Files
2026-10-25 17:36:44 +00:00

92 lines
3.7 KiB
TypeScript

/**
* `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<Record<string, unknown>>;
let posted: Array<Array<Record<string, unknown>>>;
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<Record<string, unknown>>;
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);
});
});