ci.yml (push) Invalid workflow file
- crm/tests: 27 тестов через настоящий server.main на sqlite-бэкенде (валидация заказов, цена только из каталога, honeypot, дедупликация, трекинг и отмена гостем, стоп-лист, аутентификация, гигиена текста, гейт расписания бара) - .gitea/workflows/ci.yml: test-джоба + deploy (только push в main, после тестов): rsync кода -> pre-flight docker compose config -> pg_dump бэкап БД (ротация 7) -> up -d --build -> health-check - деплой на neon-bar86.ru идёт деплой-ключом из secrets репозитория
74 lines
2.9 KiB
Python
74 lines
2.9 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""Чистые функции без сервера: гигиена гостевого текста и гейт «бар открыт»."""
|
||
import sys
|
||
from datetime import datetime, timedelta
|
||
from pathlib import Path
|
||
|
||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||
|
||
from server import BAR_TZ, bar_is_open, next_bar_opening # noqa: E402
|
||
from storage import clean_guest_text # noqa: E402
|
||
|
||
# 2026-10-05 — понедельник; от него строим любой день недели
|
||
MONDAY = datetime(2026, 10, 5, 12, 0, tzinfo=BAR_TZ)
|
||
|
||
|
||
def dt(day: str, hour: int, minute: int = 0) -> datetime:
|
||
shift = "mon tue wed thu fri sat sun".split().index(day)
|
||
return MONDAY.replace(hour=hour, minute=minute) + timedelta(days=shift)
|
||
|
||
|
||
# ------------------------------------------------------------- clean_guest_text
|
||
|
||
def test_clean_text_removes_hidden_chars():
|
||
assert clean_guest_text("Кола\u200b", 60) == "Кола" # zero-width space
|
||
assert clean_guest_text("\ufeffпиво\u200e", 60) == "пиво" # BOM + LRM
|
||
assert clean_guest_text("a\x00b\x1f[c]\x7f", 60) == "ab[c]" # control chars
|
||
assert clean_guest_text("ab\u202edc", 60) == "abdc" # bidi override
|
||
|
||
|
||
def test_clean_text_keeps_normal_text_and_emoji():
|
||
text = "Столик у окна, пиво 🍺 и — тире"
|
||
assert clean_guest_text(text, 60) == text
|
||
|
||
|
||
def test_clean_text_truncates_to_limit():
|
||
assert len(clean_guest_text("х" * 500, 60)) == 60
|
||
|
||
|
||
# ------------------------------------------------------------------ бар открыт?
|
||
|
||
def test_bar_open_in_the_evening():
|
||
assert bar_is_open(dt("fri", 21)) is True
|
||
assert bar_is_open(dt("sat", 21)) is True
|
||
assert bar_is_open(dt("sun", 20)) is True
|
||
|
||
|
||
def test_bar_closed_monday_evening():
|
||
# понедельник — вечерних сессий нет (BAR_CLOSED_WEEKDAY)
|
||
assert bar_is_open(dt("mon", 21)) is False
|
||
|
||
|
||
def test_bar_tail_after_midnight():
|
||
assert bar_is_open(dt("sat", 2)) is True # хвост пятницы до 03:00
|
||
assert bar_is_open(dt("sun", 2)) is True # хвост субботы до 03:00
|
||
assert bar_is_open(dt("tue", 0, 30)) is False # во вторник хвоста нет
|
||
assert bar_is_open(dt("sat", 3, 30)) is False # хвост закончился
|
||
|
||
|
||
def test_preorder_window_is_one_hour():
|
||
now = dt("fri", 18, 30)
|
||
opening = next_bar_opening(now)
|
||
assert opening is not None and (opening - now) <= timedelta(hours=1)
|
||
|
||
now2 = dt("fri", 17, 0)
|
||
opening2 = next_bar_opening(now2)
|
||
assert (opening2 - now2) > timedelta(hours=1)
|
||
|
||
|
||
def test_next_opening_skips_monday():
|
||
# воскресенье 21:00 → ближайшее открытие во вторник 19:00, не в понедельник
|
||
opening = next_bar_opening(dt("sun", 21))
|
||
assert opening is not None
|
||
assert opening.weekday() == 1 # вторник
|