- tests/: 104 теста — lessons (все форматы ячеек), xlsx (мержи/скрытые столбцы/красный текст/даты), parsing (группы/недели/индексы), API (TestClient, TTL/modified/stale-while-error, 404/400/502) - xlsx_builder.py: генерация xlsx в памяти для тестов без openpyxl - docker-compose: healthz-хелскчеки (не блокируют старт), SCHEDULE_LINK - Makefile: target test - requirements: закреплённые версии - .gitignore: __pycache__/, .venv/ и т.д.
350 lines
15 KiB
Python
350 lines
15 KiB
Python
"""Тесты разбора ячеек занятий: каждый найденный в таблице формат."""
|
||
|
||
import pytest
|
||
|
||
from schedule.lessons import Lesson, parse_lesson_cell
|
||
|
||
R = "\u27EA" # ⟨
|
||
Cc = "\u27EB" # ⟩
|
||
|
||
|
||
def lessons(text):
|
||
return [l.to_dict() for l in parse_lesson_cell(text)]
|
||
|
||
|
||
def one(text):
|
||
ls = parse_lesson_cell(text)
|
||
assert len(ls) == 1, f"ожидалося 1 занятие, got: {ls}"
|
||
return ls[0].to_dict()
|
||
|
||
|
||
# --- базовые формы -------------------------------------------------------------
|
||
|
||
|
||
def test_simple_three_lines():
|
||
assert one("Математика\nСмирнов А.В.\nауд. 215") == {
|
||
"marker": "", "subject": "Математика", "teacher": "Смирнов А.В.",
|
||
"room": "ауд. 215", "note": "",
|
||
}
|
||
|
||
|
||
def test_two_lines_no_room():
|
||
assert one("ТВИМС\nКоролев Г.К") == {
|
||
"marker": "", "subject": "ТВИМС", "teacher": "Королев Г.К", "room": "", "note": "",
|
||
}
|
||
|
||
|
||
def test_single_word_subject():
|
||
assert one("ДАСП") == {
|
||
"marker": "", "subject": "ДАСП", "teacher": "", "room": "", "note": "",
|
||
}
|
||
|
||
|
||
def test_acronym_subject_not_org():
|
||
for subject in ("МАИС", "БЖД", "ООП", "ЯПМТ", "ТВиМС", "ФТД"):
|
||
result = one(f"{subject}\nГладков А.Н.\nауд. 207")
|
||
assert result["subject"] == subject, subject
|
||
assert result["teacher"] == "Гладков А.Н."
|
||
|
||
|
||
def test_multiple_teachers_slash():
|
||
result = one("Иностранный язык\nМастакова Н.К./Умнова И.В./Чвягина Т.В.\nауд. 305 / 207")
|
||
assert result["teacher"] == "Мастакова Н.К./Умнова И.В./Чвягина Т.В."
|
||
assert result["room"] == "ауд. 305 / 207"
|
||
|
||
|
||
def test_subject_lines_join():
|
||
result = one("Методы компьютерной лингвистики\nв ИИ - 2\nПарамонов И.В.\nауд. 231")
|
||
assert result["subject"] == "Методы компьютерной лингвистики в ИИ - 2"
|
||
assert result["teacher"] == "Парамонов И.В."
|
||
assert result["room"] == "ауд. 231"
|
||
|
||
|
||
# --- маркеры -------------------------------------------------------------------
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
"raw, expected",
|
||
[
|
||
("к.в. - 2\nФизика\nИванов И.И.\nауд. 301", "к.в. - 2"),
|
||
("к.в.-7\nСети Петри\nБашкин В.А.\nауд. 224", "к.в. - 7"),
|
||
("к.в.\nХимия\nПетров П.П.\nауд. 201", "к.в."),
|
||
("к.в. - 1 \nОсновы тестирования\nОкопный Марк Олегович\nонлайн", "к.в. - 1"),
|
||
("к.в.-2 Распределенные технологии\nКорсаков С.В.\nауд. 201", "к.в. - 2"),
|
||
],
|
||
)
|
||
def test_markers(raw, expected):
|
||
assert one(raw)["marker"] == expected
|
||
|
||
|
||
def test_marker_alone_line_then_blank():
|
||
ls = lessons("к.в. \n\nОсновы веб-технологий\nЛавровский В.А.\nауд. 210")
|
||
assert len(ls) == 1
|
||
assert ls[0]["marker"] == "к.в."
|
||
assert ls[0]["subject"] == "Основы веб-технологий"
|
||
|
||
|
||
def test_marker_inherited():
|
||
ls = lessons("к.в. - 3\nA\nБелов А.А.\nауд. 1\n\nB\nБелов А.А.\nауд. 2")
|
||
assert [l["marker"] for l in ls] == ["к.в. - 3", "к.в. - 3"]
|
||
|
||
|
||
# --- аудитории и локации ---------------------------------------------------------
|
||
|
||
|
||
def test_room_normalization():
|
||
result = one("Математика\nСмирнов А.В.\nауд312")
|
||
assert result["room"] == "ауд. 312"
|
||
|
||
|
||
def test_room_with_building():
|
||
result = one("Основы права (л)\nКолпазанова Е.В.\nюридический корпус, 3 этаж ауд. 22\nначало в 16:05")
|
||
assert result["subject"] == "Основы права (л)"
|
||
assert result["teacher"] == "Колпазанова Е.В."
|
||
assert result["room"] == "юридический корпус, 3 этаж ауд. 22"
|
||
assert result["note"] == "начало в 16:05"
|
||
|
||
|
||
def test_room_with_time_after():
|
||
result = one("Бизнес-аналитика\nЕлин А.С.\nонлайн\nс 17:00 до 20:00\nс 8.09.26")
|
||
assert result["room"] == "онлайн"
|
||
assert "с 17:00 до 20:00" in result["note"]
|
||
assert "с 8.09.26" in result["note"]
|
||
|
||
|
||
def test_room_online_with_time_inside():
|
||
result = one("Облачные сервисы\nБегунов А.В.\nонлайн (начало в 14:30)")
|
||
assert result["room"] == "онлайн"
|
||
assert result["note"] == "начало в 14:30"
|
||
|
||
|
||
def test_room_url():
|
||
result = one("Физкультура (онлайн)\nhttps://next.uniyar.ac.ru/course/view.php?id=407")
|
||
assert result["subject"] == "Физкультура (онлайн)"
|
||
assert result["room"].startswith("https://next.uniyar.ac.ru")
|
||
assert result["teacher"] == ""
|
||
|
||
|
||
def test_location_instead_of_room():
|
||
result = one("Прикладная физическая культура\nТкач О.С.\nпервый этаж, восточное крыло")
|
||
assert result["room"] == "первый этаж, восточное крыло"
|
||
assert result["note"] == ""
|
||
|
||
|
||
def test_room_paren_note():
|
||
result = one("Базы данных\nПолетаев А.Ю.\nауд. 223 (возможно и 221)")
|
||
assert result["room"] == "ауд. 223"
|
||
assert result["note"] == "возможно и 221"
|
||
|
||
|
||
def test_room_paren_alternative_kept():
|
||
result = one("Сетевые технологии Huawei\nКорсаков С.В.\nауд.201 (221)")
|
||
assert result["room"] == "ауд. 201 (221)"
|
||
assert result["note"] == ""
|
||
|
||
|
||
# --- преподаватели ---------------------------------------------------------------
|
||
|
||
|
||
def test_full_name_teacher():
|
||
result = one("Основы тестирования\nОкопный Марк Олегович (БИФИТ)\nонлайн\nначало в 12:00")
|
||
assert result["teacher"] == "Окопный Марк Олегович (БИФИТ)"
|
||
assert result["room"] == "онлайн"
|
||
assert result["note"] == "начало в 12:00"
|
||
|
||
|
||
def test_full_name_with_company():
|
||
result = one("Проектный практикум\nЛабеев Игорь Владимирович (Корус консалтинг)\nначало занятия в 18:30 онлайн\nссылка в эл. университете")
|
||
assert result["teacher"] == "Лабеев Игорь Владимирович (Корус консалтинг)"
|
||
assert result["room"] == "онлайн"
|
||
assert "начало занятия в 18:30" in result["note"]
|
||
assert "ссылка в эл. университете" in result["note"]
|
||
|
||
|
||
def test_teacher_with_substitute():
|
||
result = one("Параллельное программирование\nМещеряков И.А. (Федулов Д.Д.) (начало пары в 14:05)\nауд.213")
|
||
assert result["teacher"] == "Мещеряков И.А. (Федулов Д.Д.)"
|
||
assert result["room"] == "ауд. 213"
|
||
assert result["note"] == "начало пары в 14:05"
|
||
|
||
|
||
def test_teacher_lecture_marker_kept():
|
||
result = one("Теория автоматов\nСмирнов А.В. (л)\nауд.312")
|
||
assert result["teacher"] == "Смирнов А.В. (л)"
|
||
|
||
|
||
def test_org_as_teacher():
|
||
result = one("Управление ИТ-проектами\nПравоТех\nауд. 309")
|
||
assert result["teacher"] == "ПравоТех"
|
||
assert result["subject"] == "Управление ИТ-проектами"
|
||
|
||
|
||
def test_single_word_org_in_teacher_position():
|
||
result = one("Оценка качества ПО\nХинт")
|
||
assert result["teacher"] == "Хинт"
|
||
assert result["subject"] == "Оценка качества ПО"
|
||
|
||
|
||
def test_org_as_location():
|
||
ls = lessons(
|
||
"Сети Петри\nБашкин В.А.\nауд. 224\n\nСовременные инструменты промышленной разработки\nКоновал О.В.\nНПО Криста"
|
||
)
|
||
second = ls[1]
|
||
assert second["teacher"] == "Коновал О.В."
|
||
assert second["room"] == "НПО Криста"
|
||
|
||
|
||
def test_teacher_and_room_on_one_line():
|
||
result = one("Логические контроллеры\nКузьмин Е.В.ауд. 301 (начало в 12:30)")
|
||
assert result["teacher"] == "Кузьмин Е.В."
|
||
assert result["room"] == "ауд. 301"
|
||
assert result["note"] == "начало в 12:30"
|
||
|
||
|
||
def test_initials_without_space():
|
||
result = one("Методы сжатия КрасновМ.В.\nауд.231")
|
||
assert result["subject"] == "Методы сжатия"
|
||
assert result["teacher"] == "КрасновМ.В."
|
||
assert result["room"] == "ауд. 231"
|
||
|
||
|
||
def test_inline_subject_teacher_room():
|
||
result = one("Сети Петри Башкин В.А. ауд. 224")
|
||
assert result["subject"] == "Сети Петри"
|
||
assert result["teacher"] == "Башкин В.А."
|
||
assert result["room"] == "ауд. 224"
|
||
|
||
|
||
# --- заметки ---------------------------------------------------------------------
|
||
|
||
|
||
def test_red_note():
|
||
assert one(f"Математика\nСмирнов А.В.\nауд. 215\n{R}пара 14.09 не будет{Cc}")["note"] == "пара 14.09 не будет"
|
||
|
||
|
||
def test_red_note_on_room_line():
|
||
result = one(f"Русский язык\nШаманова М.В.\nауд.312 {R}с 7.09 по 20.09 пары не будет{Cc}")
|
||
assert result["room"] == "ауд. 312"
|
||
assert result["note"] == "с 7.09 по 20.09 пары не будет"
|
||
|
||
|
||
def test_red_note_multiline_with_embedded_lesson():
|
||
raw = (
|
||
"Теория автоматов\nСмирнов А.В. (л)\nауд.312\n"
|
||
f"{R}занятия начнутся с 21.09\nИностранный язык\nБугрова Е.И.\n"
|
||
f"ауд. 312 у ИТ-22БО 7.09 и 14.09 с 12:30{Cc}"
|
||
)
|
||
result = one(raw)
|
||
assert result["subject"] == "Теория автоматов"
|
||
assert result["teacher"] == "Смирнов А.В. (л)"
|
||
assert result["room"] == "ауд.312" or result["room"] == "ауд. 312"
|
||
assert "занятия начнутся с 21.09" in result["note"]
|
||
assert "Бугрова Е.И." in result["note"]
|
||
|
||
|
||
def test_red_note_between_teacher_and_room():
|
||
raw = f"Математика\nСмирнов А.В.\n{R}(пара 8.09 не будет)\n{Cc}ауд. 203"
|
||
result = one(raw)
|
||
assert result["teacher"] == "Смирнов А.В."
|
||
assert result["room"] == "ауд. 203"
|
||
assert result["note"] == "пара 8.09 не будет"
|
||
|
||
|
||
def test_only_red_text_gives_lesson_with_note():
|
||
result = one(f"{R}нет занятий{Cc}")
|
||
assert result["note"] == "нет занятий"
|
||
|
||
|
||
def test_date_note_not_room():
|
||
result = one("Практика в НПО Криста\nс 9.09.2026")
|
||
assert result["subject"] == "Практика в НПО Криста"
|
||
assert result["room"] == ""
|
||
assert result["note"] == "с 9.09.2026"
|
||
|
||
|
||
# --- несколько занятий в одной ячейке ---------------------------------------------
|
||
|
||
|
||
def test_two_lessons_blank_line():
|
||
ls = lessons("Сети Петри\nБашкин В.А.\nауд. 224\n\nСовременные инструменты\nКоновал О.В.\nауд. 301")
|
||
assert [l["subject"] for l in ls] == ["Сети Петри", "Современные инструменты"]
|
||
assert [l["room"] for l in ls] == ["ауд. 224", "ауд. 301"]
|
||
|
||
|
||
def test_two_lessons_room_subject_border():
|
||
ls = lessons("Логические контроллеры\nКузьмин Е.В.ауд. 301 (начало в 12:30)\nЧисленные методы\nГлызин С.Д. ауд. 312")
|
||
assert [l["subject"] for l in ls] == ["Логические контроллеры", "Численные методы"]
|
||
assert [l["teacher"] for l in ls] == ["Кузьмин Е.В.", "Глызин С.Д."]
|
||
assert [l["room"] for l in ls] == ["ауд. 301", "ауд. 312"]
|
||
|
||
|
||
def test_group_tag_after_room_is_note():
|
||
result = one("Алгебра и геометрия\nСедов А.Г.\nауд.219\nИТ-11")
|
||
assert result["room"] == "ауд. 219"
|
||
assert result["note"] == "ИТ-11"
|
||
|
||
|
||
def test_lesson_with_red_between_lessons():
|
||
ls = lessons(
|
||
"Введение в промышленную разработку\nПарамонов И.В.\n"
|
||
f"ауд. 220 {R}2.09 пара отменяется{Cc}\n\n"
|
||
"Язык UML и CASE-системы\nЛарина Ю.А.\nауд.210"
|
||
)
|
||
assert ls[0]["note"] == "2.09 пара отменяется"
|
||
assert ls[0]["room"] == "ауд. 220"
|
||
assert ls[1]["subject"] == "Язык UML и CASE-системы"
|
||
|
||
|
||
# --- пустые и экзотические ----------------------------------------------------------
|
||
|
||
|
||
def test_empty_cell():
|
||
assert parse_lesson_cell("") == []
|
||
assert parse_lesson_cell(" \n ") == []
|
||
|
||
|
||
def test_cell_with_underscores_subject():
|
||
lesson = one("Промышленное программирование\nПарамонов И.В.\nауд. 220")
|
||
assert lesson["subject"] == "Промышленное программирование"
|
||
|
||
|
||
def test_lesson_subject_only():
|
||
result = one(f"{R}не будет{Cc}")
|
||
assert result["subject"] == ""
|
||
assert result["note"] == "не будет"
|
||
|
||
|
||
def test_unclosed_paren_in_table():
|
||
# в таблице не закрыли скобку вокруг красной заметки
|
||
result = one("Основы веб-технологий\nЛавровский В.А. (" + R + "предмет начнется с 8.09)" + Cc + "\nауд. 210")
|
||
assert result["teacher"] == "Лавровский В.А."
|
||
assert result["room"] == "ауд. 210"
|
||
assert result["note"] == "предмет начнется с 8.09"
|
||
|
||
|
||
def test_inline_lesson_after_room_no_blank_line():
|
||
raw = (
|
||
"к.в. - 2\nАлгоритмы и структуры данных Волченков С.Г. "
|
||
+ R + "(14.09 по 30.09 пары нет) " + Cc
|
||
+ "ауд.219 \nМетоды сжатия КрасновМ.В. ауд.231"
|
||
)
|
||
ls = lessons(raw)
|
||
assert len(ls) == 2
|
||
assert ls[0]["room"] == "ауд. 219"
|
||
assert "14.09 по 30.09 пары нет" in ls[0]["note"]
|
||
assert ls[1]["subject"] == "Методы сжатия"
|
||
assert ls[1]["teacher"] == "КрасновМ.В."
|
||
assert ls[1]["room"] == "ауд. 231"
|
||
|
||
|
||
def test_plain_time_paren_line():
|
||
result = one("Параллельное программирование (пр)\nМещеряков И.А.\nауд. 213\n(начало пары в 12:30)")
|
||
assert result["note"] == "начало пары в 12:30"
|
||
|
||
|
||
def test_full_name_inline_after_subject():
|
||
result = one("Основы тестирования веб-приложений Денис Верховцев (Сигма)\nонлайн")
|
||
assert result["subject"] == "Основы тестирования веб-приложений"
|
||
assert result["teacher"] == "Денис Верховцев (Сигма)"
|
||
assert result["room"] == "онлайн"
|