import { nanoid } from 'nanoid' import * as TestApp from '../../../test/App.js' import * as Runtime from '../../../test/runtime.js' import * as Db from '../Db.js' import * as Cursor from '../../internal/Cursor.js' import * as Schema from '../../internal/Schema.js' import * as RewardEligibilityAssociations from './rewardEligibilityAssociations.js' const input = { chainId: 4217, transactionHash: `0x${'33'.repeat(32)}`, vaultAddress: Schema.Address.parse(`0x${'Aa'.repeat(20)}`), walletAddress: Schema.Address.parse(`0x${'Bb'.repeat(20)}`), } as const satisfies RewardEligibilityAssociations.upsert.Options describe('upsert', () => { test('creates a normalized association and refreshes its latest registration', async () => { vi.useFakeTimers() try { const db = TestApp.database() vi.setSystemTime('2026-08-25T20:00:00.000Z') const first = await RewardEligibilityAssociations.upsert(db, input) vi.setSystemTime('2026-08-25T21:00:00.000Z') const replay = await RewardEligibilityAssociations.upsert(db, { ...input, transactionHash: `0x${'44'.repeat(32)}`, }) expect({ first, replay }).toMatchInlineSnapshot(` { "first": { "created": true, "record": { "chainId": 4217, "firstRegisteredAt": "2026-08-25T20:00:00.000Z", "latestRegisteredAt": "2026-08-25T20:00:00.000Z", "registrationOrder": "1", "transactionHash": "0x3333333333333333333333333333333333333333333333333333333333333333", "vaultAddress": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "walletAddress": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", }, }, "replay": { "created": false, "record": { "chainId": 4217, "firstRegisteredAt": "2026-08-25T20:00:00.000Z", "latestRegisteredAt": "2026-08-25T21:00:00.000Z", "registrationOrder": "1", "transactionHash": "0x4444444444444444444444444444444444444444444444444444444444444444", "vaultAddress": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "walletAddress": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", }, }, } `) } finally { vi.useRealTimers() } }) test('creates one row across concurrent duplicates', async () => { const db = TestApp.databaseFactory() const results = await Promise.all([ RewardEligibilityAssociations.upsert(db(), input), RewardEligibilityAssociations.upsert(db(), input), ]) expect(results.filter((result) => result.created)).toHaveLength(1) }) test('does not let an older registration replace newer fields', async () => { vi.useFakeTimers() try { const db = TestApp.database() vi.setSystemTime('2026-08-25T21:00:00.000Z') const newer = await RewardEligibilityAssociations.upsert(db, { ...input, transactionHash: `0x${'44'.repeat(32)}`, }) vi.setSystemTime('2026-08-25T20:00:00.000Z') const older = await RewardEligibilityAssociations.upsert(db, input) expect(older.record).toEqual({ ...newer.record, firstRegisteredAt: '2026-08-25T20:00:00.000Z', }) } finally { vi.useRealTimers() } }) test('accepts a missing transaction hash and preserves the latest known hash', async () => { const db = TestApp.database() const first = await RewardEligibilityAssociations.upsert(db, input) const replay = await RewardEligibilityAssociations.upsert(db, { chainId: input.chainId, vaultAddress: input.vaultAddress, walletAddress: input.walletAddress, }) expect({ first: first.record.transactionHash, replay: replay.record.transactionHash }).toEqual({ first: input.transactionHash, replay: input.transactionHash, }) }) test('isolates the same association by chain', async () => { const db = TestApp.database() const results = await Promise.all([ RewardEligibilityAssociations.upsert(db, input), RewardEligibilityAssociations.upsert(db, { ...input, chainId: 42_431 }), ]) expect(results.map(({ created, record }) => ({ chainId: record.chainId, created }))).toEqual([ { chainId: 4217, created: true }, { chainId: 42_431, created: true }, ]) }) }) describe('list', () => { test('returns only response-owned association columns', async () => { const db = TestApp.database() await db.kysely.schema .alterTable('rewardEligibilityAssociations') .addColumn('futureColumn', 'text') .execute() await RewardEligibilityAssociations.upsert(db, input) const page = await RewardEligibilityAssociations.list(db, { chainId: input.chainId, limit: 10, vaultAddress: input.vaultAddress, }) expect(page.data[0]).not.toHaveProperty('futureColumn') }) test('locks and reads a schema-scoped table', async () => { const db = Db.postgres({ connectionString: Runtime.postgresUrl, schema: `t_${nanoid()}`, }) await db.migrate() await RewardEligibilityAssociations.upsert(db, input) expect( await RewardEligibilityAssociations.list(db, { chainId: input.chainId, limit: 10, vaultAddress: input.vaultAddress, }), ).toMatchObject({ data: [{ walletAddress: input.walletAddress.toLowerCase() }] }) }) test('paginates at a fixed checkpoint and excludes later registrations', async () => { const db = TestApp.database() const second = { ...input, walletAddress: Schema.Address.parse(`0x${'Cc'.repeat(20)}`), } const later = { ...input, walletAddress: Schema.Address.parse(`0x${'Dd'.repeat(20)}`), } await RewardEligibilityAssociations.upsert(db, input) await RewardEligibilityAssociations.upsert(db, second) const first = await RewardEligibilityAssociations.list(db, { chainId: input.chainId, limit: 1, vaultAddress: input.vaultAddress, }) await RewardEligibilityAssociations.upsert(db, later) if (!first.nextCursor) throw new Error('Expected another eligibility page.') const next = await RewardEligibilityAssociations.list(db, { chainId: input.chainId, cursor: first.nextCursor, limit: 1, vaultAddress: input.vaultAddress, }) const fresh = await RewardEligibilityAssociations.list(db, { chainId: input.chainId, limit: 2, vaultAddress: input.vaultAddress, }) if (!fresh.nextCursor) throw new Error('Expected a fresh eligibility cursor.') expect({ first: { checkpoint: Cursor.decode(first.nextCursor, ['uint', 'uint'])?.[0], nextCursor: first.nextCursor === null ? null : 'present', wallets: first.data.map((record) => record.walletAddress), }, fresh: { checkpoint: Cursor.decode(fresh.nextCursor, ['uint', 'uint'])?.[0], wallets: fresh.data.map((record) => record.walletAddress), }, next: { nextCursor: next.nextCursor, wallets: next.data.map((record) => record.walletAddress), }, }).toMatchInlineSnapshot(` { "first": { "checkpoint": "2", "nextCursor": "present", "wallets": [ "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", ], }, "fresh": { "checkpoint": "3", "wallets": [ "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "0xcccccccccccccccccccccccccccccccccccccccc", ], }, "next": { "nextCursor": null, "wallets": [ "0xcccccccccccccccccccccccccccccccccccccccc", ], }, } `) }) test('waits for in-flight registrations before fixing the checkpoint', async () => { const getDb = TestApp.databaseFactory() let release = () => {} let inserted = () => {} const pending = new Promise((resolve) => (release = resolve)) const started = new Promise((resolve) => (inserted = resolve)) const write = getDb().transaction(async (tx) => { await RewardEligibilityAssociations.upsert(tx, input) inserted() await pending }) await started const pagePromise = RewardEligibilityAssociations.list(getDb(), { chainId: input.chainId, limit: 10, vaultAddress: input.vaultAddress, }) release() await write expect((await pagePromise).data.map((record) => record.walletAddress)).toEqual([ input.walletAddress.toLowerCase(), ]) }) test('treats cursor integers above PostgreSQL bigint as invalid', async () => { const db = TestApp.database() await RewardEligibilityAssociations.upsert(db, input) const page = await RewardEligibilityAssociations.list(db, { chainId: input.chainId, cursor: Cursor.encode(['9223372036854775808', '9223372036854775808']), limit: 10, vaultAddress: input.vaultAddress, }) expect(page.data.map((record) => record.walletAddress)).toEqual([ input.walletAddress.toLowerCase(), ]) }) })