From ce10d20cbba989687057a6fd9847a76678d4c908 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Gierwia=C5=82o?= Date: Sat, 29 Nov 2025 23:52:12 +0100 Subject: [PATCH] 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) --- backend/src/__tests__/matching.test.js | 93 ++++++++++++++++++++++++++ backend/src/services/matching.js | 2 + 2 files changed, 95 insertions(+) diff --git a/backend/src/__tests__/matching.test.js b/backend/src/__tests__/matching.test.js index 7453b08..81cd5da 100644 --- a/backend/src/__tests__/matching.test.js +++ b/backend/src/__tests__/matching.test.js @@ -5,12 +5,14 @@ const { getTimeSlot, getPreDanceBufferSlots, + getPostDanceBufferSlots, getCoverableHeats, hasCollision, getLocationScore, buildDivisionSlotMap, MAX_RECORDINGS_PER_PERSON, HEAT_BUFFER_BEFORE, + HEAT_BUFFER_AFTER, } = require('../services/matching'); 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', () => { it('should return all heats when recorder has no heats', () => { const dancerHeats = [ @@ -107,6 +161,26 @@ describe('Matching Service - Unit Tests', () => { // Novice H1 blocked, Intermediate H1 available 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', () => { @@ -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) 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', () => { @@ -202,6 +291,10 @@ describe('Matching Service - Unit Tests', () => { it('should have correct heat buffer before', () => { expect(HEAT_BUFFER_BEFORE).toBe(1); }); + + it('should have correct heat buffer after', () => { + expect(HEAT_BUFFER_AFTER).toBe(1); + }); }); describe('Schedule Config - buildDivisionSlotMap', () => { diff --git a/backend/src/services/matching.js b/backend/src/services/matching.js index 7ddfb7a..ccf2aaf 100644 --- a/backend/src/services/matching.js +++ b/backend/src/services/matching.js @@ -552,10 +552,12 @@ module.exports = { getCoverableHeats, getTimeSlot, getPreDanceBufferSlots, + getPostDanceBufferSlots, getLocationScore, buildDivisionSlotMap, getEffectiveTier, getRecordingStatsForUsers, MAX_RECORDINGS_PER_PERSON, HEAT_BUFFER_BEFORE, + HEAT_BUFFER_AFTER, };