// test/commitment-deduplication.test.ts
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { SummaryCalculator } from '../src/services/summary-calculator.js';
import type { DailySummary, CompanyConfig } from '../src/types/index.js';
/**
* Test that entries with multiple tags mapping to the same commitment
* only count once per commitment (no duplicate counting)
*/
describe('Commitment Deduplication', () => {
it('should not double-count entries with multiple tags mapping to same commitment', () => {
const calculator = new SummaryCalculator();
// Config where #dev and #devops both map to 'development'
const config: CompanyConfig = {
company: 'Test',
commitments: {
development: { limit: 20, unit: 'hours/week' },
total: { limit: 25, unit: 'hours/week' }
},
projects: {
'Infrastructure': {
tags: ['devops'],
commitment: 'development'
}
},
tagMappings: {
'dev': 'development'
}
};
// Entry with both #dev and #devops (both map to development)
const days: DailySummary[] = [{
date: '2025-10-21',
totalHours: 2,
entries: [{
date: '2025-10-21',
time: '14:00',
task: 'DevOps work',
duration: 2,
tags: ['dev', 'devops'] // Both map to 'development'
}]
}];
const summary = calculator.calculateWeekly(43, 2025, '2025-10-21', '2025-10-27', days, config);
// Should count 2h once, not 4h (2h × 2 tags)
assert.strictEqual(summary.byCommitment['development'], 2,
'Entry with multiple tags mapping to same commitment should count only once');
// Both tags should still be tracked separately
assert.strictEqual(summary.byTag['dev'], 2, 'Tag dev should have 2h');
assert.strictEqual(summary.byTag['devops'], 2, 'Tag devops should have 2h');
});
it('should handle multiple entries with overlapping tag mappings', () => {
const calculator = new SummaryCalculator();
const config: CompanyConfig = {
company: 'Test',
commitments: {
development: { limit: 20, unit: 'hours/week' },
total: { limit: 25, unit: 'hours/week' }
},
projects: {
'Infrastructure': {
tags: ['devops', 'infra'],
commitment: 'development'
}
},
tagMappings: {
'dev': 'development',
'code': 'development'
}
};
const days: DailySummary[] = [{
date: '2025-10-21',
totalHours: 5,
entries: [
{
date: '2025-10-21',
time: '10:00',
task: 'Coding',
duration: 2,
tags: ['dev', 'code'] // Both map to 'development'
},
{
date: '2025-10-21',
time: '14:00',
task: 'Infrastructure',
duration: 3,
tags: ['devops', 'infra'] // Both map to 'development' via Infrastructure project
}
]
}];
const summary = calculator.calculateWeekly(43, 2025, '2025-10-21', '2025-10-27', days, config);
// Should be 2h + 3h = 5h, not 10h (if double-counted)
assert.strictEqual(summary.byCommitment['development'], 5,
'Multiple entries with overlapping mappings should each count once');
// Tags should still aggregate correctly
assert.strictEqual(summary.byTag['dev'], 2);
assert.strictEqual(summary.byTag['code'], 2);
assert.strictEqual(summary.byTag['devops'], 3);
assert.strictEqual(summary.byTag['infra'], 3);
});
it('should handle entry with three tags mapping to same commitment', () => {
const calculator = new SummaryCalculator();
const config: CompanyConfig = {
company: 'Test',
commitments: {
development: { limit: 20, unit: 'hours/week' },
total: { limit: 25, unit: 'hours/week' }
},
tagMappings: {
'dev': 'development',
'code': 'development',
'coding': 'development'
}
};
const days: DailySummary[] = [{
date: '2025-10-21',
totalHours: 1.5,
entries: [{
date: '2025-10-21',
time: '14:00',
task: 'Development work',
duration: 1.5,
tags: ['dev', 'code', 'coding'] // All three map to 'development'
}]
}];
const summary = calculator.calculateWeekly(43, 2025, '2025-10-21', '2025-10-27', days, config);
// Should count 1.5h once, not 4.5h (1.5h × 3 tags)
assert.strictEqual(summary.byCommitment['development'], 1.5,
'Entry with three tags mapping to same commitment should count only once');
});
it('should correctly count entries mapping to different commitments', () => {
const calculator = new SummaryCalculator();
const config: CompanyConfig = {
company: 'Test',
commitments: {
development: { limit: 20, unit: 'hours/week' },
meeting: { limit: 5, unit: 'hours/week' },
total: { limit: 25, unit: 'hours/week' }
},
tagMappings: {
'dev': 'development',
'sync': 'meeting'
}
};
const days: DailySummary[] = [{
date: '2025-10-21',
totalHours: 3,
entries: [
{
date: '2025-10-21',
time: '10:00',
task: 'Development',
duration: 2,
tags: ['dev']
},
{
date: '2025-10-21',
time: '14:00',
task: 'Standup',
duration: 1,
tags: ['sync']
}
]
}];
const summary = calculator.calculateWeekly(43, 2025, '2025-10-21', '2025-10-27', days, config);
// Should have correct counts for different commitments
assert.strictEqual(summary.byCommitment['development'], 2);
assert.strictEqual(summary.byCommitment['meeting'], 1);
});
});