- 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 репозитория
This commit is contained in:
1 parent
da5b259d06
commit
881ef45be5
8 files changed
+469
No files matched your search
@@ -0,0 +1,73 @@
|
||||
# -*- 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 # вторник
|
||||
Reference in new issue
Block a user