407 lines
13 KiB
TypeScript
407 lines
13 KiB
TypeScript
import { AdminTrustRatingCalculator, VoteOutcomeInput, SystemSignalInput } from './AdminTrustRatingCalculator';
|
|
import { RatingEvent } from '../entities/RatingEvent';
|
|
import { RatingEventId } from '../value-objects/RatingEventId';
|
|
import { RatingDimensionKey } from '../value-objects/RatingDimensionKey';
|
|
import { RatingDelta } from '../value-objects/RatingDelta';
|
|
import { AdminVoteOutcome } from '../entities/AdminVoteSession';
|
|
|
|
describe('AdminTrustRatingCalculator', () => {
|
|
describe('calculate', () => {
|
|
it('should sum all event deltas', () => {
|
|
const events: RatingEvent[] = [
|
|
RatingEvent.create({
|
|
id: RatingEventId.generate(),
|
|
userId: 'user-123',
|
|
dimension: RatingDimensionKey.create('adminTrust'),
|
|
delta: RatingDelta.create(5),
|
|
occurredAt: new Date(),
|
|
createdAt: new Date(),
|
|
source: { type: 'vote', id: 'vote-123' },
|
|
reason: {
|
|
code: 'ADMIN_VOTE_OUTCOME_POSITIVE',
|
|
summary: 'Test',
|
|
details: {},
|
|
},
|
|
visibility: { public: true, redactedFields: [] },
|
|
version: 1,
|
|
}),
|
|
RatingEvent.create({
|
|
id: RatingEventId.generate(),
|
|
userId: 'user-123',
|
|
dimension: RatingDimensionKey.create('adminTrust'),
|
|
delta: RatingDelta.create(-2),
|
|
occurredAt: new Date(),
|
|
createdAt: new Date(),
|
|
source: { type: 'adminAction', id: 'action-456' },
|
|
reason: {
|
|
code: 'ADMIN_ACTION_REVERSAL_PENALTY',
|
|
summary: 'Test',
|
|
details: {},
|
|
},
|
|
visibility: { public: true, redactedFields: [] },
|
|
version: 1,
|
|
}),
|
|
];
|
|
|
|
const result = AdminTrustRatingCalculator.calculate(events);
|
|
expect(result).toBe(3); // 5 + (-2)
|
|
});
|
|
|
|
it('should handle empty events array', () => {
|
|
const result = AdminTrustRatingCalculator.calculate([]);
|
|
expect(result).toBe(0);
|
|
});
|
|
|
|
it('should apply weight to deltas', () => {
|
|
const events: RatingEvent[] = [
|
|
RatingEvent.create({
|
|
id: RatingEventId.generate(),
|
|
userId: 'user-123',
|
|
dimension: RatingDimensionKey.create('adminTrust'),
|
|
delta: RatingDelta.create(10),
|
|
weight: 2,
|
|
occurredAt: new Date(),
|
|
createdAt: new Date(),
|
|
source: { type: 'vote', id: 'vote-123' },
|
|
reason: {
|
|
code: 'ADMIN_VOTE_OUTCOME_POSITIVE',
|
|
summary: 'Test',
|
|
details: {},
|
|
},
|
|
visibility: { public: true, redactedFields: [] },
|
|
version: 1,
|
|
}),
|
|
];
|
|
|
|
const result = AdminTrustRatingCalculator.calculate(events);
|
|
expect(result).toBe(20); // 10 * 2
|
|
});
|
|
|
|
it('should handle mixed weighted and unweighted events', () => {
|
|
const events: RatingEvent[] = [
|
|
RatingEvent.create({
|
|
id: RatingEventId.generate(),
|
|
userId: 'user-123',
|
|
dimension: RatingDimensionKey.create('adminTrust'),
|
|
delta: RatingDelta.create(5),
|
|
weight: 1,
|
|
occurredAt: new Date(),
|
|
createdAt: new Date(),
|
|
source: { type: 'vote', id: 'vote-123' },
|
|
reason: {
|
|
code: 'ADMIN_VOTE_OUTCOME_POSITIVE',
|
|
summary: 'Test',
|
|
details: {},
|
|
},
|
|
visibility: { public: true, redactedFields: [] },
|
|
version: 1,
|
|
}),
|
|
RatingEvent.create({
|
|
id: RatingEventId.generate(),
|
|
userId: 'user-123',
|
|
dimension: RatingDimensionKey.create('adminTrust'),
|
|
delta: RatingDelta.create(3),
|
|
occurredAt: new Date(),
|
|
createdAt: new Date(),
|
|
source: { type: 'adminAction', id: 'action-456' },
|
|
reason: {
|
|
code: 'ADMIN_ACTION_SLA_BONUS',
|
|
summary: 'Test',
|
|
details: {},
|
|
},
|
|
visibility: { public: true, redactedFields: [] },
|
|
version: 1,
|
|
}),
|
|
];
|
|
|
|
const result = AdminTrustRatingCalculator.calculate(events);
|
|
expect(result).toBe(8); // (5 * 1) + 3
|
|
});
|
|
});
|
|
|
|
describe('calculateFromVote', () => {
|
|
it('should calculate positive outcome with full participation', () => {
|
|
const input: VoteOutcomeInput = {
|
|
outcome: {
|
|
percentPositive: 100,
|
|
count: { positive: 10, negative: 0, total: 10 },
|
|
eligibleVoterCount: 10,
|
|
participationRate: 100,
|
|
outcome: 'positive',
|
|
},
|
|
eligibleVoterCount: 10,
|
|
voteCount: 10,
|
|
percentPositive: 100,
|
|
};
|
|
|
|
const delta = AdminTrustRatingCalculator.calculateFromVote(input);
|
|
expect(delta.value).toBe(20); // Full positive, full participation
|
|
});
|
|
|
|
it('should calculate negative outcome with full participation', () => {
|
|
const input: VoteOutcomeInput = {
|
|
outcome: {
|
|
percentPositive: 0,
|
|
count: { positive: 0, negative: 10, total: 10 },
|
|
eligibleVoterCount: 10,
|
|
participationRate: 100,
|
|
outcome: 'negative',
|
|
},
|
|
eligibleVoterCount: 10,
|
|
voteCount: 10,
|
|
percentPositive: 0,
|
|
};
|
|
|
|
const delta = AdminTrustRatingCalculator.calculateFromVote(input);
|
|
expect(delta.value).toBe(-20); // Full negative, full participation
|
|
});
|
|
|
|
it('should calculate partial positive outcome', () => {
|
|
const input: VoteOutcomeInput = {
|
|
outcome: {
|
|
percentPositive: 75,
|
|
count: { positive: 3, negative: 1, total: 4 },
|
|
eligibleVoterCount: 4,
|
|
participationRate: 100,
|
|
outcome: 'positive',
|
|
},
|
|
eligibleVoterCount: 4,
|
|
voteCount: 4,
|
|
percentPositive: 75,
|
|
};
|
|
|
|
const delta = AdminTrustRatingCalculator.calculateFromVote(input);
|
|
expect(delta.value).toBe(15); // 75% of 20 = 15
|
|
});
|
|
|
|
it('should reduce delta for low participation', () => {
|
|
const input: VoteOutcomeInput = {
|
|
outcome: {
|
|
percentPositive: 100,
|
|
count: { positive: 2, negative: 0, total: 2 },
|
|
eligibleVoterCount: 10,
|
|
participationRate: 20,
|
|
outcome: 'positive',
|
|
},
|
|
eligibleVoterCount: 10,
|
|
voteCount: 2,
|
|
percentPositive: 100,
|
|
};
|
|
|
|
const delta = AdminTrustRatingCalculator.calculateFromVote(input);
|
|
// 20 * 0.5 (minimum participation multiplier) = 10
|
|
expect(delta.value).toBe(10);
|
|
});
|
|
|
|
it('should handle tie outcome', () => {
|
|
const input: VoteOutcomeInput = {
|
|
outcome: {
|
|
percentPositive: 50,
|
|
count: { positive: 5, negative: 5, total: 10 },
|
|
eligibleVoterCount: 10,
|
|
participationRate: 100,
|
|
outcome: 'tie',
|
|
},
|
|
eligibleVoterCount: 10,
|
|
voteCount: 10,
|
|
percentPositive: 50,
|
|
};
|
|
|
|
const delta = AdminTrustRatingCalculator.calculateFromVote(input);
|
|
expect(delta.value).toBe(0);
|
|
});
|
|
|
|
it('should return zero for no votes', () => {
|
|
const input: VoteOutcomeInput = {
|
|
outcome: {
|
|
percentPositive: 0,
|
|
count: { positive: 0, negative: 0, total: 0 },
|
|
eligibleVoterCount: 10,
|
|
participationRate: 0,
|
|
outcome: 'tie',
|
|
},
|
|
eligibleVoterCount: 10,
|
|
voteCount: 0,
|
|
percentPositive: 0,
|
|
};
|
|
|
|
const delta = AdminTrustRatingCalculator.calculateFromVote(input);
|
|
expect(delta.value).toBe(0);
|
|
});
|
|
|
|
it('should round to 2 decimal places', () => {
|
|
const input: VoteOutcomeInput = {
|
|
outcome: {
|
|
percentPositive: 66.67,
|
|
count: { positive: 2, negative: 1, total: 3 },
|
|
eligibleVoterCount: 4,
|
|
participationRate: 75,
|
|
outcome: 'positive',
|
|
},
|
|
eligibleVoterCount: 4,
|
|
voteCount: 3,
|
|
percentPositive: 66.67,
|
|
};
|
|
|
|
const delta = AdminTrustRatingCalculator.calculateFromVote(input);
|
|
// 66.67% of 20 = 13.334, * 0.75 (participation) = 10.0005, rounded = 10.00
|
|
expect(delta.value).toBe(10.00);
|
|
});
|
|
});
|
|
|
|
describe('calculateFromSystemSignal', () => {
|
|
it('should calculate SLA response bonus', () => {
|
|
const input: SystemSignalInput = {
|
|
actionType: 'sla_response',
|
|
details: {},
|
|
};
|
|
|
|
const delta = AdminTrustRatingCalculator.calculateFromSystemSignal(input);
|
|
expect(delta.value).toBe(5);
|
|
});
|
|
|
|
it('should calculate minor reversal penalty', () => {
|
|
const input: SystemSignalInput = {
|
|
actionType: 'reversal',
|
|
details: {},
|
|
severity: 'minor',
|
|
};
|
|
|
|
const delta = AdminTrustRatingCalculator.calculateFromSystemSignal(input);
|
|
expect(delta.value).toBe(-10);
|
|
});
|
|
|
|
it('should calculate major reversal penalty', () => {
|
|
const input: SystemSignalInput = {
|
|
actionType: 'reversal',
|
|
details: {},
|
|
severity: 'major',
|
|
};
|
|
|
|
const delta = AdminTrustRatingCalculator.calculateFromSystemSignal(input);
|
|
expect(delta.value).toBe(-20);
|
|
});
|
|
|
|
it('should calculate rule clarity bonus', () => {
|
|
const input: SystemSignalInput = {
|
|
actionType: 'rule_clarity',
|
|
details: {},
|
|
};
|
|
|
|
const delta = AdminTrustRatingCalculator.calculateFromSystemSignal(input);
|
|
expect(delta.value).toBe(3);
|
|
});
|
|
|
|
it('should calculate minor abuse report penalty', () => {
|
|
const input: SystemSignalInput = {
|
|
actionType: 'abuse_report',
|
|
details: {},
|
|
severity: 'minor',
|
|
};
|
|
|
|
const delta = AdminTrustRatingCalculator.calculateFromSystemSignal(input);
|
|
expect(delta.value).toBe(-15);
|
|
});
|
|
|
|
it('should calculate major abuse report penalty', () => {
|
|
const input: SystemSignalInput = {
|
|
actionType: 'abuse_report',
|
|
details: {},
|
|
severity: 'major',
|
|
};
|
|
|
|
const delta = AdminTrustRatingCalculator.calculateFromSystemSignal(input);
|
|
expect(delta.value).toBe(-30);
|
|
});
|
|
|
|
it('should default to zero for unknown action type', () => {
|
|
const input: SystemSignalInput = {
|
|
actionType: 'sla_response' as any,
|
|
details: {},
|
|
};
|
|
|
|
// Override for test
|
|
const delta = AdminTrustRatingCalculator.calculateFromSystemSignal(input);
|
|
expect(delta.value).toBe(5); // Known type
|
|
});
|
|
});
|
|
|
|
describe('calculateFromMultipleVotes', () => {
|
|
it('should sum multiple vote outcomes', () => {
|
|
const inputs: VoteOutcomeInput[] = [
|
|
{
|
|
outcome: {
|
|
percentPositive: 100,
|
|
count: { positive: 5, negative: 0, total: 5 },
|
|
eligibleVoterCount: 5,
|
|
participationRate: 100,
|
|
outcome: 'positive',
|
|
},
|
|
eligibleVoterCount: 5,
|
|
voteCount: 5,
|
|
percentPositive: 100,
|
|
},
|
|
{
|
|
outcome: {
|
|
percentPositive: 0,
|
|
count: { positive: 0, negative: 3, total: 3 },
|
|
eligibleVoterCount: 3,
|
|
participationRate: 100,
|
|
outcome: 'negative',
|
|
},
|
|
eligibleVoterCount: 3,
|
|
voteCount: 3,
|
|
percentPositive: 0,
|
|
},
|
|
];
|
|
|
|
const delta = AdminTrustRatingCalculator.calculateFromMultipleVotes(inputs);
|
|
expect(delta.value).toBe(0); // +20 + (-20) = 0
|
|
});
|
|
});
|
|
|
|
describe('calculateFromMultipleSystemSignals', () => {
|
|
it('should sum multiple system signals', () => {
|
|
const inputs: SystemSignalInput[] = [
|
|
{ actionType: 'sla_response', details: {} },
|
|
{ actionType: 'reversal', details: {}, severity: 'minor' },
|
|
{ actionType: 'rule_clarity', details: {} },
|
|
];
|
|
|
|
const delta = AdminTrustRatingCalculator.calculateFromMultipleSystemSignals(inputs);
|
|
expect(delta.value).toBe(-2); // 5 + (-10) + 3 = -2
|
|
});
|
|
});
|
|
|
|
describe('calculateTotalDelta', () => {
|
|
it('should combine votes and system signals', () => {
|
|
const voteInputs: VoteOutcomeInput[] = [
|
|
{
|
|
outcome: {
|
|
percentPositive: 75,
|
|
count: { positive: 3, negative: 1, total: 4 },
|
|
eligibleVoterCount: 4,
|
|
participationRate: 100,
|
|
outcome: 'positive',
|
|
},
|
|
eligibleVoterCount: 4,
|
|
voteCount: 4,
|
|
percentPositive: 75,
|
|
},
|
|
];
|
|
|
|
const systemInputs: SystemSignalInput[] = [
|
|
{ actionType: 'sla_response', details: {} },
|
|
{ actionType: 'reversal', details: {}, severity: 'minor' },
|
|
];
|
|
|
|
const delta = AdminTrustRatingCalculator.calculateTotalDelta(voteInputs, systemInputs);
|
|
expect(delta.value).toBe(10); // 15 (vote) + 5 (SLA) + (-10) (reversal) = 10
|
|
});
|
|
|
|
it('should handle empty inputs', () => {
|
|
const delta = AdminTrustRatingCalculator.calculateTotalDelta([], []);
|
|
expect(delta.value).toBe(0);
|
|
});
|
|
});
|
|
}); |