package agzam.vyatsu import agzam.vyatsu.data.remote.TeacherScheduleParser import agzam.vyatsu.domain.model.LessonType import java.time.LocalDate import java.time.LocalTime import org.junit.Assert.assertEquals import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Test class TeacherScheduleParserTest { private fun fixture(): String = checkNotNull(javaClass.getResourceAsStream("/prepod.html")) .readBytes().toString(Charsets.UTF_8) private fun fixture517(): String = checkNotNull(javaClass.getResourceAsStream("/prepod517.html")) .readBytes().toString(Charsets.UTF_8) private fun fixture974(): String = checkNotNull(javaClass.getResourceAsStream("/prepod974.html")) .readBytes().toString(Charsets.UTF_8) @Test fun teachersHeaderParsedInOrderAndDeduped() { val parsed = TeacherScheduleParser.parse(fixture()) assertEquals(listOf("Лелекова Е.В.", "Иванов И.И.", "Петрова А.С."), parsed.teachers) } @Test fun daysWithCorrectDates() { val parsed = TeacherScheduleParser.parse(fixture()) assertEquals(2, parsed.days.size) assertEquals(LocalDate.of(2026, 9, 14), parsed.days[0].date) assertEquals(LocalDate.of(2026, 9, 15), parsed.days[1].date) } @Test fun intervalTimesParsed() { val parsed = TeacherScheduleParser.parse(fixture()) val day0 = parsed.days[0] assertEquals(2, day0.intervals.size) assertEquals(LocalTime.of(8, 20), day0.intervals[0].start) assertEquals(LocalTime.of(9, 50), day0.intervals[0].end) assertEquals(LocalTime.of(10, 0), day0.intervals[1].start) assertEquals(LocalTime.of(11, 30), day0.intervals[1].end) } @Test fun multiGroupCellSplitsIntoPerGroupLessons() { val parsed = TeacherScheduleParser.parse(fixture()) val cell = parsed.days[0].intervals[0].lessons.filter { it.teacher == "Лелекова Е.В." } assertEquals(2, cell.size) val lesson0 = cell[0] val lesson1 = cell[1] assertEquals("Бб-1101-04-00", lesson0.group) assertNull(lesson0.subgroup) assertEquals("01 подгруппа", lesson1.subgroup) assertEquals("4-302", lesson0.room) assertEquals("Биоразнообразие", lesson0.subject) assertEquals(LessonType.LECTURE, lesson0.type) assertEquals("Лелекова Е.В.", lesson0.teacher) } @Test fun emptyCellContributesNoLesson() { val parsed = TeacherScheduleParser.parse(fixture()) val threeSlots = (0..6).map { slot -> parsed.days[0].intervals.firstOrNull { it.start.hour == 8 && it.start.minute == 20 } ?: return@map true } val petrovLessons = parsed.days[0].intervals[0].lessons.filter { it.teacher == "Петрова А.С." } assertEquals(0, petrovLessons.size) assertTrue(threeSlots.isEmpty() || threeSlots.size == 7) } @Test fun noRoomCellKeepsRawAndNullRoom() { val parsed = TeacherScheduleParser.parse(fixture()) val cell = parsed.days[0].intervals[1].lessons.filter { it.teacher == "Лелекова Е.В." } assertEquals(2, cell.size) assertNull(cell[0].room) assertTrue(cell[0].raw.contains("Бб-1101-04-00")) assertNull(cell[0].subject) } @Test fun exactTotalLessonCount() { val parsed = TeacherScheduleParser.parse(fixture()) val total = parsed.days.sumOf { day -> day.intervals.sumOf { it.lessons.size } } assertEquals(11, total) } private fun spacerColumnFixture(): String = """
ДеньИнтервал Марков Р.В.Мельцов В.Ю.
четверг 08.10.2608:20-09:50 2-206 Математика Практическое занятие УТб-2301-02-00 1-334 Организация параллельных вычислительных систем Лекция ИВТб-4301-05-00
10:00-11:30 2-206 Математика Практическое занятие УТб-2301-02-00 1-334 Организация параллельных вычислительных систем Лекция ИВТб-4301-05-00
""".trimIndent() @Test fun emptyHeaderSpacerDoesNotShiftTeacherColumns() { val parsed = TeacherScheduleParser.parse(spacerColumnFixture()) assertEquals(listOf("Марков Р.В.", "Мельцов В.Ю."), parsed.teachers) assertEquals(1, parsed.days.size) assertEquals(LocalDate.of(2026, 10, 8), parsed.days[0].date) assertEquals(2, parsed.days[0].intervals.size) for (slot in parsed.days[0].intervals) { val markov = slot.lessons.filter { it.teacher == "Марков Р.В." } val meltsov = slot.lessons.filter { it.teacher == "Мельцов В.Ю." } assertEquals(1, markov.size) assertEquals(1, meltsov.size) assertEquals("Математика", markov[0].subject) assertEquals("Организация параллельных вычислительных систем", meltsov[0].subject) } } @Test fun realPage517TeacherColumnsAligned() { val html = checkNotNull(javaClass.getResourceAsStream("/prepod517.html")) .readBytes().toString(Charsets.UTF_8) val parsed = TeacherScheduleParser.parse(html) assertTrue(parsed.teachers.contains("Мельцов В.Ю.")) val day = parsed.days.first { it.date == LocalDate.of(2026, 10, 8) } val all = day.intervals.flatMap { it.lessons } val markov = all.filter { it.teacher == "Марков Р.В." } val meltsov = all.filter { it.teacher == "Мельцов В.Ю." } assertTrue("Мельцов должен иметь пары 08.10", meltsov.isNotEmpty()) assertTrue( "У Мельцова не должно быть математики Маркова", meltsov.none { it.subject?.contains("Математика") == true } ) assertTrue( "У Маркова должна остаться математика", markov.any { it.subject?.contains("Математика") == true } ) assertTrue( "У Мельцова параллельные вычислительные системы", meltsov.any { it.subject?.contains("Организация параллельных") == true } ) } @Test fun realPage517MeltsovLabsSitAt1000And1145NotAt0820() { val parsed = TeacherScheduleParser.parse(fixture517()) val day = parsed.days.first { it.date == LocalDate.of(2026, 10, 6) } val labsAt = { time: java.time.LocalTime -> day.intervals.firstOrNull { it.start == time } ?.lessons ?.filter { it.teacher == "Мельцов В.Ю." && it.type == LessonType.LABORATORY } .orEmpty() } assertEquals( "В 08:20 у Мельцова на странице 517 лабораторных нет", 0, labsAt(java.time.LocalTime.of(8, 20)).size ) assertTrue(labsAt(java.time.LocalTime.of(10, 0)).isNotEmpty()) assertTrue(labsAt(java.time.LocalTime.of(11, 45)).isNotEmpty()) } @Test fun realPage974MeltsovHasPracticeAt0820() { val parsed = TeacherScheduleParser.parse(fixture974()) val day = parsed.days.first { it.date == LocalDate.of(2026, 10, 6) } val slot = day.intervals.first { it.start == java.time.LocalTime.of(8, 20) } val mel = slot.lessons.filter { it.teacher == "Мельцов В.Ю." } assertTrue(mel.isNotEmpty()) assertTrue("08:20 на странице 974 — практическое занятие", mel.all { it.type == LessonType.PRACTICE }) assertEquals("ИВТб-2305-06-00", mel.first().group) } }