Files
schedule/tests/fixtures.py
Vladislav Kopnin cfa6a99639 Админка: секретный путь, смена ссылки, интервал и принудительное обновление
Панель без публичного входа: страница живёт по ADMIN_PATH, логин/пароль —
ADMIN_LOGIN/ADMIN_PASSWORD из .env (пусто = админка выключена и маршруты
/api/admin/* не регистрируются, прод можно не трогать).

Умеет: посмотреть текущую ссылку и состояние кэша, вставить новую ссылку
(проверяется скачиванием и разбором до сохранения, при ошибке откат),
принудительно обновить таблицу вне интервала и без проверки modified,
задать интервал в минутах (1..1440).

Реализация:
- backend/admin.py — cookie-сессия на HMAC (ключ = пароль, 12 часов,
  HttpOnly/SameSite/Secure-за-https), сравнение за постоянное время;
  compare_digest по байтам — кириллица в логине не роняет сервер;
- schedule/settings.py — ссылка и интервал в JSON (SETTINGS_FILE), атомарная
  запись, терпимость к битому/недоступному файлу (тогда живёт в памяти);
- schedule/cache.py — интервал вместо TTL, apply_link с откатом,
  force_reload, state() для панели; кэш без ссылки больше не роняет старт;
- frontend: страница admin.html (вне static, прямой /admin.html закрыт) и
  прокси /api/admin/* с телом и Set-Cookie;
- docker-compose: том schedule_data для настроек, ADMIN_* в окружении;
- deploy/: nginx-конфиг и инструкция для стенда dev.schedule.our-place.ru.

Тесты: 138 (новые — админ-API, сессии, ссылка/интервал, force, хранилище).
2026-09-28 12:06:25 +03:00

112 lines
4.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Общая тестовая таблица: 2 группы, 2 дня, мержи, скрытый столбец, красный текст."""
from xlsx_builder import SheetSpec, build_workbook
MAIN = "Бакалавры и магистры"
ROOMS = "аудитории"
TEACHERS = "Преподаватели"
# серии Excel
# недели ч: 08.31-09.06, 14.09-20.09, 28.09-04.10, 12.10-18.10
# недели з: 07.09-13.09, 21.09-27.09, 05.10-11.10, 19.10-25.10
W1 = (46265, 46271, 46272, 46278)
W2 = (46279, 46285, 46286, 46292)
W3 = (46293, 46299, 46300, 46306)
W4 = (46307, 46313, 46314, 46320)
def main_sheet() -> SheetSpec:
g1 = "ИВТ-11БО\nсеместр до 11.01.27"
g2 = "ИТ-12БО\nсеместр до 29.12.26"
hidden = "СКРЫ-11БО\nсеместр до 1.1.27"
math_red = [
("Математика\nСмирнов А.В.\nауд. 215\n", False),
("пара 14.09 не будет", True),
]
rows = [
# 0: заголовок
["числитель", "", "знаменатель", "", "день недели", "пара\nвремя",
g1, g2, hidden, None],
# 1: с/по
["с", "по", "с", "по", "день недели", "пара\nвремя", None, None, None, None],
# 2: понедельник, пара 1, ч
[*W1, "понедельник", "1\n9:00-\n10:35", math_red, "Физика\nИванов И.И.\nауд. 301"],
# 3: понедельник, пара 1, з
[*W2, "", "", "Математика\nСмирнов А.В.\nауд. 216", None],
# 4: понедельник, пара 2, ч
[*W3, "", "2\n10:45- 12:20", "к.в. - 2\nФизика\nИванов И.И.\nауд. 301",
"Химия\nПетров П.П.\nонлайн"],
# 5: понедельник, пара 2, з
[*W4, "", "", None, "к.в.\nХимия\nПетров П.П.\nауд. 201"],
# 6: вторник, пара 1, ч
[*W1, "вторник", "1\n9:00-\n10:35", "Биология\nСидорова С.С.\nауд. 220", None],
# 7: вторник, пара 1, з
[*W2, "", "", None, None],
]
return SheetSpec(
rows=rows,
merges=["F3:F4", "F5:F6", "F7:F8"],
hidden_cols=[(8, 8)],
)
def rooms_sheet() -> SheetSpec:
rows = [
[None, None, None, None, None, None, "215", "301", "220"],
[None, None, None, None, None, None, "комп.класс, 12 мест",
"интерактивная панель, 24 места", "20 мест"],
]
return SheetSpec(rows=rows)
def teachers_sheet() -> SheetSpec:
rows = [
["числитель", "числитель", "знаменатель", "знаменатель", "день недели", "пара\nвремя"],
[None, None, None, None, None, None,
"Смирнов\nАлександр Викторович", "Иванов\nИван Иванович"],
[*W1, "понедельник", "1\n9:00-\n10:35",
"Математика\nСмирнов А.В.\nауд. 215", "Физика\nИванов И.И.\nауд. 301"],
]
return SheetSpec(rows=rows)
def fixture_sheets() -> dict[str, SheetSpec]:
return {MAIN: main_sheet(), ROOMS: rooms_sheet(), TEACHERS: teachers_sheet()}
def fixture_xlsx() -> bytes:
return build_workbook(fixture_sheets())
def make_sheets(sheets: dict[str, SheetSpec]):
"""SheetSpec-фикстуры -> те же объекты, что парсит parse_workbook."""
from schedule.xlsx import parse_workbook
return parse_workbook(build_workbook(sheets))
def make_cache(monkeypatch, xlsx: bytes | None, modified: str = "fixed-modified", settings=None):
"""ScheduleCache с заменёнными сетевыми функциями."""
import schedule.cache as cache_mod
from schedule import ScheduleCache
calls = {"download": 0, "modified": 0}
fail = {"download": False, "modified": False}
def fake_download(url, timeout=60.0):
calls["download"] += 1
if fail["download"]:
from schedule import ScheduleError
raise ScheduleError("сетевой сбой (тест)")
return xlsx
def fake_modified(url, timeout=20.0):
calls["modified"] += 1
return None if fail["modified"] else modified
monkeypatch.setattr(cache_mod, "download_schedule", fake_download)
monkeypatch.setattr(cache_mod, "fetch_table_modified", fake_modified)
cache = ScheduleCache("test-link", ttl_seconds=3600, retry_after=60, settings=settings)
return cache, calls, fail