All checks were successful
CI Trade-In / changes (pull_request) Successful in 13s
CI / changes (pull_request) Successful in 13s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m13s
CI / backend-tests (pull_request) Successful in 17m35s
Сторож свежести молчал 24 суток, пока источник НСПД лежал. nspd_quarter_dumps — контент-таблица: harvest пишет строку и когда получилось, и когда упал (тогда harvest_error заполнен, счётчики нулевые, но fetched_at_utc всё равно проставлен). Монитор брал MAX(fetched_at_utc) без разбора — и каждый ПРОВАЛ обновлял часы свежести. последний успешный дамп 27.07.2026 01:06:59 с 03.08 61 дамп подряд, все с harvest_error что видел монитор возраст ~3 дня при пороге fresh_days=14 → ok Слепота по построению: мерилось «записали ли мы строку», а не «получили ли мы данные». Правильный образец лежал в соседней ветке того же if — run-ledger режим отделяет успех от попытки через FILTER (WHERE status = 'done'). Правка: у FreshnessSource появилось необязательное success_where, в data-table ветке оно вешается на last_success_at. last_attempt_at остаётся MAX(ts) — это именно РАЗДЕЛЕНИЕ, а не отбрасывание: признак «загрузчик ходит, но не приносит» должен остаться виден. Для nspd success_where = "harvest_error IS NULL". Больше нигде не включаю: из двух data-table источников колонка ошибки есть только у nspd_quarter_dumps (проверено по information_schema). gisogd_permits — чистая контент-таблица, где сама строка и есть данные, там разделять нечего. Тест герметичный: все шесть таблиц реестра создаются ВРЕМЕННЫМИ в своей же сессии, поэтому он не зависит от схемы CI-базы. В фикстуре стоит защита — сразу после создания каждая таблица обязана быть пустой; если затенение не сработало (опечатка в имени, изменившийся реестр), запросы ушли бы в настоящую таблицу и тест читал бы боевые данные. По конвенции tests/sql DSN по умолчанию смотрит в туннель к проду, так что цена такой опечатки реальна. Против кода origin/main (прогон по живому Postgres): возраст 1.0д вместо >20 — провал обновил часы свежести → падает попытка не новее успеха — на main это одно и то же MAX(ts) → падает успехом сочтена строка с ошибкой — last_success_at не None → падает свежий успех по-прежнему ok — контроль, зелёный на обеих сторонах Прогоны: tests/sql rc=0 (4 passed по живой БД); tests/workers + tests/services 3320 passed rc=0. Четыре nodeid внесены в skip_allowlist.txt — нужен живой Postgres. В CI они идут (postgres-сервис, #2745), запись нужна для машины без БД и без туннеля. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
108 lines
10 KiB
Text
108 lines
10 KiB
Text
# Объявленные пропуски сьюта backend/.
|
||
#
|
||
# Пропуск, которого здесь нет, роняет прогон (хук в tests/conftest.py). Смысл: не
|
||
# запретить пропуски, а запретить НЕЗАМЕТНЫЕ. Проверка, которая тихо перестала
|
||
# исполняться, со временем расходится с кодом, и узнают об этом ровно тогда,
|
||
# когда на неё понадобилось опереться (#2722, #2729, #2740).
|
||
#
|
||
# Формат: один nodeid в строке, без «[параметров]»; `#` — комментарий.
|
||
# Прежде чем добавить строку, ответь: почему эту проверку нельзя выполнить ЗДЕСЬ
|
||
# и где она выполняется вместо этого. Нет ответа — это не пропуск, а дыра.
|
||
#
|
||
# Список — НАДмножество сред: на CI (ubuntu + libpango + postgres-сервис) часть
|
||
# записей не срабатывает, на ноутбуке без них — срабатывает. Запись, чей пропуск
|
||
# не случился, безвредна; пропуск без записи — нет.
|
||
|
||
# ── WeasyPrint: нужны native-либы (Pango/cairo/GObject) ───────────────────────
|
||
# В CI ЭТИ ТЕСТЫ ИДУТ: ci.yml ставит libcairo2/libpango-1.0-0/libpangoft2-1.0-0.
|
||
# Записи нужны только для macOS-dev, где libgobject-2.0-0 нет.
|
||
tests/api/v1/test_parcels_forecast.py::test_export_forecast_pdf_returns_pdf
|
||
tests/services/exporters/test_report_pdf.py::TestConfidenceSectionParity::test_dict_shaped_factor_produces_valid_pdf
|
||
tests/services/exporters/test_report_pdf.py::TestFullReportExport::test_accepts_as_dict_input
|
||
tests/services/exporters/test_report_pdf.py::TestFullReportExport::test_returns_non_trivial_length
|
||
tests/services/exporters/test_report_pdf.py::TestFullReportExport::test_returns_pdf_magic_bytes
|
||
tests/services/exporters/test_report_pdf.py::TestGracefulPartialReport::test_empty_report_still_valid_pdf
|
||
tests/services/exporters/test_report_pdf.py::TestGracefulPartialReport::test_garbage_input_does_not_crash
|
||
tests/services/exporters/test_report_pdf.py::TestGracefulPartialReport::test_partial_report_some_sections
|
||
tests/services/generative/test_exporters.py::test_pdf_export_produces_pdf_bytes
|
||
tests/test_layout_tz_pdf.py
|
||
|
||
# ── phantom-column gate: нужна КОПИЯ ПРОДОВОЙ схемы ───────────────────────────
|
||
# Смысл этих тестов — поймать колонку, которой нет в реальной БД, поэтому пустой
|
||
# сервис-контейнер их не заменяет: против пустой схемы они дали бы красноту, не
|
||
# относящуюся к делу. Условие: TEST_DATABASE_URL на SSH-туннель к проду
|
||
# (`ssh -N gendesign` → localhost:15432), см. tests/integration/conftest.py.
|
||
# ЗАПУСКАТЬ ВРУЧНУЮ после правок SQL-запросов в app/services/**.
|
||
tests/integration/test_analyze_parcels_sql.py::TestIrdOverlapSql::test_explain_ird_overlap
|
||
tests/integration/test_analyze_parcels_sql.py::TestVelocityCompetitorsSql::test_explain_competitors
|
||
tests/integration/test_analyze_parcels_sql.py::TestNeighborsSummarySql::test_explain_neighbors_summary
|
||
tests/integration/test_phantom_columns.py::TestCadGeoTables::test_parcel_centroid_query
|
||
tests/integration/test_phantom_columns.py::TestDomrfKnFlats::test_avg_price_query
|
||
tests/integration/test_phantom_columns.py::TestDomrfKnFlats::test_supply_batch_query
|
||
tests/integration/test_phantom_columns.py::TestDomrfKnObjects::test_competitors_full_cte_query
|
||
tests/integration/test_phantom_columns.py::TestDomrfKnObjects::test_competitors_radius_query
|
||
tests/integration/test_phantom_columns.py::TestDomrfKnObjects::test_velocity_competitor_query_columns
|
||
tests/integration/test_phantom_columns.py::TestEkburgConstructionPermits::test_permits_rns_columns
|
||
tests/integration/test_phantom_columns.py::TestEkburgConstructionPermits::test_permits_rve_columns
|
||
tests/integration/test_phantom_columns.py::TestEkburgConstructionPermits::test_recent_permits_query
|
||
tests/integration/test_phantom_columns.py::TestMvLayoutVelocity::test_velocity_by_room_bucket
|
||
tests/integration/test_phantom_columns.py::TestObjectiveTables::test_objective_corpus_room_month_columns
|
||
tests/integration/test_phantom_columns.py::TestObjectiveTables::test_objective_mapping_columns
|
||
tests/integration/test_phantom_columns.py::TestObjectiveTables::test_sold_count_query
|
||
|
||
# ── SQL-логика на живой Postgres ──────────────────────────────────────────────
|
||
# В CI ЭТИ ТЕСТЫ ИДУТ с #2745 (postgres-сервис в ci.yml); до него не бежали ни
|
||
# разу с момента написания. Схема не нужна — строят себе временные таблицы.
|
||
# Локально: docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=... postgres:16
|
||
tests/sql/test_ddu_price_indicator.py::test_basis_and_previous_index
|
||
tests/sql/test_ddu_price_indicator.py::test_packaged_deal_bucketed_by_per_unit_area
|
||
tests/sql/test_ddu_price_indicator.py::test_prev_period_value_skips_filtered_quarter
|
||
tests/sql/test_mv_layout_velocity_weighted_avg.py::TestWeightedAvgFormula::test_hand_computed_weighted_average
|
||
tests/sql/test_mv_layout_velocity_weighted_avg.py::TestWeightedAvgFormula::test_no_zero_months_weighted_equals_naive
|
||
tests/sql/test_mv_layout_velocity_weighted_avg.py::TestWeightedAvgFormula::test_nullif_prevents_division_by_zero
|
||
tests/sql/test_mv_layout_velocity_weighted_avg.py::TestWeightedAvgFormula::test_weighted_differs_from_naive_sparse_project
|
||
tests/sql/test_mv_layout_velocity_weighted_avg.py::TestWeightedAvgFormula::test_zero_deal_months_skew_naive_avg
|
||
tests/sql/test_velocity_alerts.py::test_lookback_anchors_to_latest_data_month
|
||
tests/sql/test_velocity_alerts.py::test_sharp_drop_is_flagged
|
||
|
||
# ── Живая сеть NSPD (nspd.gov.ru) ─────────────────────────────────────────────
|
||
# Ходят в внешний сервис: в гейте это флейк и зависимость от чужого аптайма.
|
||
# Запуск вручную: uv run pytest tests/scrapers/ -m slow / -m integration -s
|
||
tests/scrapers/test_nspd_bulk_client.py::test_list_objects_in_building_real
|
||
tests/scrapers/test_nspd_bulk_client.py::test_search_by_quarter_empty
|
||
tests/scrapers/test_nspd_bulk_client.py::test_search_by_quarter_real_ekb
|
||
tests/scrapers/test_nspd_bulk_client.py::test_wms_feature_info_real
|
||
tests/scrapers/test_nspd_grid_walk.py::test_live_nspd_grid_walk_skipped
|
||
|
||
# ── Образец, которого нет в git ───────────────────────────────────────────────
|
||
# ДОЛГ, а не норма. Было 8 записей; 2 из них (test_ekb_krt_registry.py::
|
||
# TestMultiSiteCountFromDump) сняты в #2778: дампа `krt_page_raw.html` не
|
||
# оказалось НИГДЕ — ни в репозитории, ни на дисках, ни на проде (в БД лежит
|
||
# `raw_block` = уже очищенный текст, вход парсера из него не восстановить), —
|
||
# зато свойство, которое они проверяли (несколько блоков на странице, уникальные
|
||
# site_name), выразимо без дампа и теперь проверяется на многоблочной странице,
|
||
# собранной из блоков, уже лежащих в том же тест-файле.
|
||
#
|
||
# Оставшиеся 6 так не лечатся: они проверяют извлечение таблиц из НАСТОЯЩЕГО PDF
|
||
# через pdfplumber, соседний класс TestParsePptTep уже мокает pdfplumber целиком.
|
||
# Нужен файл `ppt2018_22823_poyasnit_PPT.pdf` (или рабочий URL на ГИСОГД ЕКБ) —
|
||
# запрошен у Anton в #2778, там же список того, что оживёт. Причин не коммитить
|
||
# нет: официальный градостроительный документ, без персональных данных.
|
||
# После получения — положить в tests/fixtures/, поправить `_REAL_PDF_PATH`,
|
||
# убрать эти шесть строк.
|
||
tests/services/scrapers/test_ekb_ppt_tep_parser.py::TestParsePptTepRealPdf::test_phasing_has_area
|
||
tests/services/scrapers/test_ekb_ppt_tep_parser.py::TestParsePptTepRealPdf::test_phasing_has_rows
|
||
tests/services/scrapers/test_ekb_ppt_tep_parser.py::TestParsePptTepRealPdf::test_raw_tables_not_empty
|
||
tests/services/scrapers/test_ekb_ppt_tep_parser.py::TestParsePptTepRealPdf::test_tep_has_rows
|
||
tests/services/scrapers/test_ekb_ppt_tep_parser.py::TestParsePptTepRealPdf::test_zone_balance_has_itogo
|
||
tests/services/scrapers/test_ekb_ppt_tep_parser.py::TestParsePptTepRealPdf::test_zone_balance_has_rows
|
||
|
||
# ── #2956: свежесть data-table источника считается по успешным строкам ────────
|
||
# Нужен живой Postgres: тест создаёт ВРЕМЕННЫЕ копии всех таблиц реестра freshness
|
||
# и гоняет по ним настоящий SQL compute_freshness. В CI ЭТИ ТЕСТЫ ИДУТ — postgres-
|
||
# сервис поднят (#2745), как и для соседних tests/sql/*. Записи нужны только для
|
||
# машины без БД и без туннеля на 15432.
|
||
tests/sql/test_2956_freshness_ignores_failed_dumps.py::test_failed_dumps_do_not_refresh_the_clock
|
||
tests/sql/test_2956_freshness_ignores_failed_dumps.py::test_successful_dump_still_counts_as_fresh
|
||
tests/sql/test_2956_freshness_ignores_failed_dumps.py::test_attempt_is_still_recorded
|
||
tests/sql/test_2956_freshness_ignores_failed_dumps.py::test_only_failures_means_no_success_at_all
|