feat(stand): make up наполняет стенд стартовым миром, опись сторожит его
Зачем Стенд поднимался пустым, и всякая приёмка следующих этапов начиналась с ручной заливки данных. Теперь `make up` сам приводит стенд к одному и тому же состоянию, а в git лежит то, чем это состояние проверяется. Что - Опись мира `data/world-inventory.json`: паспорт (зерно, версия генератора, хеш каталога) и по строке на каждый из восьми дней — дата, число событий, хеш байтов. Собирается `make inventory`, свежесть сторожит `test_inventory.py` — тем же способом, что свежесть описания выгрузки. - Разовая служба `world-init` вышла из-под профиля и играет в топик восемь дней при каждом подъёме; зависимый у неё — `airflow-init`, иначе `--wait` считает успешно отработавшую службу упавшей. - `scripts/wait-for-world.sh` — вторая половина `make up`: приём асинхронный, поэтому ждать надо доезда до `ods.event`, а не завершения заливки. Ограниченный цикл опроса, не пауза наугад. - Девятая проверка `make check-clickhouse`: подневный счёт событий против описи, рамка по датам стартового мира, счёт через `FINAL`. При расхождении называет, где искать, — в событиях или в браке. - Порог «день ≤ 30 с» снят из спеки генератора в обоих местах: замер дал 1,7 с, порог был выше факта в восемнадцать раз. На его месте — замеры с датой. Раздел 9 спеки закрыт: открытых вопросов не осталось. - Слова: «манифест» стал описью мира, «зерновой мир» — стартовым миром (решение владельца). Оба заведены в словарь CONTEXT.md. Проверка `make clean && make up` с нуля — 2 м 50 с, доехало ровно 401 185 событий. `make check-clickhouse` зелёный (8 с), `make smoke` зелёный (9 с), `make test` — 407 тестов за 71 с, `make lint`, `make typecheck`, `make config-test` зелёные. Что проверка умеет краснеть, снято двумя поломками: снос партиции 2026-06-03 дал диагноз «не доехали до ODS», негодная строка в сырье — «сломан разбор». Строки опыта убраны, день переигран, счёт вернулся. Тест свежести проверен молчаливой правкой цены в каталоге: покраснел. Ссылка: #42
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
"""Проверка описи мира: та ли она, что собирается из кода сегодня.
|
||||
|
||||
Опись собирается из кода, значит разойтись они могут только одним способом —
|
||||
код правили, опись не пересобрали. Ровно это здесь и сторожится, тем же
|
||||
способом, что свежесть «описания выгрузки».
|
||||
|
||||
Проверка дорогая — она пересчитывает восемь модельных дней целиком, и это
|
||||
единственный способ сравнить хеши: дешевле мир не пересобрать. Зато краснеет
|
||||
она там, где надо, — сразу после правки генератора, а не через полчаса на
|
||||
поднятом стенде, где расхождение счётчиков выглядит поломкой хранилища.
|
||||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from clickstream_generator.inventory import build
|
||||
|
||||
INVENTORY_PATH = Path(__file__).resolve().parents[2] / "data" / "world-inventory.json"
|
||||
|
||||
|
||||
def test_inventory_is_up_to_date():
|
||||
stored = json.loads(INVENTORY_PATH.read_text(encoding="utf-8"))
|
||||
assert stored == build(), (
|
||||
"опись мира отстала от кода — пересоберите: make inventory."
|
||||
" Разошлись хеши дней и каталога — правили data/catalog/products.csv;"
|
||||
" разошлись только дни — правили генератор"
|
||||
)
|
||||
@@ -98,7 +98,7 @@ def _run_apart(path) -> None:
|
||||
def test_days_play_in_a_row(tmp_path, monkeypatch):
|
||||
"""Дни идут подряд от названного, а пачка считается на весь прогон.
|
||||
|
||||
Восемь дней одним запуском — то, чем зальётся зерновой мир (#42), поэтому
|
||||
Восемь дней одним запуском — то, чем заливается стартовый мир, поэтому
|
||||
порядок дней проверяется, а не предполагается. День здесь подменён коротким:
|
||||
проверяется ход проигрывателя, а не содержимое дня, и платить за полсотни
|
||||
тысяч событий трижды незачем.
|
||||
|
||||
@@ -28,7 +28,7 @@ def test_every_event_carries_every_column(events):
|
||||
|
||||
«Пусто» по контракту — пустое значение, а не отсутствие ключа: пропавший
|
||||
ключ уводит событие в брак целиком (ADR 0005). Порядок ключей — часть
|
||||
канона: от него зависят байты, а значит и хеши манифеста.
|
||||
канона: от него зависят байты, а значит и хеши описи.
|
||||
"""
|
||||
names = [column.name for column in schema.COLUMNS]
|
||||
for event in events:
|
||||
|
||||
Reference in New Issue
Block a user