Files
clickstream-ch-kafka-supers…/tests/test_generated_analytics_check_contract.py
T
ddadmin 9b0b063fed 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.
2026-07-05 22:46:53 +03:00

222 lines
8.3 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.
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():
"""Проверка берёт стык и профиль из manifest фактического прогона."""
script = (REPO_ROOT / "scripts" / "check_generated_analytics.sh").read_text(
encoding="utf-8"
)
makefile = (REPO_ROOT / "Makefile").read_text(encoding="utf-8")
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
def test_generated_history_check_does_not_skip_required_live_seam():
"""Обычная проверка требует реальные live-строки после стыка."""
script = (REPO_ROOT / "scripts" / "check_generated_analytics.sh").read_text(
encoding="utf-8"
)
makefile = (REPO_ROOT / "Makefile").read_text(encoding="utf-8")
assert 'CHECK_LIVE_SEAM="${CHECK_LIVE_SEAM:-1}"' in script
assert 'if [[ "${should_check_live_seam}" == "1" ]] && (( live_rows == 0 )); then' in script
assert "live-продолжение не записало строки" in script
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(
encoding="utf-8"
)
assert "CHECK_LIVE_SEAM=0" in script
assert script.index("CHECK_LIVE_SEAM=0") < script.index('bash "${SCRIPT_DIR}/check_generated_analytics.sh"')
def test_runtime_gate_has_bounded_daily_wave_live_seam_path():
"""Runtime gate issue 17 не использует полный daily-wave на 2 суток."""
makefile = (REPO_ROOT / "Makefile").read_text(encoding="utf-8")
script = (REPO_ROOT / "scripts" / "run_generated_history_runtime_check.sh").read_text(
encoding="utf-8"
)
assert "generated-history-runtime-check" in makefile
assert 'PROFILE="${PROFILE:-daily-wave}"' in script
assert 'GEN_HISTORY_DURATION="${GEN_HISTORY_DURATION:-1h}"' in script
assert 'LIVE_SECONDS="${LIVE_SECONDS:-25}"' in script
assert "stg_rows_after_live > stg_rows_before_live" in script
assert "GEN_RUN_MODE=live" in script
assert "GEN_STATE_RESET=false" in script
assert "up -d --build generator" in script
assert "CHECK_LIVE_SEAM=1" in script
assert "REQUIRE_SUPERSET=0" in script
def test_generated_history_check_rejects_empty_ods_context():
"""ODS-блок стыка падает, если в ODS нет строк для переходящих визитов."""
script = (REPO_ROOT / "scripts" / "check_generated_analytics.sh").read_text(
encoding="utf-8"
)
assert "ods_device_rows" in script
assert "ods_geo_rows" in script
assert "ODS device пустой на стыке" in script
assert "ODS geo пустой на стыке" in script