import { describe, it, expect } from 'vitest'; import { applyFilterRules } from '../src/core/filtering/filter-engine'; import { RawParticipant } from '../src/core/types/participant'; import { FilterRules } from '../src/core/types/giveaway'; describe('Filter Engine', () => { const sampleParticipants: RawParticipant[] = [ { platformUserId: '1', firstName: 'Иван', lastName: 'Иванов', source: 'LIKES', liked: true, commented: true, commentsCount: 1, reposted: true, subscribed: true, isAdmin: false, }, { platformUserId: '2', firstName: 'Петр', lastName: 'Петров', source: 'LIKES', liked: false, commented: true, commentsCount: 1, reposted: true, subscribed: true, isAdmin: false, }, { platformUserId: '3', firstName: 'Анна', lastName: 'Сидорова', source: 'COMMENTS', liked: true, commented: false, commentsCount: 0, reposted: true, subscribed: true, isAdmin: false, }, { platformUserId: '4', firstName: 'Админ', lastName: 'Группы', source: 'LIKES', liked: true, commented: true, commentsCount: 1, reposted: true, subscribed: true, isAdmin: true, }, { platformUserId: '5', firstName: 'Не', lastName: 'Подписан', source: 'LIKES', liked: true, commented: true, commentsCount: 1, reposted: true, subscribed: false, isAdmin: false, }, ]; it('should filter by like requirement correctly', () => { const rules: FilterRules = { requireLike: true, requireComment: false, requireRepost: false, requireSubscription: false, excludeAdmins: false, excludeBlacklistedIds: [], excludeDuplicateComments: true, }; const result = applyFilterRules(sampleParticipants, rules); expect(result.stats.eligibleCount).toBe(4); expect(result.stats.excludedCount).toBe(1); expect(result.excludedParticipants[0].platformUserId).toBe('2'); expect(result.excludedParticipants[0].exclusionReason).toBe('MISSING_LIKE'); }); it('should filter by comment and subscription requirements', () => { const rules: FilterRules = { requireLike: true, requireComment: true, requireRepost: false, requireSubscription: true, excludeAdmins: false, excludeBlacklistedIds: [], excludeDuplicateComments: true, }; const result = applyFilterRules(sampleParticipants, rules); // 1: passed // 2: missing like // 3: missing comment // 4: passed (admin allowed here) // 5: not subscribed expect(result.stats.eligibleCount).toBe(2); const eligibleIds = result.eligibleParticipants.map(p => p.platformUserId); expect(eligibleIds).toEqual(['1', '4']); }); it('should exclude administrators when excludeAdmins is true', () => { const rules: FilterRules = { requireLike: true, requireComment: false, requireRepost: false, requireSubscription: false, excludeAdmins: true, excludeBlacklistedIds: [], excludeDuplicateComments: true, }; const result = applyFilterRules(sampleParticipants, rules); const adminParticipant = result.allParticipants.find(p => p.platformUserId === '4'); expect(adminParticipant?.eligible).toBe(false); expect(adminParticipant?.exclusionReason).toContain('IS_ADMIN'); }); it('should exclude blacklisted user IDs and usernames', () => { const rules: FilterRules = { requireLike: false, requireComment: false, requireRepost: false, requireSubscription: false, excludeAdmins: false, excludeBlacklistedIds: ['1', '5'], excludeDuplicateComments: true, }; const result = applyFilterRules(sampleParticipants, rules); const blacklisted = result.excludedParticipants.map(p => p.platformUserId); expect(blacklisted).toContain('1'); expect(blacklisted).toContain('5'); expect(result.stats.reasonsBreakdown['BLACKLISTED']).toBe(2); }); it('should deduplicate multiple raw comments from the same user into single participant', () => { const multiComments: RawParticipant[] = [ { platformUserId: '100', firstName: 'Спамер', lastName: 'Обыкновенный', source: 'COMMENTS', liked: true, commented: true, commentsCount: 1, reposted: false, subscribed: true, }, { platformUserId: '100', firstName: 'Спамер', lastName: 'Обыкновенный', source: 'COMMENTS', liked: true, commented: true, commentsCount: 1, reposted: false, subscribed: true, }, { platformUserId: '100', firstName: 'Спамер', lastName: 'Обыкновенный', source: 'COMMENTS', liked: true, commented: true, commentsCount: 1, reposted: false, subscribed: true, }, ]; const rules: FilterRules = { requireLike: true, requireComment: true, requireRepost: false, requireSubscription: true, excludeAdmins: false, excludeBlacklistedIds: [], excludeDuplicateComments: true, }; const result = applyFilterRules(multiComments, rules); expect(result.stats.total).toBe(1); expect(result.stats.eligibleCount).toBe(1); expect(result.allParticipants[0].commentsCount).toBe(3); }); describe('rule combination matrix', () => { function makeParticipant( id: string, overrides: Partial = {} ): RawParticipant { return { platformUserId: id, firstName: 'User', lastName: id, source: 'LIKES', liked: false, commented: false, commentsCount: 0, reposted: false, subscribed: false, isAdmin: false, ...overrides, }; } it('should require like only', () => { const participants = [ makeParticipant('1', { liked: true }), makeParticipant('2', { liked: false }), ]; const rules: FilterRules = { requireLike: true, requireComment: false, requireRepost: false, requireSubscription: false, excludeAdmins: false, excludeBlacklistedIds: [], excludeDuplicateComments: true, }; const result = applyFilterRules(participants, rules); expect(result.eligibleParticipants.map(p => p.platformUserId)).toEqual(['1']); expect(result.excludedParticipants[0].exclusionReason).toBe('MISSING_LIKE'); }); it('should require comment only', () => { const participants = [ makeParticipant('1', { commented: true, commentsCount: 1 }), makeParticipant('2', { commented: false, commentsCount: 0 }), ]; const rules: FilterRules = { requireLike: false, requireComment: true, requireRepost: false, requireSubscription: false, excludeAdmins: false, excludeBlacklistedIds: [], excludeDuplicateComments: true, }; const result = applyFilterRules(participants, rules); expect(result.eligibleParticipants.map(p => p.platformUserId)).toEqual(['1']); expect(result.excludedParticipants[0].exclusionReason).toBe('MISSING_COMMENT'); }); it('should require subscription only', () => { const participants = [ makeParticipant('1', { subscribed: true }), makeParticipant('2', { subscribed: false }), ]; const rules: FilterRules = { requireLike: false, requireComment: false, requireRepost: false, requireSubscription: true, excludeAdmins: false, excludeBlacklistedIds: [], excludeDuplicateComments: true, }; const result = applyFilterRules(participants, rules); expect(result.eligibleParticipants.map(p => p.platformUserId)).toEqual(['1']); expect(result.excludedParticipants[0].exclusionReason).toBe('NOT_SUBSCRIBED'); }); it('should require like + comment together', () => { const participants = [ makeParticipant('1', { liked: true, commented: true, commentsCount: 1 }), makeParticipant('2', { liked: true, commented: false, commentsCount: 0 }), makeParticipant('3', { liked: false, commented: true, commentsCount: 1 }), ]; const rules: FilterRules = { requireLike: true, requireComment: true, requireRepost: false, requireSubscription: false, excludeAdmins: false, excludeBlacklistedIds: [], excludeDuplicateComments: true, }; const result = applyFilterRules(participants, rules); expect(result.eligibleParticipants.map(p => p.platformUserId)).toEqual(['1']); expect(result.excludedParticipants.map(p => p.exclusionReason)).toContain('MISSING_COMMENT'); expect(result.excludedParticipants.map(p => p.exclusionReason)).toContain('MISSING_LIKE'); }); it('should combine admin exclusion and subscription requirement', () => { const participants = [ makeParticipant('1', { liked: true, subscribed: true, isAdmin: false }), makeParticipant('2', { liked: true, subscribed: true, isAdmin: true }), makeParticipant('3', { liked: true, subscribed: false, isAdmin: false }), ]; const rules: FilterRules = { requireLike: true, requireComment: false, requireRepost: false, requireSubscription: true, excludeAdmins: true, excludeBlacklistedIds: [], excludeDuplicateComments: true, }; const result = applyFilterRules(participants, rules); expect(result.eligibleParticipants.map(p => p.platformUserId)).toEqual(['1']); expect(result.excludedParticipants.map(p => p.platformUserId).sort()).toEqual(['2', '3']); expect(result.excludedParticipants.find(p => p.platformUserId === '2')?.exclusionReason).toContain('IS_ADMIN'); expect(result.excludedParticipants.find(p => p.platformUserId === '3')?.exclusionReason).toContain('NOT_SUBSCRIBED'); }); it('should combine blacklist with action requirements', () => { const participants = [ makeParticipant('1', { liked: true }), makeParticipant('2', { liked: true }), makeParticipant('3', { liked: false }), ]; const rules: FilterRules = { requireLike: true, requireComment: false, requireRepost: false, requireSubscription: false, excludeAdmins: false, excludeBlacklistedIds: ['2'], excludeDuplicateComments: true, }; const result = applyFilterRules(participants, rules); expect(result.eligibleParticipants.map(p => p.platformUserId)).toEqual(['1']); const excluded2 = result.excludedParticipants.find(p => p.platformUserId === '2'); expect(excluded2?.exclusionReason).toContain('BLACKLISTED'); const excluded3 = result.excludedParticipants.find(p => p.platformUserId === '3'); expect(excluded3?.exclusionReason).toContain('MISSING_LIKE'); }); it('should aggregate duplicate users with both LIKE and COMMENT into a single eligible participant', () => { const participants: RawParticipant[] = [ { platformUserId: '10', firstName: 'Like', lastName: 'Only', source: 'LIKES', liked: true, commented: false, commentsCount: 0, reposted: false, subscribed: true, }, { platformUserId: '10', firstName: 'Comment', lastName: 'Only', source: 'COMMENTS', liked: false, commented: true, commentsCount: 1, reposted: false, subscribed: true, }, ]; const rules: FilterRules = { requireLike: true, requireComment: true, requireRepost: false, requireSubscription: true, excludeAdmins: false, excludeBlacklistedIds: [], excludeDuplicateComments: true, }; const result = applyFilterRules(participants, rules); expect(result.stats.total).toBe(1); expect(result.stats.eligibleCount).toBe(1); const merged = result.eligibleParticipants[0]; expect(merged.liked).toBe(true); expect(merged.commented).toBe(true); expect(merged.commentsCount).toBe(1); }); it('should exclude a user who satisfies only some of several mandatory conditions', () => { const participants = [ makeParticipant('all', { liked: true, commented: true, commentsCount: 1, reposted: true, subscribed: true }), makeParticipant('missing-repost', { liked: true, commented: true, commentsCount: 1, reposted: false, subscribed: true }), makeParticipant('missing-sub', { liked: true, commented: true, commentsCount: 1, reposted: true, subscribed: false }), ]; const rules: FilterRules = { requireLike: true, requireComment: true, requireRepost: true, requireSubscription: true, excludeAdmins: false, excludeBlacklistedIds: [], excludeDuplicateComments: true, }; const result = applyFilterRules(participants, rules); expect(result.eligibleParticipants.map(p => p.platformUserId)).toEqual(['all']); expect(result.excludedParticipants).toHaveLength(2); expect(result.excludedParticipants.map(p => p.exclusionReason)).toContain('MISSING_REPOST'); expect(result.excludedParticipants.map(p => p.exclusionReason)).toContain('NOT_SUBSCRIBED'); }); it('should report all reasons for exclusion when multiple conditions fail', () => { const participant = makeParticipant('bad', { liked: false, commented: false, subscribed: false }); const rules: FilterRules = { requireLike: true, requireComment: true, requireRepost: false, requireSubscription: true, excludeAdmins: false, excludeBlacklistedIds: [], excludeDuplicateComments: true, }; const result = applyFilterRules([participant], rules); expect(result.excludedParticipants[0].exclusionReason?.split(', ').sort()).toEqual([ 'MISSING_COMMENT', 'MISSING_LIKE', 'NOT_SUBSCRIBED', ]); }); it('should handle username-based blacklist matching', () => { const participants = [ makeParticipant('1', { username: 'spammer' }), makeParticipant('2', { username: 'gooduser' }), ]; const rules: FilterRules = { requireLike: false, requireComment: false, requireRepost: false, requireSubscription: false, excludeAdmins: false, excludeBlacklistedIds: ['@Spammer'], excludeDuplicateComments: true, }; const result = applyFilterRules(participants, rules); expect(result.eligibleParticipants.map(p => p.platformUserId)).toEqual(['2']); expect(result.excludedParticipants[0].platformUserId).toBe('1'); }); }); });