116 lines
3.6 KiB
TypeScript
116 lines
3.6 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import {
|
|
computeConditionsHash,
|
|
computeParticipantsSnapshotHash,
|
|
canonicalStringify
|
|
} from '../src/core/randomizer/canonical';
|
|
import { FilterRules } from '../src/core/types/giveaway';
|
|
import { FilteredParticipant } from '../src/core/types/participant';
|
|
|
|
describe('Snapshot Immutability & Canonical Hashing', () => {
|
|
const baseRules: FilterRules = {
|
|
requireLike: true,
|
|
requireComment: false,
|
|
requireRepost: false,
|
|
requireSubscription: true,
|
|
excludeAdmins: true,
|
|
excludeBlacklistedIds: ['100', '200'],
|
|
excludeDuplicateComments: true,
|
|
minEligibleParticipants: 1,
|
|
};
|
|
|
|
const sampleParticipants: FilteredParticipant[] = [
|
|
{
|
|
platformUserId: '101',
|
|
firstName: 'Иван',
|
|
lastName: 'Иванов',
|
|
username: 'ivanov',
|
|
source: 'LIKES',
|
|
liked: true,
|
|
commented: true,
|
|
commentsCount: 1,
|
|
reposted: false,
|
|
subscribed: true,
|
|
eligible: true,
|
|
exclusionReason: null,
|
|
},
|
|
{
|
|
platformUserId: '102',
|
|
firstName: 'Анна',
|
|
lastName: 'Смирнова',
|
|
username: 'anna_s',
|
|
source: 'LIKES',
|
|
liked: true,
|
|
commented: false,
|
|
commentsCount: 0,
|
|
reposted: false,
|
|
subscribed: true,
|
|
eligible: true,
|
|
exclusionReason: null,
|
|
},
|
|
];
|
|
|
|
it('should generate identical conditionsHash regardless of key order in rules object', () => {
|
|
const rulesA = { ...baseRules };
|
|
const rulesB: FilterRules = {
|
|
minEligibleParticipants: 1,
|
|
excludeDuplicateComments: true,
|
|
excludeBlacklistedIds: ['200', '100'], // reversed array
|
|
excludeAdmins: true,
|
|
requireSubscription: true,
|
|
requireRepost: false,
|
|
requireComment: false,
|
|
requireLike: true,
|
|
};
|
|
|
|
const hashA = computeConditionsHash(rulesA);
|
|
const hashB = computeConditionsHash(rulesB);
|
|
|
|
expect(hashA).toBe(hashB);
|
|
});
|
|
|
|
it('should change conditionsHash when any rule changes', () => {
|
|
const originalHash = computeConditionsHash(baseRules);
|
|
|
|
const changedLike = computeConditionsHash({ ...baseRules, requireLike: false });
|
|
const changedComment = computeConditionsHash({ ...baseRules, requireComment: true });
|
|
const changedBlacklist = computeConditionsHash({ ...baseRules, excludeBlacklistedIds: ['300'] });
|
|
|
|
expect(changedLike).not.toBe(originalHash);
|
|
expect(changedComment).not.toBe(originalHash);
|
|
expect(changedBlacklist).not.toBe(originalHash);
|
|
});
|
|
|
|
it('should generate invariant snapshot hash regardless of participant list ordering', () => {
|
|
const p1 = [...sampleParticipants];
|
|
const p2 = [...sampleParticipants].reverse();
|
|
|
|
const hash1 = computeParticipantsSnapshotHash(p1);
|
|
const hash2 = computeParticipantsSnapshotHash(p2);
|
|
|
|
expect(hash1).toBe(hash2);
|
|
});
|
|
|
|
it('should change snapshot hash when participant attributes or actions change', () => {
|
|
const originalHash = computeParticipantsSnapshotHash(sampleParticipants);
|
|
|
|
const modifiedParticipants: FilteredParticipant[] = [
|
|
{
|
|
...sampleParticipants[0],
|
|
firstName: 'Иван Измененный',
|
|
},
|
|
sampleParticipants[1],
|
|
];
|
|
|
|
const newHash = computeParticipantsSnapshotHash(modifiedParticipants);
|
|
expect(newHash).not.toBe(originalHash);
|
|
});
|
|
|
|
it('should produce valid canonical string for deep objects', () => {
|
|
const obj1 = { z: 1, a: { y: 2, b: 3 } };
|
|
const obj2 = { a: { b: 3, y: 2 }, z: 1 };
|
|
|
|
expect(canonicalStringify(obj1)).toBe(canonicalStringify(obj2));
|
|
expect(canonicalStringify(obj1)).toBe('{"a":{"b":3,"y":2},"z":1}');
|
|
});
|
|
});
|