94 lines
3.1 KiB
TypeScript
94 lines
3.1 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { executeDeterministicDrawV1, ALGORITHM_VERSION_V1 } from '../src/core/randomizer/deterministic';
|
|
import { computeParticipantsSnapshotHash, computeConditionsHash } from '../src/core/randomizer/canonical';
|
|
import { DEFAULT_FILTER_RULES } from '../src/core/types/giveaway';
|
|
import { FilteredParticipant } from '../src/core/types/participant';
|
|
import { ParticipantSnapshotData } from '../src/core/types/audit';
|
|
|
|
describe('True Partial Fisher-Yates (HMAC_SHA256_FY_V1)', () => {
|
|
function createTestSnapshot(size: number): ParticipantSnapshotData {
|
|
const participants: FilteredParticipant[] = Array.from({ length: size }, (_, i) => ({
|
|
platformUserId: String(i + 1),
|
|
firstName: `User${i + 1}`,
|
|
lastName: `Surname${i + 1}`,
|
|
source: 'LIKES',
|
|
liked: true,
|
|
commented: false,
|
|
commentsCount: 0,
|
|
reposted: false,
|
|
subscribed: true,
|
|
eligible: true,
|
|
exclusionReason: null,
|
|
}));
|
|
|
|
return {
|
|
id: `snap-fy-${size}`,
|
|
giveawayId: 'gw-fy-1',
|
|
version: 1,
|
|
createdAt: '2026-08-17T12:00:00.000Z',
|
|
eligibleParticipants: participants,
|
|
participantCount: size,
|
|
participantsSnapshotHash: computeParticipantsSnapshotHash(participants),
|
|
conditionsHash: computeConditionsHash(DEFAULT_FILTER_RULES),
|
|
};
|
|
}
|
|
|
|
it('should have algorithmVersion set strictly to HMAC_SHA256_FY_V1', () => {
|
|
expect(ALGORITHM_VERSION_V1).toBe('HMAC_SHA256_FY_V1');
|
|
});
|
|
|
|
it('should select k distinct elements in 0..k-1 range with correct positions', () => {
|
|
const snapshot = createTestSnapshot(20);
|
|
const result = executeDeterministicDrawV1({
|
|
giveawayId: 'gw-fy-1',
|
|
snapshot,
|
|
totalLoadedCount: 20,
|
|
winnersCount: 3,
|
|
reserveWinnersCount: 2,
|
|
seed: 'fy-test-seed-1',
|
|
});
|
|
|
|
expect(result.winners.length).toBe(3);
|
|
expect(result.reserveWinners.length).toBe(2);
|
|
|
|
expect(result.winners[0].position).toBe(1);
|
|
expect(result.winners[1].position).toBe(2);
|
|
expect(result.winners[2].position).toBe(3);
|
|
|
|
expect(result.reserveWinners[0].position).toBe(4);
|
|
expect(result.reserveWinners[1].position).toBe(5);
|
|
|
|
const allChosen = [...result.winnerIds, ...result.reserveWinnerIds];
|
|
const uniqueChosen = new Set(allChosen);
|
|
expect(uniqueChosen.size).toBe(5);
|
|
});
|
|
|
|
it('should produce invariant selection across 100 replays', () => {
|
|
const snapshot = createTestSnapshot(10);
|
|
const seed = 'deterministic-invariant-test-seed';
|
|
|
|
const baseline = executeDeterministicDrawV1({
|
|
giveawayId: 'gw-fy-1',
|
|
snapshot,
|
|
totalLoadedCount: 10,
|
|
winnersCount: 2,
|
|
reserveWinnersCount: 1,
|
|
seed,
|
|
});
|
|
|
|
for (let i = 0; i < 100; i++) {
|
|
const current = executeDeterministicDrawV1({
|
|
giveawayId: 'gw-fy-1',
|
|
snapshot,
|
|
totalLoadedCount: 10,
|
|
winnersCount: 2,
|
|
reserveWinnersCount: 1,
|
|
seed,
|
|
});
|
|
|
|
expect(current.winnerIds).toEqual(baseline.winnerIds);
|
|
expect(current.reserveWinnerIds).toEqual(baseline.reserveWinnerIds);
|
|
expect(current.deterministicProofHash).toBe(baseline.deterministicProofHash);
|
|
}
|
|
});
|
|
});
|