Files
gridpilot.gg/core/identity/domain/services/AdminTrustRatingCalculator.test.ts
2026-01-16 19:46:49 +01:00

405 lines
12 KiB
TypeScript

import { RatingEvent } from '../entities/RatingEvent';
import { RatingDelta } from '../value-objects/RatingDelta';
import { RatingDimensionKey } from '../value-objects/RatingDimensionKey';
import { RatingEventId } from '../value-objects/RatingEventId';
import { AdminTrustRatingCalculator, SystemSignalInput, VoteOutcomeInput } from './AdminTrustRatingCalculator';
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: 'unknown_type' as unknown as SystemSignalInput['actionType'],
details: {},
};
const delta = AdminTrustRatingCalculator.calculateFromSystemSignal(input);
expect(delta.value).toBe(0);
});
});
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);
});
});
});