All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / changes (pull_request) Successful in 11s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 4m41s
Правка меняет две вещи разом, обе намеренно. 1. Равенство по дате -> «последняя строка не позже якоря». listing_source_snapshots переходит на модель «строка на изменение». В ней equality-join теряет 95.6% пар (на 2026-08-20 изменениями являются 4458 строк из 101795): выборка для percentile_disc схлопывается до n=3-4, квантиль вырождается в максимум из трёх чисел, TTL обваливается — domklik 28->14 (под снятие сразу 128 активных строк), yandex 44->30, avito 13->10. Дыры в суточной истории ломали equality-join и до перехода: на проде 03-14.06 (12 суток подряд), 03-04.07, 12.07, 26.07, 30-31.07, 01.08 — там n_pairs=0, floor_days=NULL и TTL молча оставался как задан. 2. Глобальный max(snapshot_date) -> максимум внутри источника. Старый подзапрос брал максимум по ВСЕЙ таблице, не скоупленный по listing_source_id: источник, чья история короче общей, выпадал из выборки целиком. LATERAL ищет предшественника по строке. Замер на живом проде 2026-08-23 (health_window_days=3): обе формы дают побитово одинаковый результат на всех четырёх источниках — avito n=765 пол=49.7, cian n=1226 пол=81.6, domklik n=565 пол=35.7, yandex n=3856 пол=87.3. Сегодня суточная джоба пишет строку для каждого источника каждый день, поэтому глобальный максимум совпадает с максимумом каждого источника, и пункт 2 — no-op на текущих данных. Расхождение проявится только на дырах и после перехода на change-only. Плюс floor_n_pairs в counters — наблюдательность для будущего гейта деградации пола, сейчас ничего не блокирует. FROM..WHERE вынесен в _revisit_floor_from_where_sql, чтобы count(*) и percentile_disc гарантированно шли по одному срезу.
120 lines
11 KiB
Text
120 lines
11 KiB
Text
# Объявленные пропуски сьюта tradein-mvp/backend.
|
||
#
|
||
# Пропуск, которого здесь нет, роняет прогон (хук в tests/conftest.py). Смысл: не
|
||
# запретить пропуски, а запретить НЕЗАМЕТНЫЕ. Проверка, которая тихо перестала
|
||
# исполняться, со временем расходится с кодом, и узнают об этом ровно тогда,
|
||
# когда на неё понадобилось опереться (#2722, #2729, #2740).
|
||
#
|
||
# Формат: один nodeid в строке, без «[параметров]»; `#` — комментарий.
|
||
# Прежде чем добавить строку, ответь: почему эту проверку нельзя выполнить ЗДЕСЬ
|
||
# и где она выполняется вместо этого. Нет ответа — это не пропуск, а дыра.
|
||
#
|
||
# Список — НАДмножество сред: в CI Postgres есть (ci-tradein.yml поднимает
|
||
# сервис-контейнер и собирает схему из backend/data/sql/), на ноутбуке без БД —
|
||
# нет. Запись, чей пропуск не случился, безвредна; пропуск без записи — нет.
|
||
|
||
# ── Реальный рендер PDF: нужны native-либы WeasyPrint (Pango/cairo/GObject) ────
|
||
# Где выполняется: внутри prod-образа — runner-stage tradein-mvp/backend/Dockerfile
|
||
# ставит libcairo2 + libpango-1.0-0 + libpangoft2-1.0-0:
|
||
# docker exec tradein-backend python -m pytest -q -m pdf_render tests/test_pdf_real_render.py
|
||
# На голом ubuntu-latest в ci-tradein.yml этих либ нет (apt-шага в job'е нет).
|
||
tests/test_pdf_real_render.py
|
||
|
||
# ── Тесты против живой Postgres ───────────────────────────────────────────────
|
||
# В CI они ИДУТ (postgres-сервис в ci-tradein.yml, #2745) — записи ниже нужны
|
||
# только для прогона на машине без БД. Именно эта группа разъехалась со схемой,
|
||
# пока не бежала нигде: #2740 (houses.url стал NOT NULL) и #2744
|
||
# (test_gar_flats_loader падал до первого утверждения). Поднять локально:
|
||
# docker run -d -p 5432:5432 -e POSTGRES_DB=tradein -e POSTGRES_USER=tradein \
|
||
# -e POSTGRES_PASSWORD=tradein postgis/postgis:16-3.4
|
||
# psql ... -f backend/data/sql/*.sql # порядок как в deploy-tradein.yml
|
||
tests/tasks/test_backfill_house_coords_from_listings.py::test_real_transfers_agreeing_coords_skips_scattered_and_is_idempotent
|
||
tests/tasks/test_cadastral_geo_match.py::test_real_knn_nearest_within_threshold_picked
|
||
tests/test_audit_api.py::test_real_accounts_and_analytics_aggregate_inserted_rows
|
||
tests/test_gar_flats_loader.py::test_upsert_and_canon_match_populates_gar_flat_count
|
||
tests/test_house_dedup_merge.py::test_real_canon_clusterkey_and_geo_guard_merge_semantics
|
||
tests/test_house_dedup_merge.py::test_real_fias_pass_cross_guard_and_identity_carryover
|
||
tests/test_house_dedup_merge.py::test_real_fias_pass_ignores_geo_guard
|
||
tests/test_house_dedup_merge.py::test_real_merge_is_reversible_via_journal
|
||
tests/test_house_dedup_merge.py::test_real_merge_repoints_dedups_deletes_and_is_idempotent
|
||
tests/test_user_events.py::test_real_record_event_inserts_row
|
||
|
||
# Приватность/ретеншн (#2547) — тот же `_live_session()`. Приехали в main
|
||
# параллельно с самим списком, поэтому первым же прогоном deploy-лэйна хук их и
|
||
# поймал: 4 проверки не исполнились и не были объявлены. Проверено против живой
|
||
# БД (схема из backend/data/sql/ по 233 включительно) — 29 passed, тесты живы.
|
||
tests/test_data_erasure.py::test_real_erase_by_phone_does_not_match_different_number
|
||
tests/test_data_erasure.py::test_real_erase_by_phone_finds_differently_formatted_number
|
||
tests/test_data_erasure.py::test_real_erase_by_phone_finds_ru_trunk_prefix_variant
|
||
tests/test_purge_expired_trade_in_data.py::test_real_purge_deletes_only_anonymous_expired_estimates
|
||
|
||
# Payments retention (PR #2754), deep-review MEDIUM finding 2026-08-06 — тот же
|
||
# `_live_session()`. Проверяют предполётную проверку purge против реального
|
||
# retain_until/payments состояния (здоровая оплаченная строка не поднимает
|
||
# тревогу и не блокирует джобу), не только SQL-текст.
|
||
tests/test_purge_expired_trade_in_data.py::test_real_preflight_ignores_healthy_paid_row_flags_only_anomaly
|
||
tests/test_purge_expired_trade_in_data.py::test_real_purge_not_wedged_by_healthy_paid_row
|
||
|
||
# Диагноз оборванного прогона (#2764) — тот же `_live_session()`. Проверяет, что
|
||
# дефолт mark_banned ('unknown') проходит CHECK-констрейнт из миграции 234:
|
||
# на мок-лэйне (deploy-tradein.yml, DSN-заглушка) констрейнта нет вовсе.
|
||
tests/test_2764_ban_kind_no_default.py::test_real_default_ban_kind_survives_the_check_constraint
|
||
|
||
# Гейт номеров миграций (#2683) сверяется с origin/main и точкой ветвления. Где
|
||
# git-эталона нет — прогон внутри prod-образа, экспорт исходников без .git —
|
||
# проверять не с чем, и тест это ГОВОРИТ вслух вместо тихого зелёного.
|
||
# В CI пропуска не бывает: при CI/GITHUB_ACTIONS та же ветка делает pytest.fail
|
||
# (отсутствие эталона в пайплайне — сломанный гейт, а не «нечего проверять»),
|
||
# а ci-tradein.yml/deploy-tradein.yml берут checkout с fetch-depth: 0 — при нём
|
||
# checkout сам приносит refs/remotes/origin/*, отдельный git fetch не нужен и
|
||
# из job-контейнера всё равно не проходит (run 6977, connection refused).
|
||
tests/test_migration_numbering.py::test_applied_migration_is_not_renamed_or_deleted
|
||
tests/test_migration_numbering.py::test_new_migration_takes_a_free_number
|
||
# Повтор застрявших transient_error (#2674, PR #2843) — тот же `_live_session()`.
|
||
# Проверяют ВЫБОРКУ очереди на живой схеме (кто попал в пакет прогона), а не текст
|
||
# SQL: на мок-лэйне deploy-tradein.yml БД нет вовсе. В ci-tradein.yml они бегут
|
||
# по-настоящему (Postgres + схема из data/sql) — там прогон и был красным на
|
||
# origin/main и зелёным на ветке. Статическая половина файла
|
||
# (test_retry_queue_is_not_bound_to_only_status) БД не требует и идёт в обоих лэйнах.
|
||
tests/test_house_imv_retry_stuck.py::test_explicit_only_status_still_takes_exhausted_houses
|
||
tests/test_house_imv_retry_stuck.py::test_stuck_transient_house_returns_to_the_queue_by_itself
|
||
tests/test_house_imv_retry_stuck.py::test_transient_attempts_counter_only_counts_transient
|
||
|
||
# MAJOR-1 fix, coverage probe (#2894, независимый ревью) — тот же `live_session` fixture
|
||
# (self-skip через `_live_db_available()`, живёт только при реальном Postgres DSN).
|
||
# Проверяет, что novostroyki-строка / geo_precision='city'-строка / price_rub=0-строка
|
||
# физически не попадают в когорту (не только SQL-текст, который проверяется отдельным
|
||
# статическим тестом test_cohort_sql_excludes_* в этом же файле, идущим на обоих лэйнах).
|
||
tests/test_coverage_probe_endpoint.py::test_major1_cohort_excludes_novostroyki_and_city_precision_live
|
||
|
||
# MAJOR-2 поведенческий пин (повторная проверка #2894) — та же `live_session` fixture.
|
||
# Ловит мутацию «убрать FILTER у percentile_cont, оставив у count(*)», которую
|
||
# текстовый тест test_max_age_outlier_days_passed_to_sql пропускал (подстрока
|
||
# `days_on_market <= :max_age_days` встречается в SQL дважды). На мок-лэйне
|
||
# (deploy-tradein.yml, DSN-заглушка) реальной БД нет — self-skip.
|
||
tests/test_coverage_probe_endpoint.py::test_max_age_outlier_excluded_from_median_live
|
||
|
||
# #2992 — гейт неизменной строки в апсертах listings / listing_sources. Шесть live-тестов
|
||
# идут через реальный save_listings / upsert_listing_source на живом Postgres и меряют,
|
||
# переписана ли строка, по ctid (save_listings коммитит — транзакционные счётчики
|
||
# бесполезны). Без БД — skip; в CI Trade-In идут на postgres-сервисе (#2745). Свои
|
||
# строки t2992-* тесты удаляют явно в finally.
|
||
tests/test_2992_upsert_unchanged_gate.py::test_unchanged_rescrape_same_day_does_not_update_the_row
|
||
tests/test_2992_upsert_unchanged_gate.py::test_changed_price_still_updates
|
||
tests/test_2992_upsert_unchanged_gate.py::test_coalesce_backfill_still_updates
|
||
tests/test_2992_upsert_unchanged_gate.py::test_next_day_rescrape_updates_even_if_unchanged
|
||
tests/test_2992_upsert_unchanged_gate.py::test_skipped_row_still_yields_listing_id_for_downstream
|
||
tests/test_2992_upsert_unchanged_gate.py::test_listing_sources_unchanged_rescrape_same_day_does_not_update
|
||
# #3036 — house-поля с детальной страницы Авито → houses (fill-only). Live-тест ходит в
|
||
# настоящую БД (в CI она есть, #2745), локально без TEST_DATABASE_URL пропускается. Строки
|
||
# t3036-* тест удаляет в finally.
|
||
tests/test_3036_detail_house_params_to_houses.py::test_live_fill_only_then_keep_then_unlinked_untouched
|
||
|
||
# PR-A LATERAL-поиск предшественника в поле переобхода (#2659 продолжение) — тот же
|
||
# `_live_session()`. Три live-теста вставляют синтетическую историю снимков (в т.ч.
|
||
# с дырами) в ОДНОЙ транзакции и проверяют результат JOIN LATERAL/equality-join
|
||
# прямым SELECT'ом в той же транзакции — cleanup через rollback (без commit), явный
|
||
# DELETE не нужен. Без БД — skip; в CI Trade-In идут на postgres-сервисе (#2745).
|
||
tests/test_revisit_floor_lateral_lookup.py::test_lateral_ignores_gaps_and_matches_dense_daily_history
|
||
tests/test_revisit_floor_lateral_lookup.py::test_equality_join_loses_gapped_pairs_and_gives_a_smaller_floor
|
||
tests/test_revisit_floor_lateral_lookup.py::test_missing_history_before_anchor_still_yields_no_floor_via_lateral
|