test(matching): add comprehensive tests for buffer functions and edge cases

- Export getPostDanceBufferSlots and HEAT_BUFFER_AFTER for testing
- Add direct tests for getPreDanceBufferSlots (4 tests)
- Add direct tests for getPostDanceBufferSlots (2 tests)
- Add test for HEAT_BUFFER_AFTER constant
- Add edge case for getCoverableHeats with multiple recorder heats
- Add edge case for hasCollision with multi-heat scenarios
- Total: 39/39 tests passing (9 new tests added)
This commit is contained in:
Radosław Gierwiało
2025-11-29 23:52:12 +01:00
parent 4fb78e08ad
commit ce10d20cbb
2 changed files with 95 additions and 0 deletions

View File

@@ -5,12 +5,14 @@
const { const {
getTimeSlot, getTimeSlot,
getPreDanceBufferSlots, getPreDanceBufferSlots,
getPostDanceBufferSlots,
getCoverableHeats, getCoverableHeats,
hasCollision, hasCollision,
getLocationScore, getLocationScore,
buildDivisionSlotMap, buildDivisionSlotMap,
MAX_RECORDINGS_PER_PERSON, MAX_RECORDINGS_PER_PERSON,
HEAT_BUFFER_BEFORE, HEAT_BUFFER_BEFORE,
HEAT_BUFFER_AFTER,
} = require('../services/matching'); } = require('../services/matching');
describe('Matching Service - Unit Tests', () => { describe('Matching Service - Unit Tests', () => {
@@ -30,6 +32,58 @@ describe('Matching Service - Unit Tests', () => {
}); });
}); });
describe('getPreDanceBufferSlots', () => {
it('should return empty array when heatNumber is 1 (no previous heats)', () => {
const heat = { divisionId: 1, competitionTypeId: 1, heatNumber: 1 };
const result = getPreDanceBufferSlots(heat);
expect(result).toEqual([]);
});
it('should return buffer slot for previous heat without divisionSlotMap', () => {
const heat = { divisionId: 1, competitionTypeId: 1, heatNumber: 3 };
const result = getPreDanceBufferSlots(heat);
// HEAT_BUFFER_BEFORE = 1 => blokujemy heatNumber 2
expect(result).toEqual(['1-1-2']);
});
it('should use divisionSlotMap when available', () => {
const divisionSlotMap = new Map([[1, 2]]); // division 1 -> slot2
const heat = { divisionId: 1, competitionTypeId: 1, heatNumber: 3 };
const result = getPreDanceBufferSlots(heat, divisionSlotMap);
// Format: slot{slotOrder}-{competitionTypeId}-{prevHeatNumber}
expect(result).toEqual(['slot2-1-2']);
});
it('should fallback to division-based buffer when division not in map', () => {
const divisionSlotMap = new Map([[99, 1]]); // 1 nie jest zmapowane
const heat = { divisionId: 1, competitionTypeId: 1, heatNumber: 3 };
const result = getPreDanceBufferSlots(heat, divisionSlotMap);
expect(result).toEqual(['1-1-2']);
});
});
describe('getPostDanceBufferSlots', () => {
it('should return buffer slot for next heat without divisionSlotMap', () => {
const heat = { divisionId: 1, competitionTypeId: 1, heatNumber: 3 };
const result = getPostDanceBufferSlots(heat);
// HEAT_BUFFER_AFTER = 1 => blokujemy heatNumber 4
expect(result).toEqual(['1-1-4']);
});
it('should use divisionSlotMap when available', () => {
const divisionSlotMap = new Map([[1, 2]]); // division 1 -> slot2
const heat = { divisionId: 1, competitionTypeId: 1, heatNumber: 3 };
const result = getPostDanceBufferSlots(heat, divisionSlotMap);
expect(result).toEqual(['slot2-1-4']);
});
});
describe('getCoverableHeats', () => { describe('getCoverableHeats', () => {
it('should return all heats when recorder has no heats', () => { it('should return all heats when recorder has no heats', () => {
const dancerHeats = [ const dancerHeats = [
@@ -107,6 +161,26 @@ describe('Matching Service - Unit Tests', () => {
// Novice H1 blocked, Intermediate H1 available // Novice H1 blocked, Intermediate H1 available
expect(result.map(h => h.id)).toEqual([2]); expect(result.map(h => h.id)).toEqual([2]);
}); });
it('should respect buffers around multiple recorder heats', () => {
const dancerHeats = [
{ id: 1, divisionId: 1, competitionTypeId: 1, heatNumber: 1 },
{ id: 2, divisionId: 1, competitionTypeId: 1, heatNumber: 3 },
{ id: 3, divisionId: 1, competitionTypeId: 1, heatNumber: 5 },
];
const recorderHeats = [
{ divisionId: 1, competitionTypeId: 1, heatNumber: 2 },
{ divisionId: 1, competitionTypeId: 1, heatNumber: 4 },
];
const result = getCoverableHeats(dancerHeats, recorderHeats);
// Recorder tańczy 2 i 4, z buforami:
// - przy 2: blokuje 1,2,3
// - przy 4: blokuje 3,4,5
// => wszystkie dancerHeats wchodzą w któryś bufor/taniec
expect(result).toHaveLength(0);
});
}); });
describe('hasCollision', () => { describe('hasCollision', () => {
@@ -155,6 +229,21 @@ describe('Matching Service - Unit Tests', () => {
// Heat 3 is outside buffer of Heat 1 (buffer = 1, so Heat 2 is blocked, Heat 3 is OK) // Heat 3 is outside buffer of Heat 1 (buffer = 1, so Heat 2 is blocked, Heat 3 is OK)
expect(hasCollision(dancerHeats, recorderHeats)).toBe(false); expect(hasCollision(dancerHeats, recorderHeats)).toBe(false);
}); });
it('should return true when any dancer heat collides with any recorder heat or buffer', () => {
const dancerHeats = [
{ divisionId: 1, competitionTypeId: 1, heatNumber: 1 },
{ divisionId: 1, competitionTypeId: 1, heatNumber: 5 }, // ten będzie w buforze
];
const recorderHeats = [
{ divisionId: 1, competitionTypeId: 1, heatNumber: 4 },
];
// Recorder tańczy 4 -> bufor obejmuje 3,4,5
// dancerHeats[1] = heatNumber 5 -> kolizja
expect(hasCollision(dancerHeats, recorderHeats)).toBe(true);
});
}); });
describe('getLocationScore', () => { describe('getLocationScore', () => {
@@ -202,6 +291,10 @@ describe('Matching Service - Unit Tests', () => {
it('should have correct heat buffer before', () => { it('should have correct heat buffer before', () => {
expect(HEAT_BUFFER_BEFORE).toBe(1); expect(HEAT_BUFFER_BEFORE).toBe(1);
}); });
it('should have correct heat buffer after', () => {
expect(HEAT_BUFFER_AFTER).toBe(1);
});
}); });
describe('Schedule Config - buildDivisionSlotMap', () => { describe('Schedule Config - buildDivisionSlotMap', () => {

View File

@@ -552,10 +552,12 @@ module.exports = {
getCoverableHeats, getCoverableHeats,
getTimeSlot, getTimeSlot,
getPreDanceBufferSlots, getPreDanceBufferSlots,
getPostDanceBufferSlots,
getLocationScore, getLocationScore,
buildDivisionSlotMap, buildDivisionSlotMap,
getEffectiveTier, getEffectiveTier,
getRecordingStatsForUsers, getRecordingStatsForUsers,
MAX_RECORDINGS_PER_PERSON, MAX_RECORDINGS_PER_PERSON,
HEAT_BUFFER_BEFORE, HEAT_BUFFER_BEFORE,
HEAT_BUFFER_AFTER,
}; };