200 lines
7.3 KiB
TypeScript
200 lines
7.3 KiB
TypeScript
import { describe, it, expect, beforeEach } from 'vitest';
|
|
import { MemoryGiveawayRepository } from '../src/lib/repository/memory-repository';
|
|
import { DEFAULT_FILTER_RULES } from '../src/core/types/giveaway';
|
|
import { FilteredParticipant } from '../src/core/types/participant';
|
|
import { NextRequest } from 'next/server';
|
|
import { GET as giveawaysGet } from '../src/app/api/giveaways/route';
|
|
import { defaultSessionStore, SESSION_COOKIE_NAME } from '../src/lib/auth/session';
|
|
import { GiveawayStore } from '../src/lib/giveaway-store';
|
|
|
|
describe('Task 09: Summary Count Parity (listGiveawaysSummary)', () => {
|
|
let memoryRepo: MemoryGiveawayRepository;
|
|
const organizerUser = { id: 'usr_summary_parity_org', vkUserId: '777888' };
|
|
let sessionCookie: string;
|
|
|
|
beforeEach(async () => {
|
|
memoryRepo = new MemoryGiveawayRepository();
|
|
GiveawayStore.setRepository(memoryRepo);
|
|
defaultSessionStore.clear();
|
|
|
|
const sessionId = await defaultSessionStore.createSession(organizerUser);
|
|
sessionCookie = `${SESSION_COOKIE_NAME}=${sessionId}`;
|
|
});
|
|
|
|
const mixedParticipants: FilteredParticipant[] = Array.from({ length: 12 }, (_, i) => ({
|
|
platformUserId: `${2000 + i}`,
|
|
firstName: `User${i}`,
|
|
lastName: `Test${i}`,
|
|
source: 'LIKES' as const,
|
|
liked: true,
|
|
commented: false,
|
|
commentsCount: 0,
|
|
reposted: false,
|
|
subscribed: true,
|
|
eligible: i < 8, // 8 eligible, 4 excluded
|
|
exclusionReason: i >= 8 ? 'BLACKLISTED' : null,
|
|
}));
|
|
|
|
const zeroEligibleParticipants: FilteredParticipant[] = Array.from({ length: 5 }, (_, i) => ({
|
|
platformUserId: `${3000 + i}`,
|
|
firstName: `User${i}`,
|
|
lastName: `Excluded${i}`,
|
|
source: 'LIKES' as const,
|
|
liked: false,
|
|
commented: false,
|
|
commentsCount: 0,
|
|
reposted: false,
|
|
subscribed: false,
|
|
eligible: false,
|
|
exclusionReason: 'MISSING_LIKE',
|
|
}));
|
|
|
|
// ─── 1. Memory repository: Undrawn giveaway with eligible participants ────────
|
|
it('memory driver returns correct eligibleCount for undrawn giveaway', async () => {
|
|
const gw = await memoryRepo.createGiveaway({
|
|
sourceUrl: 'https://vk.com/wall-10_1',
|
|
post: {
|
|
platform: 'VK',
|
|
ownerId: '-10',
|
|
postId: '1',
|
|
sourceUrl: 'https://vk.com/wall-10_1',
|
|
title: 'Undrawn Test',
|
|
text: 'Undrawn giveaway',
|
|
likesCount: 12,
|
|
commentsCount: 0,
|
|
repostsCount: 0,
|
|
},
|
|
filterRules: DEFAULT_FILTER_RULES,
|
|
organizerId: organizerUser.id,
|
|
});
|
|
|
|
await memoryRepo.saveParticipants(gw.id, mixedParticipants);
|
|
|
|
const summaries = await memoryRepo.listGiveawaysSummary(organizerUser.id);
|
|
expect(summaries).toHaveLength(1);
|
|
expect(summaries[0].totalParticipantsCount).toBe(12);
|
|
expect(summaries[0].eligibleParticipantsCount).toBe(8);
|
|
expect(summaries[0].hasDrawResult).toBe(false);
|
|
expect(summaries[0].algorithmVersion).toBeNull();
|
|
});
|
|
|
|
// ─── 2. Memory repository: Undrawn giveaway with 0 eligible participants ──────
|
|
it('memory driver returns 0 eligibleCount when all participants are excluded', async () => {
|
|
const gw = await memoryRepo.createGiveaway({
|
|
sourceUrl: 'https://vk.com/wall-10_2',
|
|
post: {
|
|
platform: 'VK',
|
|
ownerId: '-10',
|
|
postId: '2',
|
|
sourceUrl: 'https://vk.com/wall-10_2',
|
|
title: 'Zero Eligible Test',
|
|
text: 'Zero eligible giveaway',
|
|
likesCount: 5,
|
|
commentsCount: 0,
|
|
repostsCount: 0,
|
|
},
|
|
filterRules: DEFAULT_FILTER_RULES,
|
|
organizerId: organizerUser.id,
|
|
});
|
|
|
|
await memoryRepo.saveParticipants(gw.id, zeroEligibleParticipants);
|
|
|
|
const summaries = await memoryRepo.listGiveawaysSummary(organizerUser.id);
|
|
expect(summaries).toHaveLength(1);
|
|
expect(summaries[0].totalParticipantsCount).toBe(5);
|
|
expect(summaries[0].eligibleParticipantsCount).toBe(0);
|
|
expect(summaries[0].hasDrawResult).toBe(false);
|
|
});
|
|
|
|
// ─── 3. Memory repository: Drawn giveaway uses totalEligibleCount from drawResult
|
|
it('memory driver uses totalEligibleCount from drawResult after draw', async () => {
|
|
const gw = await memoryRepo.createGiveaway({
|
|
sourceUrl: 'https://vk.com/wall-10_3',
|
|
post: {
|
|
platform: 'VK',
|
|
ownerId: '-10',
|
|
postId: '3',
|
|
sourceUrl: 'https://vk.com/wall-10_3',
|
|
title: 'Drawn Test',
|
|
text: 'Drawn giveaway',
|
|
likesCount: 12,
|
|
commentsCount: 0,
|
|
repostsCount: 0,
|
|
},
|
|
filterRules: DEFAULT_FILTER_RULES,
|
|
organizerId: organizerUser.id,
|
|
});
|
|
|
|
await memoryRepo.saveParticipants(gw.id, mixedParticipants);
|
|
const locked = await memoryRepo.createAndLockSnapshot(gw.id, mixedParticipants.filter(p => p.eligible), DEFAULT_FILTER_RULES);
|
|
|
|
await memoryRepo.saveDrawResultAndAudit(gw.id, locked.snapshot.id, {
|
|
drawId: 'draw_summary_test_1',
|
|
giveawayId: gw.id,
|
|
snapshotId: locked.snapshot.id,
|
|
winners: [{ position: 1, participant: locked.snapshot.eligibleParticipants[0], isReserve: false, selectionIndex: 0, proofHash: 'h' }],
|
|
reserveWinners: [],
|
|
winnerIds: [locked.snapshot.eligibleParticipants[0].platformUserId],
|
|
reserveWinnerIds: [],
|
|
totalEligibleCount: 8,
|
|
totalLoadedCount: 12,
|
|
seedUsed: 'seed123',
|
|
algorithmVersion: 'HMAC_SHA256_FY_V1',
|
|
deterministicProofHash: 'a'.repeat(64),
|
|
auditEventHash: 'b'.repeat(64),
|
|
drawnAt: new Date().toISOString(),
|
|
participantsSnapshotHash: locked.snapshot.participantsSnapshotHash,
|
|
conditionsHash: locked.snapshot.conditionsHash,
|
|
});
|
|
|
|
const summaries = await memoryRepo.listGiveawaysSummary(organizerUser.id);
|
|
expect(summaries).toHaveLength(1);
|
|
expect(summaries[0].totalParticipantsCount).toBe(12);
|
|
expect(summaries[0].eligibleParticipantsCount).toBe(8);
|
|
expect(summaries[0].hasDrawResult).toBe(true);
|
|
expect(summaries[0].algorithmVersion).toBe('HMAC_SHA256_FY_V1');
|
|
});
|
|
|
|
// ─── 4. API Route: GET /api/giveaways returns lightweight summaries without arrays
|
|
it('GET /api/giveaways returns summaries with exact counts and no participant arrays or seeds', async () => {
|
|
const gw = await memoryRepo.createGiveaway({
|
|
sourceUrl: 'https://vk.com/wall-10_4',
|
|
post: {
|
|
platform: 'VK',
|
|
ownerId: '-10',
|
|
postId: '4',
|
|
sourceUrl: 'https://vk.com/wall-10_4',
|
|
title: 'API Summary Test',
|
|
text: 'API test',
|
|
likesCount: 12,
|
|
commentsCount: 0,
|
|
repostsCount: 0,
|
|
},
|
|
filterRules: DEFAULT_FILTER_RULES,
|
|
organizerId: organizerUser.id,
|
|
});
|
|
|
|
await memoryRepo.saveParticipants(gw.id, mixedParticipants);
|
|
|
|
const req = new NextRequest('http://localhost:3000/api/giveaways', {
|
|
headers: { Cookie: sessionCookie },
|
|
});
|
|
|
|
const res = await giveawaysGet(req);
|
|
expect(res.status).toBe(200);
|
|
const data = await res.json();
|
|
|
|
expect(data.success).toBe(true);
|
|
expect(data.giveaways).toHaveLength(1);
|
|
|
|
const summary = data.giveaways[0];
|
|
expect(summary.id).toBe(gw.id);
|
|
expect(summary.totalParticipantsCount).toBe(12);
|
|
expect(summary.eligibleParticipantsCount).toBe(8);
|
|
|
|
// Verify lightweight payload: no participant arrays or seeds
|
|
expect(summary.participants).toBeUndefined();
|
|
expect(summary.eligibleParticipants).toBeUndefined();
|
|
expect(summary.seed).toBeUndefined();
|
|
});
|
|
});
|