6de2455917
- Added GLOBAL_MARKETS_TITLE to all translation files - Updated footer with 12 markets (4 active + 8 upcoming) - Translated market section to: zh-cn, zh-tw, ja, ko, th, vi, id, ms, hi - Built and deployed to production - CloudFront invalidation: I3RTMXVFDJXWLG3SYX208OP1CC
228 lines
5.8 KiB
JavaScript
228 lines
5.8 KiB
JavaScript
/**
|
|
* Risk Classifier Service Tests
|
|
*/
|
|
|
|
const { RiskClassifierService } = require('../src/services/riskClassifier');
|
|
|
|
describe('RiskClassifierService', () => {
|
|
let service;
|
|
|
|
beforeEach(() => {
|
|
service = new RiskClassifierService();
|
|
});
|
|
|
|
describe('classify', () => {
|
|
it('should classify skimmer as red', () => {
|
|
const detection = {
|
|
anomalyType: 'skimmer',
|
|
confidence: 0.95
|
|
};
|
|
|
|
const result = service.classify(detection, 'atm');
|
|
|
|
expect(result.level).toBe('red');
|
|
expect(result.action).toBe('immediate_alert');
|
|
});
|
|
|
|
it('should classify camera blocked as orange', () => {
|
|
const detection = {
|
|
anomalyType: 'camera_blocked',
|
|
confidence: 0.8
|
|
};
|
|
|
|
const result = service.classify(detection, 'atm');
|
|
|
|
expect(result.level).toBe('orange');
|
|
});
|
|
|
|
it('should escalate high confidence anomalies', () => {
|
|
const detection = {
|
|
anomalyType: 'display_changed',
|
|
confidence: 0.95
|
|
};
|
|
|
|
const result = service.classify(detection, 'atm');
|
|
|
|
// display_changed is yellow but high confidence should escalate
|
|
expect(result.level).toBe('orange');
|
|
});
|
|
|
|
it('should de-escalate low confidence anomalies', () => {
|
|
const detection = {
|
|
anomalyType: 'pin_pad_modified',
|
|
confidence: 0.3
|
|
};
|
|
|
|
const result = service.classify(detection, 'atm');
|
|
|
|
// pin_pad_modified is red but low confidence should de-escalate
|
|
expect(result.level).toBe('orange');
|
|
});
|
|
});
|
|
|
|
describe('aggregate', () => {
|
|
it('should return green for no classifications', () => {
|
|
const result = service.aggregate([]);
|
|
|
|
expect(result.level).toBe('green');
|
|
});
|
|
|
|
it('should return highest risk level', () => {
|
|
const classifications = [
|
|
{ level: 'green', reason: 'Normal' },
|
|
{ level: 'yellow', reason: 'Minor' },
|
|
{ level: 'red', reason: 'Critical' }
|
|
];
|
|
|
|
const result = service.aggregate(classifications);
|
|
|
|
expect(result.level).toBe('red');
|
|
});
|
|
|
|
it('should count by level', () => {
|
|
const classifications = [
|
|
{ level: 'yellow', reason: 'Minor 1' },
|
|
{ level: 'yellow', reason: 'Minor 2' },
|
|
{ level: 'orange', reason: 'Major' }
|
|
];
|
|
|
|
const result = service.aggregate(classifications);
|
|
|
|
expect(result.counts.yellow).toBe(2);
|
|
expect(result.counts.orange).toBe(1);
|
|
});
|
|
|
|
it('should determine action based on highest risk', () => {
|
|
const classifications = [
|
|
{ level: 'yellow', reason: 'Minor' }
|
|
];
|
|
|
|
const result = service.aggregate(classifications);
|
|
|
|
expect(result.action).toBe('verify_maintenance');
|
|
});
|
|
|
|
it('should escalate action for multiple yellow', () => {
|
|
const classifications = [
|
|
{ level: 'yellow', reason: 'Minor 1' },
|
|
{ level: 'yellow', reason: 'Minor 2' },
|
|
{ level: 'yellow', reason: 'Minor 3' }
|
|
];
|
|
|
|
const result = service.aggregate(classifications);
|
|
|
|
expect(result.action).toBe('scheduled_inspection');
|
|
});
|
|
});
|
|
|
|
describe('processObservation', () => {
|
|
it('should process observation with no anomalies', () => {
|
|
const observation = { id: 'obs-1' };
|
|
const detections = [
|
|
{ componentType: 'card_reader', confidence: 0.9, isAnomaly: false }
|
|
];
|
|
|
|
const result = service.processObservation(observation, detections, 'atm');
|
|
|
|
expect(result.level).toBe('green');
|
|
});
|
|
|
|
it('should process observation with anomalies', () => {
|
|
const observation = { id: 'obs-1' };
|
|
const detections = [
|
|
{ componentType: 'skimmer', confidence: 0.9, isAnomaly: true }
|
|
];
|
|
|
|
const result = service.processObservation(observation, detections, 'atm');
|
|
|
|
expect(result.level).toBe('red');
|
|
expect(result.observationId).toBe('obs-1');
|
|
});
|
|
});
|
|
|
|
describe('calculateTrend', () => {
|
|
it('should detect increasing trend', () => {
|
|
const history = [
|
|
{ level: 'green' },
|
|
{ level: 'yellow' },
|
|
{ level: 'orange' }
|
|
];
|
|
|
|
const result = service.calculateTrend(history);
|
|
|
|
expect(result.trend).toBe('increasing');
|
|
});
|
|
|
|
it('should detect decreasing trend', () => {
|
|
const history = [
|
|
{ level: 'red' },
|
|
{ level: 'orange' },
|
|
{ level: 'yellow' }
|
|
];
|
|
|
|
const result = service.calculateTrend(history);
|
|
|
|
expect(result.trend).toBe('decreasing');
|
|
});
|
|
|
|
it('should detect stable trend', () => {
|
|
const history = [
|
|
{ level: 'green' },
|
|
{ level: 'green' },
|
|
{ level: 'green' }
|
|
];
|
|
|
|
const result = service.calculateTrend(history);
|
|
|
|
expect(result.trend).toBe('stable');
|
|
});
|
|
|
|
it('should return stable for insufficient data', () => {
|
|
const result = service.calculateTrend([]);
|
|
|
|
expect(result.trend).toBe('stable');
|
|
expect(result.confidence).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('validateRules', () => {
|
|
it('should validate correct rules', () => {
|
|
const rules = {
|
|
atm: {
|
|
skimmer: { level: 'red', action: 'immediate_alert' }
|
|
}
|
|
};
|
|
|
|
const result = service.validateRules(rules);
|
|
|
|
expect(result.valid).toBe(true);
|
|
expect(result.errors).toHaveLength(0);
|
|
});
|
|
|
|
it('should detect invalid level', () => {
|
|
const rules = {
|
|
atm: {
|
|
skimmer: { level: 'invalid', action: 'immediate_alert' }
|
|
}
|
|
};
|
|
|
|
const result = service.validateRules(rules);
|
|
|
|
expect(result.valid).toBe(false);
|
|
expect(result.errors.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should detect invalid action', () => {
|
|
const rules = {
|
|
atm: {
|
|
skimmer: { level: 'red', action: 'invalid_action' }
|
|
}
|
|
};
|
|
|
|
const result = service.validateRules(rules);
|
|
|
|
expect(result.valid).toBe(false);
|
|
});
|
|
});
|
|
});
|