/**
* @fileoverview Tests for ComplianceReportMapper
*/
import { describe, it, expect } from 'vitest';
import { ComplianceReportMapper } from '../compliance-report.mapper.js';
import { ComplianceReport as ApiComplianceReport } from '../../../deepsource.js';
import { ReportType } from '../../../types/report-types.js';
import { ComplianceReport } from '../../../domain/aggregates/compliance-report/compliance-report.aggregate.js';
import { IssueCount } from '../../../domain/value-objects/issue-count.js';
describe('ComplianceReportMapper', () => {
const mockApiReport: ApiComplianceReport = {
key: ReportType.OWASP_TOP_10,
title: 'OWASP Top 10',
currentValue: 85,
status: 'READY',
securityIssueStats: [
{
key: 'A01',
title: 'A01: Broken Access Control',
occurrence: {
critical: 2,
major: 5,
minor: 3,
total: 11,
},
},
{
key: 'A02',
title: 'A02: Cryptographic Failures',
occurrence: {
critical: 0,
major: 1,
minor: 4,
total: 7,
},
},
{
key: 'A03',
title: 'A03: Injection',
occurrence: {
critical: 1,
major: 0,
minor: 0,
total: 1,
},
},
],
trends: [],
};
describe('toDomain', () => {
it('should map API compliance report to domain aggregate', () => {
const projectKey = 'test-project';
const repositoryId = 'repo-123';
const report = ComplianceReportMapper.toDomain(mockApiReport, projectKey, repositoryId);
expect(report).toBeInstanceOf(ComplianceReport);
expect(report.projectKey).toBe(projectKey);
expect(report.repositoryId).toBe(repositoryId);
expect(report.reportType).toBe(ReportType.OWASP_TOP_10);
expect(report.categories).toHaveLength(3);
});
it('should map categories correctly', () => {
const report = ComplianceReportMapper.toDomain(mockApiReport, 'test-project', 'repo-123');
const categories = report.categories;
expect(categories[0].name).toBe('A01: Broken Access Control');
expect(categories[0].severity).toBe('CRITICAL');
expect(categories[0].issueCount.count).toBe(11); // 2+5+3+1
expect(categories[1].name).toBe('A02: Cryptographic Failures');
expect(categories[1].severity).toBe('MAJOR'); // Highest severity present
expect(categories[1].issueCount.count).toBe(7); // 0+1+4+2
expect(categories[2].name).toBe('A03: Injection');
expect(categories[2].severity).toBe('CRITICAL');
expect(categories[2].issueCount.count).toBe(1); // 1+0+0+0
});
it('should handle empty security issue stats', () => {
const emptyReport = {
...mockApiReport,
securityIssueStats: [],
};
const report = ComplianceReportMapper.toDomain(emptyReport, 'test-project', 'repo-123');
expect(report.categories).toHaveLength(0);
});
});
describe('mapStatus', () => {
it('should map API status to domain status', () => {
expect(ComplianceReportMapper.mapStatus('READY')).toBe('READY');
expect(ComplianceReportMapper.mapStatus('COMPLETED')).toBe('READY');
expect(ComplianceReportMapper.mapStatus('GENERATING')).toBe('GENERATING');
expect(ComplianceReportMapper.mapStatus('PENDING')).toBe('GENERATING');
expect(ComplianceReportMapper.mapStatus('ERROR')).toBe('ERROR');
expect(ComplianceReportMapper.mapStatus('FAILED')).toBe('ERROR');
});
it('should default to PENDING for unknown status', () => {
expect(ComplianceReportMapper.mapStatus('UNKNOWN')).toBe('PENDING');
expect(ComplianceReportMapper.mapStatus(undefined)).toBe('PENDING');
});
});
describe('mapHighestSeverity', () => {
it('should return CRITICAL when critical issues present', () => {
const occurrence = { critical: 1, major: 5, minor: 3, total: 9 };
expect(ComplianceReportMapper.mapHighestSeverity(occurrence)).toBe('CRITICAL');
});
it('should return MAJOR when no critical but major issues present', () => {
const occurrence = { critical: 0, major: 2, minor: 5, total: 7 };
expect(ComplianceReportMapper.mapHighestSeverity(occurrence)).toBe('MAJOR');
});
it('should return MINOR when no critical/major but minor issues present', () => {
const occurrence = { critical: 0, major: 0, minor: 3, total: 3 };
expect(ComplianceReportMapper.mapHighestSeverity(occurrence)).toBe('MINOR');
});
it('should return INFO when no specific severity issues present', () => {
const occurrence = { critical: 0, major: 0, minor: 0, total: 2 };
expect(ComplianceReportMapper.mapHighestSeverity(occurrence)).toBe('INFO');
});
it('should return INFO when no issues present', () => {
const occurrence = { critical: 0, major: 0, minor: 0, total: 0 };
expect(ComplianceReportMapper.mapHighestSeverity(occurrence)).toBe('INFO');
});
});
describe('toPersistence', () => {
it('should map domain aggregate to persistence format', () => {
const report = ComplianceReportMapper.toDomain(mockApiReport, 'test-project', 'repo-123');
const persistence = ComplianceReportMapper.toPersistence(report);
expect(persistence.id).toBe(report.id);
expect(persistence.projectKey).toBe('test-project');
expect(persistence.repositoryId).toBe('repo-123');
expect(persistence.reportType).toBe(ReportType.OWASP_TOP_10);
expect(persistence.categories).toEqual(report.categories);
expect(persistence.generatedAt).toBeInstanceOf(Date);
expect(persistence.lastUpdated).toBeInstanceOf(Date);
});
});
describe('toDomainFromList', () => {
it('should map multiple API reports to domain aggregates', () => {
const apiReports = [
mockApiReport,
{
...mockApiReport,
key: ReportType.SANS_TOP_25,
title: 'SANS Top 25',
securityIssueStats: [
{
key: 'CWE-79',
title: 'Cross-site Scripting',
occurrence: {
critical: 1,
major: 2,
minor: 1,
total: 4,
},
},
],
},
];
const reports = ComplianceReportMapper.toDomainFromList(
apiReports,
'test-project',
'repo-123'
);
expect(reports).toHaveLength(2);
expect(reports[0].reportType).toBe(ReportType.OWASP_TOP_10);
expect(reports[1].reportType).toBe(ReportType.SANS_TOP_25);
expect(reports[1].categories).toHaveLength(1);
});
it('should handle empty reports list', () => {
const reports = ComplianceReportMapper.toDomainFromList([], 'test-project', 'repo-123');
expect(reports).toEqual([]);
});
});
describe('createCategoryFromStat', () => {
it('should create a compliance category from security issue stat', () => {
const stat = {
key: 'A01',
title: 'Broken Access Control',
occurrence: {
critical: 2,
major: 3,
minor: 1,
total: 6,
},
};
const category = ComplianceReportMapper.createCategoryFromStat(stat, ReportType.OWASP_TOP_10);
expect(category.name).toBe('Broken Access Control');
expect(category.description).toBe('OWASP_TOP_10 category: A01');
expect(category.issueCount.count).toBe(6); // 2+3+1+0
expect(category.nonCompliant.count).toBe(6);
expect(category.compliant.count).toBe(0);
expect(category.severity).toBe('CRITICAL');
});
});
describe('estimateComplianceScore', () => {
it('should return 100 for empty categories', () => {
const score = ComplianceReportMapper.estimateComplianceScore([]);
expect(score).toBe(100);
});
it('should calculate score based on severity and issue counts', () => {
const categories = [
{
name: 'Test Category 1',
description: 'Test',
compliant: IssueCount.create(0),
nonCompliant: IssueCount.create(5),
issueCount: IssueCount.create(5),
severity: 'CRITICAL' as const,
},
{
name: 'Test Category 2',
description: 'Test',
compliant: IssueCount.create(0),
nonCompliant: IssueCount.create(3),
issueCount: IssueCount.create(3),
severity: 'MAJOR' as const,
},
];
const score = ComplianceReportMapper.estimateComplianceScore(categories);
// Expected: 100 - (1 * 20 + 1 * 10 + 8 * 2) = 100 - 46 = 54
expect(score).toBe(54);
});
it('should not go below 0', () => {
const categories = Array(10).fill({
name: 'Critical Category',
description: 'Test',
compliant: IssueCount.create(0),
nonCompliant: IssueCount.create(10),
issueCount: IssueCount.create(10),
severity: 'CRITICAL' as const,
});
const score = ComplianceReportMapper.estimateComplianceScore(categories);
expect(score).toBe(0);
});
it('should not go above 100', () => {
const categories = [
{
name: 'Minor Category',
description: 'Test',
compliant: IssueCount.create(0),
nonCompliant: IssueCount.create(1),
issueCount: IssueCount.create(1),
severity: 'INFO' as const,
},
];
const score = ComplianceReportMapper.estimateComplianceScore(categories);
expect(score).toBe(98); // 100 - 2 = 98
});
});
});