From 596e4a274dc63f92e67acacde0eff7fa5be56f5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=85=E6=88=BF?= Date: Fri, 16 Jan 2026 23:51:55 +0800 Subject: [PATCH] refactor: Phase 1 - Extract TimeSlotGenerator from God Class - Create TimeSlot model class in schedule/model/ - Create TimeSlotGenerator component in schedule/generator/ - Modify MartialScheduleArrangeServiceImpl to use TimeSlotGenerator - Remove internal TimeSlot class (now standalone) - Add 11 unit tests for TimeSlotGenerator - All 438 tests passing Related to Issue #11 --- .../buckets/.bloomcycle.bin/xl.meta | Bin 743 -> 743 bytes .../buckets/.usage-cache.bin.bkp/xl.meta | Bin 637 -> 630 bytes .../buckets/.usage-cache.bin/xl.meta | Bin 637 -> 630 bytes .../.minio.sys/buckets/.usage.json/xl.meta | Bin 2360 -> 2360 bytes .../.usage-cache.bin.bkp/xl.meta | Bin 627 -> 636 bytes .../000000-assets/.usage-cache.bin/xl.meta | Bin 627 -> 636 bytes .../buckets/assets/.metadata.bin/xl.meta | Bin 1466 -> 1466 bytes .../assets/.usage-cache.bin.bkp/xl.meta | Bin 575 -> 575 bytes .../buckets/assets/.usage-cache.bin/xl.meta | Bin 575 -> 575 bytes .../MartialScheduleArrangeServiceImpl.java | 97 ++--------- .../schedule/generator/TimeSlotGenerator.java | 127 +++++++++++++++ .../service/schedule/model/TimeSlot.java | 69 ++++++++ .../generator/TimeSlotGeneratorTest.java | 153 ++++++++++++++++++ 13 files changed, 359 insertions(+), 87 deletions(-) create mode 100644 src/main/java/org/springblade/modules/martial/service/schedule/generator/TimeSlotGenerator.java create mode 100644 src/main/java/org/springblade/modules/martial/service/schedule/model/TimeSlot.java create mode 100644 src/test/java/org/springblade/modules/martial/service/schedule/generator/TimeSlotGeneratorTest.java diff --git a/minio_data/.minio.sys/buckets/.bloomcycle.bin/xl.meta b/minio_data/.minio.sys/buckets/.bloomcycle.bin/xl.meta index 6f64feb7aa2deb68c6e713fa49ef8e1d21144041..a660807cce6494bee1d50ea562799597df40566f 100644 GIT binary patch delta 446 zcmaFP`kZxwI&Zhbi?Z0G*N(6R1h&tbXkskzuKIVUx0_SY4w-w5C5n1B6U*z^p^7H{ zv5`+QPqa)jGDu1@NwF|XO-?jRH8e1^v@|p{vP?@cF`OLEs9%3BO@BFK6Su?N8|HEfRx zM{qY9sZLk~;U)uJ8uQlhc=a;4bgZV~xyNA$ZmQwb_zMW`Lc@2X!?|&i PMn;bN5!_J2GaCB=XwR7L delta 446 zcmaFP`kZxwI&aqv{)8zOPmi!<{bIN~(ZpCF{bJF!o~bV1dJ5)j?pUrMG_kyn9ja*J z9~*gNb5rxgRKrxGB+F!zBuh&JOGC>fBjZ#fBMWm2vy{oyOY diff --git a/minio_data/.minio.sys/buckets/.usage-cache.bin.bkp/xl.meta b/minio_data/.minio.sys/buckets/.usage-cache.bin.bkp/xl.meta index 5e32f856efa8f07d2c4acf8a41cc8286087fcbef..5eb67790ca92f512fe5ce9ef8da8d3958f9eaacf 100644 GIT binary patch delta 314 zcmey%@{MJJI&Zhbi!%Gdg-2LkX&POaXkskD@~2CBr;(fAEGwyP*Zn(YPArd8KfEd+ zv8co`II}9Xc;ZEd!%ILcAidN#Br`YlvINwSiGOSqj8hB^4J^&fOw3IzOf4*pEzQ!* z%+nH$O$;rJlZ=v6CPy&pSv}({KF8R&B(F3l=g8yre`_);g^CjFHro~l7=OHcSAKD6 zb-^{KOCjZ}gj8ZmvS6BHY7MFw;q$HN49_L|Y0D{b{ zxKP8cInJwtvoi}kQWJAB^U@D3bxusqNNrx0SX`W1Qrxj9Ib*_PH6|syh58`&q_qZM zpnHk2HP&NMez6=A6E_C~yBrt80uBYPqrV(`7!*A=Sdf$jHJx)za7`%`(M2$u!B* z$RO1s*(@c=*wP@$a&iQto|TP+zb0ejlDyKKoFlJhSZ=P^!RR$0vjMV0Z`W=gsGbSxdEG|whDPC&;2D%X5WHBZs zQ;_t8Ma5N@7+YgK7UdVqF)?v-FtE#UG6BWl(Q%TP&;Oam%<~SI G3IhOKntXZy diff --git a/minio_data/.minio.sys/buckets/.usage-cache.bin/xl.meta b/minio_data/.minio.sys/buckets/.usage-cache.bin/xl.meta index e800fd91a93fbf8dc6fdf2d5dfebed2d2c09c7f3..d22f73ae28dde173368190f3680bf3e23b8aa366 100644 GIT binary patch delta 329 zcmey%@{MJJI&Zhbi!!;*hDTULmR(ApXksi77k%!wg0oBf)*~PCk3NhzHnBWT{qU-Q z#G(?%;LNJj;)xd-4le<*fb>$|kj&iF%MwsSCjK#1Uy@pqn0`~iIK|M=z|zdj#N5Qf z)WXu((k#u)JT1}K#L&Vx$tXExvLU0I)55ZI=}D&<&Mjh7+s@dyB(F3l=g8yre`_); zg^CjFHro~l7=OHcSAKD6b-^{KOCjZ}gj8ZmvS6BHY z7MFw;q$HN49_L|Y0D{b{xKP8cInJwtvoi}kQWJAB^U@D3bxusqNNrx0SX`W1Qrxj9 zIb*_PH6|syh58`&q_qZMpnHk2HP&NMez6=A6E_C~yBrt80uBYPqrV(`7!*(5Ieri=f zVo`}>aAs9%@x+S^r&8WeKPu6aSb_j$l;FT#{Onn0`|s)xyNc$ih6; z(%2-;GQ~W}G|AG)Ak`w-EG5a<(jdulVcEI#q|*%NJc8#hVr*QJSDKS^=ZTP7?F^Khv0b-T_l#08{aTk^lez diff --git a/minio_data/.minio.sys/buckets/.usage.json/xl.meta b/minio_data/.minio.sys/buckets/.usage.json/xl.meta index 2258b4b47d3b06333415ab6bc390851c48a19e8d..2884fb93932a8e611676730a87ebb0a9ad25c813 100644 GIT binary patch delta 183 zcmV;o07(D161WnOCk=~0=yqSG$HWBSn;5u}G&2yz#II9ogGddBe6^=w{7>bPcYX(p zK5} zm`;vhl#5-GT9TN4Q^71HDa9bgDAg2*Qj<D=$gQ_W41Q<hQw$Rg7M7h$Pdd$T zZkw}wE@R`8ywaSUqwH(K9!1u^x!5KCY*w!4Tfx_*r7iP|w0s)pyt!;$V%qdXc=8%X f2@%r}19K}AODiK2JtGrSV{;=z3!}|97!%k5tO`bc diff --git a/minio_data/.minio.sys/buckets/000000-assets/.usage-cache.bin.bkp/xl.meta b/minio_data/.minio.sys/buckets/000000-assets/.usage-cache.bin.bkp/xl.meta index c7cbbacf94be5675bf9f5da186ab34b9854325e8..d5341bd942cb7600008a9d7f11cb1838347191ac 100644 GIT binary patch delta 322 zcmey&@`q)DI&Zhbi_#xm>yEH27iCPHXksj2@O0^aAs9%@x+S^CzpU&KzgZfNM>&8WeKPu6aUyKBqdoSC#M)Cnj0ILo12-M8zdSh znOY=Un41_Tr6!vjOpajGvy3*G^_8)4NnUAA&XJdT4{KJ}-@4OvWux1Me2#{-B6H6? zv@Ix8N?iW&%3UwpMh=aw`hQs%?y@qta82INs4A0pb(K$IaY<-FN@7XsaUNC%Ajr&$ z3pF&kkv@q@Bx8$yX+chYVoJxN0VTpFEu}B2XU#P!9vcA$Eoa4uOayhNzO79{>7dDH~=e&tVkE HIP(brVpV_v delta 313 zcmeyv@|k6VI&aqv{`mVlKOSN6vDSYz(ZpDw`ggth4i=Z2YxMUV-Fk)h*TnKT^#iK{ z5{pV4gEOmAizi-WIIsl70@6!;Lo#zyFH1lTnfS*>!5}%sJjKK!DK*u?GQ~30&@$27 z*gVbH!YDDxJjp0IX>tUko@L$~=1j)MC3&SeIY%CJ?mX^uag~L+LjLXr^98i=b7n99nqgmdzKMpcsdqIyojLZVm=^xx*X`4IBzy8oNqc>$|kj&iF%MwsSCjK#1Uy@pqn0`|sDaj%^ImIZ^+}Oz6 z+|1P6AkjF<)FRo!+{7>`HQC%?vLU0I>cUc)+xF+vsRF%oQy2>Z9 zxFob7C9x#+I1eiW5M*Y>g&LaNNT0+clCed+!xF{NWsa>j&3#Z{LWTVp*I zDl2AKoRSM9imQreGcYl6b1<-jbT2B)m@WrmFfc4kJ`B<~5vY#=sE2{!5IaKyhd@LU XLsUskkAMBKlnpbK=P(LnocRO*{D6ks delta 327 zcmeyv@|k6VI&aqv{`h50sYh78$XjPkG%*(NWtjTI;HU4$byGu?{`l3TPArd8Kd>qw zv8co`II}9Xc;ZEd14}?GAidN#Br`YlvINwSiGNHdM=+{oEJ-a%OuwmMkep(kVq%e$ znrdO0Vwq}anP_foo@Q)el$d0mWR#q=uK-|&I(}U(AcW~mxW;}E5j1b$@>{qWpdS4`6L#X zgchVEmZTo%VPyb<%#1lKd@0s(3b#n=zeXVe%o6+6h24ml#`PJ%H-un3%XZ7}(_wb1*b;D0pe?Ds8bpaN&_z QdU43n6E}F4$R6nd0J*+}TmS$7 diff --git a/minio_data/.minio.sys/buckets/assets/.metadata.bin/xl.meta b/minio_data/.minio.sys/buckets/assets/.metadata.bin/xl.meta index 25beed042c4f8038950375050e873c98d48532c5..fd0f469307176017351c63aba305f3e1ea4c885b 100644 GIT binary patch delta 194 zcmdnRy^DK-I&ZgT>ebBH=|@;BHmmebG%*&i+Ze;G`q$rk*JFu2wfh_oPAsouhbo%* z$40?8DcQ`>A}KA+(%3vLEzLA3*(l8-$<)-qEGfy*(js|s1f!mw*SE4J#>ORir8zlA zS=8c}7cH^5ldtA6Eh}$f{M(Ce@dkT37bNx9UzDxc6yE)M@)pKbitec;PNm7&sU<%7 o$%!SI`FTn@O2x)jKAFW(@y+#2QyJMaf5tF+A8p<&%`%@60D8JjF8}}l delta 194 zcmdnRy^DK-I&TN(>&ta3^^dSTP}eq{Xksidd$rw?NJsadv(i^=yUlb)abkHLJ57YgcBrC> ze@xYvq?RP6-&C+PvPd;FHcd20Nw!Q*HA*!!OiMI3OEpO}PfRvSGqRj)$f#zzu1jd-2SBm#6Vomk+k=as4rq tH#2I<rDcv*JPxIv*KL{>kXX!tF7K4FHnYP;&qP delta 193 zcmdnbvY%ywI&aqv{&;tZZAVyG9tb^|XksjI^WdA)%u9XeE-iR>iI3-M%Ea ze@rJwFsj8YNi9iCzo}rFVrFh^mSSRHYG#sZV4P-|A=% zX@+xs`}7twHZI94&B-~kM*P4QKKohLt=@-EePlg)Bcs`D`GV}Gjk?!1r#JdrT>CqD tGozMFuG%V}#Nv|Bf|SIP)Z;v?3_y^XF^7e>^TqkeKN+1^$~9Qu0{{q!PVWE! diff --git a/minio_data/.minio.sys/buckets/assets/.usage-cache.bin/xl.meta b/minio_data/.minio.sys/buckets/assets/.usage-cache.bin/xl.meta index 48ca106975e3889210c54663bdd7022d2c6a33ff..9324f621726a54a02fed73a81ad8342bd31ff26a 100644 GIT binary patch delta 179 zcmdnbvY%ywI&Zhbi&D8qZ;!C#naQ$EG%*(VBk!TC9OvS?G}w3BvjtJs6U*z^p^7H{ zu~D!zvPd;FHcd20Nw!Q*HA*!!OiMI3OEpO}PfRvSGqRiUOe;O#17NjJW dq#oyCWdMTAthi8v&PPU*e=<6;aC^*Q0{{t*M)Lpw delta 179 zcmdnbvY%ywI&aqv{&>N)21i)DLqlUGnivb%d!|U0u5c{hrQcX=Stm1TVtE}qRMEsg zHVURGX6D9bDJBM{W+tfy#%TsgmgW{|7G`D^iIx_ImWh)i81-~7J>@vb*tjIGG$-fC z8u0^L`0Qs{w|XBw^^x`Hjf`fq getUnlockedCompetitions() { @@ -352,52 +355,14 @@ public class MartialScheduleArrangeServiceImpl implements IMartialScheduleArrang return athleteMapper.selectList(wrapper); } + + /** + * Generate time slots for competition + * @deprecated Use TimeSlotGenerator.generate() directly + */ + @Deprecated private List generateTimeSlots(MartialCompetition competition) { - List timeSlots = new ArrayList<>(); - - LocalDateTime startTime = competition.getCompetitionStartTime(); - LocalDateTime endTime = competition.getCompetitionEndTime(); - - if (startTime == null || endTime == null) { - log.warn("赛事时间信息不完整, 使用默认时间段"); - return timeSlots; - } - - LocalDate currentDate = startTime.toLocalDate(); - LocalDate endDate = endTime.toLocalDate(); - - while (!currentDate.isAfter(endDate)) { - // 上午时段 (08:00-12:00, 共240分钟) - TimeSlot morning = new TimeSlot(); - morning.setDate(currentDate); - morning.setPeriod("morning"); - morning.setStartTime("08:00"); - morning.setCapacity(240); // 4小时 = 240分钟 - timeSlots.add(morning); - - // 下午时段 (14:00-18:00, 共240分钟) - TimeSlot afternoon = new TimeSlot(); - afternoon.setDate(currentDate); - afternoon.setPeriod("afternoon"); - afternoon.setStartTime("14:00"); - afternoon.setCapacity(240); // 4小时 = 240分钟 - timeSlots.add(afternoon); - - // 晚上时段 (19:00-22:00, 共180分钟) - TimeSlot evening = new TimeSlot(); - evening.setDate(currentDate); - evening.setPeriod("evening"); - evening.setStartTime("19:00"); - evening.setCapacity(180); // 3小时 = 180分钟 - timeSlots.add(evening); - - currentDate = currentDate.plusDays(1); - } - - log.info("生成时间段: {}天, 每天3个时段(上午/下午/晚上), 共{}个时段", - java.time.temporal.ChronoUnit.DAYS.between(startTime.toLocalDate(), endDate) + 1, timeSlots.size()); - - return timeSlots; + return timeSlotGenerator.generate(competition); } @@ -953,48 +918,6 @@ public class MartialScheduleArrangeServiceImpl implements IMartialScheduleArrang } } - // ==================== 内部数据类 ==================== - - private static class TimeSlot { - private LocalDate date; - private String period; // morning/afternoon - private String startTime; // 08:30/13:30 - private Integer capacity; // 容量(分钟) - - // Getters and Setters - public LocalDate getDate() { - return date; - } - - public void setDate(LocalDate date) { - this.date = date; - } - - public String getPeriod() { - return period; - } - - public void setPeriod(String period) { - this.period = period; - } - - public String getStartTime() { - return startTime; - } - - public void setStartTime(String startTime) { - this.startTime = startTime; - } - - public Integer getCapacity() { - return capacity; - } - - public void setCapacity(Integer capacity) { - this.capacity = capacity; - } - } - private static class ScheduleGroupData { private String groupName; private Long projectId; diff --git a/src/main/java/org/springblade/modules/martial/service/schedule/generator/TimeSlotGenerator.java b/src/main/java/org/springblade/modules/martial/service/schedule/generator/TimeSlotGenerator.java new file mode 100644 index 0000000..449c530 --- /dev/null +++ b/src/main/java/org/springblade/modules/martial/service/schedule/generator/TimeSlotGenerator.java @@ -0,0 +1,127 @@ +package org.springblade.modules.martial.service.schedule.generator; + +import lombok.extern.slf4j.Slf4j; +import org.springblade.modules.martial.pojo.entity.MartialCompetition; +import org.springblade.modules.martial.service.schedule.model.TimeSlot; +import org.springframework.stereotype.Component; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.List; + +/** + * Time slot generator for schedule arrangement + * Generates available time slots based on competition dates + * + * @author Refactored from MartialScheduleArrangeServiceImpl + */ +@Slf4j +@Component +public class TimeSlotGenerator { + + // Default time slot configurations + private static final String MORNING_START = "08:00"; + private static final String AFTERNOON_START = "14:00"; + private static final String EVENING_START = "19:00"; + + private static final int MORNING_CAPACITY = 240; // 4 hours = 240 minutes + private static final int AFTERNOON_CAPACITY = 240; // 4 hours = 240 minutes + private static final int EVENING_CAPACITY = 180; // 3 hours = 180 minutes + + /** + * Generate time slots for a competition + * + * @param competition the competition entity + * @return list of time slots + */ + public List generate(MartialCompetition competition) { + if (competition == null) { + log.warn("Competition is null, returning empty time slots"); + return new ArrayList<>(); + } + return generate( + competition.getCompetitionStartTime(), + competition.getCompetitionEndTime() + ); + } + + /** + * Generate time slots between start and end time + * + * @param startTime competition start time + * @param endTime competition end time + * @return list of time slots + */ + public List generate(LocalDateTime startTime, LocalDateTime endTime) { + List timeSlots = new ArrayList<>(); + + if (startTime == null || endTime == null) { + log.warn("Competition time info incomplete, returning empty time slots"); + return timeSlots; + } + + LocalDate currentDate = startTime.toLocalDate(); + LocalDate endDate = endTime.toLocalDate(); + + while (!currentDate.isAfter(endDate)) { + timeSlots.addAll(generateDailySlots(currentDate)); + currentDate = currentDate.plusDays(1); + } + + long days = ChronoUnit.DAYS.between(startTime.toLocalDate(), endDate) + 1; + log.info("Generated time slots: {} days, 3 slots per day (morning/afternoon/evening), total {} slots", + days, timeSlots.size()); + + return timeSlots; + } + + /** + * Generate time slots for a single day + * + * @param date the date + * @return list of time slots for that day + */ + public List generateDailySlots(LocalDate date) { + List slots = new ArrayList<>(); + + // Morning slot (08:00-12:00) + slots.add(TimeSlot.builder() + .date(date) + .period("morning") + .startTime(MORNING_START) + .capacity(MORNING_CAPACITY) + .build()); + + // Afternoon slot (14:00-18:00) + slots.add(TimeSlot.builder() + .date(date) + .period("afternoon") + .startTime(AFTERNOON_START) + .capacity(AFTERNOON_CAPACITY) + .build()); + + // Evening slot (19:00-22:00) + slots.add(TimeSlot.builder() + .date(date) + .period("evening") + .startTime(EVENING_START) + .capacity(EVENING_CAPACITY) + .build()); + + return slots; + } + + /** + * Calculate total capacity in minutes + * + * @param timeSlots list of time slots + * @return total capacity in minutes + */ + public int calculateTotalCapacity(List timeSlots) { + return timeSlots.stream() + .mapToInt(TimeSlot::getCapacity) + .sum(); + } +} diff --git a/src/main/java/org/springblade/modules/martial/service/schedule/model/TimeSlot.java b/src/main/java/org/springblade/modules/martial/service/schedule/model/TimeSlot.java new file mode 100644 index 0000000..8302bf6 --- /dev/null +++ b/src/main/java/org/springblade/modules/martial/service/schedule/model/TimeSlot.java @@ -0,0 +1,69 @@ +package org.springblade.modules.martial.service.schedule.model; + +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; +import lombok.Builder; + +import java.time.LocalDate; + +/** + * Time slot model for schedule arrangement + * Represents a time period (morning/afternoon/evening) on a specific date + * + * @author Refactored from MartialScheduleArrangeServiceImpl + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class TimeSlot { + + /** + * Date of the time slot + */ + private LocalDate date; + + /** + * Period: morning, afternoon, evening + */ + private String period; + + /** + * Start time in HH:mm format (e.g., "08:00", "14:00", "19:00") + */ + private String startTime; + + /** + * Capacity in minutes + */ + private Integer capacity; + + /** + * Get unique key for this time slot + */ + public String getSlotKey() { + return date.toString() + "_" + period; + } + + /** + * Check if this is a morning slot + */ + public boolean isMorning() { + return "morning".equals(period); + } + + /** + * Check if this is an afternoon slot + */ + public boolean isAfternoon() { + return "afternoon".equals(period); + } + + /** + * Check if this is an evening slot + */ + public boolean isEvening() { + return "evening".equals(period); + } +} diff --git a/src/test/java/org/springblade/modules/martial/service/schedule/generator/TimeSlotGeneratorTest.java b/src/test/java/org/springblade/modules/martial/service/schedule/generator/TimeSlotGeneratorTest.java new file mode 100644 index 0000000..cd06463 --- /dev/null +++ b/src/test/java/org/springblade/modules/martial/service/schedule/generator/TimeSlotGeneratorTest.java @@ -0,0 +1,153 @@ +package org.springblade.modules.martial.service.schedule.generator; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springblade.modules.martial.pojo.entity.MartialCompetition; +import org.springblade.modules.martial.service.schedule.model.TimeSlot; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for TimeSlotGenerator + */ +@DisplayName("TimeSlotGenerator Tests") +class TimeSlotGeneratorTest { + + private TimeSlotGenerator generator; + + @BeforeEach + void setUp() { + generator = new TimeSlotGenerator(); + } + + @Test + @DisplayName("Should generate 3 slots per day") + void shouldGenerate3SlotsPerDay() { + LocalDate date = LocalDate.of(2026, 1, 15); + List slots = generator.generateDailySlots(date); + + assertEquals(3, slots.size()); + assertTrue(slots.get(0).isMorning()); + assertTrue(slots.get(1).isAfternoon()); + assertTrue(slots.get(2).isEvening()); + } + + @Test + @DisplayName("Should generate correct time slots for single day competition") + void shouldGenerateSlotsForSingleDay() { + LocalDateTime start = LocalDateTime.of(2026, 1, 15, 8, 0); + LocalDateTime end = LocalDateTime.of(2026, 1, 15, 22, 0); + + List slots = generator.generate(start, end); + + assertEquals(3, slots.size()); + } + + @Test + @DisplayName("Should generate correct time slots for multi-day competition") + void shouldGenerateSlotsForMultiDay() { + LocalDateTime start = LocalDateTime.of(2026, 1, 15, 8, 0); + LocalDateTime end = LocalDateTime.of(2026, 1, 17, 22, 0); + + List slots = generator.generate(start, end); + + assertEquals(9, slots.size()); // 3 days * 3 slots + } + + @Test + @DisplayName("Should return empty list when times are null") + void shouldReturnEmptyWhenTimesNull() { + List slots = generator.generate(null, null); + assertTrue(slots.isEmpty()); + } + + @Test + @DisplayName("Should generate from competition entity") + void shouldGenerateFromCompetition() { + MartialCompetition competition = new MartialCompetition(); + competition.setCompetitionStartTime(LocalDateTime.of(2026, 1, 15, 8, 0)); + competition.setCompetitionEndTime(LocalDateTime.of(2026, 1, 16, 22, 0)); + + List slots = generator.generate(competition); + + assertEquals(6, slots.size()); // 2 days * 3 slots + } + + @Test + @DisplayName("Should return empty list when competition is null") + void shouldReturnEmptyWhenCompetitionNull() { + List slots = generator.generate((MartialCompetition) null); + assertTrue(slots.isEmpty()); + } + + @Test + @DisplayName("Should calculate correct total capacity") + void shouldCalculateTotalCapacity() { + LocalDateTime start = LocalDateTime.of(2026, 1, 15, 8, 0); + LocalDateTime end = LocalDateTime.of(2026, 1, 15, 22, 0); + + List slots = generator.generate(start, end); + int totalCapacity = generator.calculateTotalCapacity(slots); + + // 240 (morning) + 240 (afternoon) + 180 (evening) = 660 + assertEquals(660, totalCapacity); + } + + @Test + @DisplayName("Should generate correct slot keys") + void shouldGenerateCorrectSlotKeys() { + LocalDate date = LocalDate.of(2026, 1, 15); + List slots = generator.generateDailySlots(date); + + assertEquals("2026-01-15_morning", slots.get(0).getSlotKey()); + assertEquals("2026-01-15_afternoon", slots.get(1).getSlotKey()); + assertEquals("2026-01-15_evening", slots.get(2).getSlotKey()); + } + + @Test + @DisplayName("Morning slot should have correct configuration") + void morningShouldHaveCorrectConfig() { + LocalDate date = LocalDate.of(2026, 1, 15); + List slots = generator.generateDailySlots(date); + TimeSlot morning = slots.get(0); + + assertEquals("08:00", morning.getStartTime()); + assertEquals(240, morning.getCapacity()); + assertTrue(morning.isMorning()); + assertFalse(morning.isAfternoon()); + assertFalse(morning.isEvening()); + } + + @Test + @DisplayName("Afternoon slot should have correct configuration") + void afternoonShouldHaveCorrectConfig() { + LocalDate date = LocalDate.of(2026, 1, 15); + List slots = generator.generateDailySlots(date); + TimeSlot afternoon = slots.get(1); + + assertEquals("14:00", afternoon.getStartTime()); + assertEquals(240, afternoon.getCapacity()); + assertFalse(afternoon.isMorning()); + assertTrue(afternoon.isAfternoon()); + assertFalse(afternoon.isEvening()); + } + + @Test + @DisplayName("Evening slot should have correct configuration") + void eveningShouldHaveCorrectConfig() { + LocalDate date = LocalDate.of(2026, 1, 15); + List slots = generator.generateDailySlots(date); + TimeSlot evening = slots.get(2); + + assertEquals("19:00", evening.getStartTime()); + assertEquals(180, evening.getCapacity()); + assertFalse(evening.isMorning()); + assertFalse(evening.isAfternoon()); + assertTrue(evening.isEvening()); + } +}