fix(generator): усилены проверки startup-history
- Зачем: - коммит-гейт не запускал корневые контрактные тесты, а часть подтверждённых обходов могла снова смешать разные миры генератора. - Что: - добавлены цели make test, make lint и contract-test с тихим pytest-выводом через Docker. - закрыты обходы через generator-reset, неизвестную версию state и fail-open проверку DM-витрин. - усилены поведенческие контракты CHECK_LIVE_SEAM, профиля manifest и pause-check etl_pipeline; обновлены документы и issue 19. - Проверка: - make test; make lint; git diff --check.
This commit is contained in:
@@ -1,7 +1,110 @@
|
||||
from pathlib import Path
|
||||
import os
|
||||
import subprocess
|
||||
import textwrap
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
CHECK_SCRIPT = REPO_ROOT / "scripts" / "check_generated_analytics.sh"
|
||||
|
||||
|
||||
def _fake_compose(
|
||||
tmp_path,
|
||||
*,
|
||||
manifest_profile="ci",
|
||||
live_rows="3",
|
||||
views_mode="ok",
|
||||
):
|
||||
fake = tmp_path / "docker-compose"
|
||||
fake.write_text(
|
||||
textwrap.dedent(
|
||||
f"""\
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
args="$*"
|
||||
query=""
|
||||
previous=""
|
||||
for arg in "$@"; do
|
||||
if [[ "$previous" == "--query" ]]; then
|
||||
query="$arg"
|
||||
break
|
||||
fi
|
||||
previous="$arg"
|
||||
done
|
||||
|
||||
if [[ "$args" == *"kafka-manifest-summary"* ]]; then
|
||||
printf '%s\\n' '16054\t2800\t930\t2026-01-01T00:00:00+00:00\t2026-01-01T05:59:00+00:00\t2026-01-01T00:00:00+00:00\t2026-01-01T06:00:00+00:00\t{manifest_profile}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "$args" == *"clickhouse-client"* ]]; then
|
||||
if [[ "$query" == *"hex(sipHash128"* ]]; then
|
||||
printf '%s\\n' '16054\t2800\t930\t2026-01-01 00:00:00.000000\t2026-01-01 05:59:00.000000\t1\t1\t1\tdeadbeef'
|
||||
elif [[ "$query" == *"returning_users / users"* ]]; then
|
||||
printf '%s\\n' '930\t120\t0.129\t4'
|
||||
elif [[ "$query" == *"short_visit_share"* ]]; then
|
||||
printf '%s\\n' '2800\t0.2\t5\t5.7\t0\t120\t900\t18'
|
||||
elif [[ "$query" == *"minIf(event_ts"* ]]; then
|
||||
printf '%s\\n' '2800\t2100\t1500\t900\t650\t1\t0.23'
|
||||
elif [[ "$query" == *"has_home"* ]]; then
|
||||
printf '%s\\n' '2800\t2200\t1600\t950\t700\t1\t0.25'
|
||||
elif [[ "$query" == *"SELECT count()"* && "$query" == *"FROM dds.event"* ]]; then
|
||||
printf '%s\\n' '{live_rows}'
|
||||
elif [[ "$query" == *"uniqExact(event_id)"* ]]; then
|
||||
printf '%s\\n' '16200\t16200\t0'
|
||||
elif [[ "$query" == *"per_event_homogeneous_visits"* ]]; then
|
||||
printf '%s\\n' '19\t19\t19'
|
||||
elif [[ "$query" == *"ods_device_rows"* ]]; then
|
||||
printf '%s\\n' '19\t19\t19\t0\t0'
|
||||
elif [[ "$query" == *"SELECT source, rows"* ]]; then
|
||||
if [[ "{views_mode}" == "fail" ]]; then
|
||||
exit 42
|
||||
fi
|
||||
printf '%s\\n' \
|
||||
'dm.dq_summary\t1' \
|
||||
'dm.v_daily_traffic\t1' \
|
||||
'dm.v_events_enriched\t16054' \
|
||||
'dm.v_session_overview\t1' \
|
||||
'dm.v_top_pages_daily\t1' \
|
||||
'dm.v_utm_effectiveness\t1'
|
||||
else
|
||||
echo "unexpected ClickHouse query: $query" >&2
|
||||
exit 91
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "unexpected compose call: $args" >&2
|
||||
exit 92
|
||||
"""
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
fake.chmod(0o755)
|
||||
return fake
|
||||
|
||||
|
||||
def _run_generated_history_check(tmp_path, *, fake_compose, **env_overrides):
|
||||
env = os.environ.copy()
|
||||
env.update(
|
||||
{
|
||||
"COMPOSE_BIN": str(fake_compose),
|
||||
"REQUIRE_SUPERSET": "0",
|
||||
"WAIT_LIVE_ROWS_SECONDS": "0",
|
||||
}
|
||||
)
|
||||
env.update(env_overrides)
|
||||
return subprocess.run(
|
||||
["bash", str(CHECK_SCRIPT)],
|
||||
cwd=REPO_ROOT,
|
||||
env=env,
|
||||
text=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
timeout=30,
|
||||
check=False,
|
||||
)
|
||||
|
||||
|
||||
def test_generated_history_check_uses_actual_manifest_boundary_and_profile():
|
||||
@@ -14,6 +117,7 @@ def test_generated_history_check_uses_actual_manifest_boundary_and_profile():
|
||||
assert "kafka-manifest-summary" in script
|
||||
assert "manifest_model_t_end" in script
|
||||
assert "CH_MODEL_T_END=\"$(clickhouse_datetime_literal \"${manifest_model_t_end}\")\"" in script
|
||||
assert "PROFILE ?= ci" in makefile
|
||||
assert "GEN_LAUNCH_PROFILE" in makefile
|
||||
assert "PROFILE" in makefile
|
||||
|
||||
@@ -31,6 +135,51 @@ def test_generated_history_check_does_not_skip_required_live_seam():
|
||||
assert 'CHECK_LIVE_SEAM="$${CHECK_LIVE_SEAM:-1}"' in makefile
|
||||
|
||||
|
||||
def test_generated_history_check_rejects_manifest_profile_mismatch(tmp_path):
|
||||
"""Проверка падает, если ожидаемый профиль не совпал с manifest."""
|
||||
fake_compose = _fake_compose(tmp_path, manifest_profile="ci")
|
||||
|
||||
result = _run_generated_history_check(
|
||||
tmp_path,
|
||||
fake_compose=fake_compose,
|
||||
PROFILE="daily-wave",
|
||||
CHECK_LIVE_SEAM="0",
|
||||
)
|
||||
|
||||
assert result.returncode != 0
|
||||
assert "профиля daily-wave, но manifest от ci" in result.stderr
|
||||
|
||||
|
||||
def test_generated_history_check_requires_live_rows_when_seam_is_required(tmp_path):
|
||||
"""CHECK_LIVE_SEAM=1 падает, если live-продолжение не записало строк."""
|
||||
fake_compose = _fake_compose(tmp_path, live_rows="0")
|
||||
|
||||
result = _run_generated_history_check(
|
||||
tmp_path,
|
||||
fake_compose=fake_compose,
|
||||
PROFILE="ci",
|
||||
CHECK_LIVE_SEAM="1",
|
||||
)
|
||||
|
||||
assert result.returncode != 0
|
||||
assert "live-продолжение не записало строки" in result.stderr
|
||||
|
||||
|
||||
def test_generated_history_check_fails_when_dm_views_query_fails(tmp_path):
|
||||
"""Ошибка запроса DM-витрин не превращается в пустой успешный цикл."""
|
||||
fake_compose = _fake_compose(tmp_path, views_mode="fail")
|
||||
|
||||
result = _run_generated_history_check(
|
||||
tmp_path,
|
||||
fake_compose=fake_compose,
|
||||
PROFILE="ci",
|
||||
CHECK_LIVE_SEAM="0",
|
||||
)
|
||||
|
||||
assert result.returncode != 0
|
||||
assert "не удалось прочитать основные DM-витрины" in result.stderr
|
||||
|
||||
|
||||
def test_clean_generated_history_run_explicitly_skips_live_seam():
|
||||
"""Чистый backfill без live-продолжения отключает проверку стыка явно."""
|
||||
script = (REPO_ROOT / "scripts" / "run_generated_history_analytics.sh").read_text(
|
||||
|
||||
Reference in New Issue
Block a user