fix(tradein): enable deactivate_stale_avito schedule on prod (#759) #1096
2 changed files with 69 additions and 0 deletions
|
|
@ -0,0 +1,19 @@
|
||||||
|
-- #759: deactivate_stale_avito schedule seeded enabled=false → task never ran on prod.
|
||||||
|
--
|
||||||
|
-- Root cause: migration 090 (ON CONFLICT (source) DO NOTHING) was a no-op because
|
||||||
|
-- the row 'deactivate_stale_avito' already existed in scrape_schedules with
|
||||||
|
-- enabled=false when 090 ran on prod (inserted by a pre-release iteration or manual
|
||||||
|
-- seed). The scheduler's WHERE enabled=true filter excluded it on every tick, so
|
||||||
|
-- last_run_id stayed NULL and next_run_at=2026-06-01 expired without any runs.
|
||||||
|
--
|
||||||
|
-- Fix: idempotent UPDATE that only fires when the row is still disabled.
|
||||||
|
-- Re-running this migration after the row is already enabled → 0 rows matched, safe.
|
||||||
|
--
|
||||||
|
-- No bind params → no CAST(:x AS type) needed; BEGIN/COMMIT per sql.md conventions.
|
||||||
|
BEGIN;
|
||||||
|
UPDATE scrape_schedules
|
||||||
|
SET enabled = true,
|
||||||
|
next_run_at = NOW()
|
||||||
|
WHERE source = 'deactivate_stale_avito'
|
||||||
|
AND enabled = false;
|
||||||
|
COMMIT;
|
||||||
|
|
@ -252,3 +252,53 @@ def test_failure_path_rollback_and_mark_failed(monkeypatch: pytest.MonkeyPatch)
|
||||||
|
|
||||||
assert db.rolled_back is True
|
assert db.rolled_back is True
|
||||||
assert failed["run_id"] == 7
|
assert failed["run_id"] == 7
|
||||||
|
|
||||||
|
|
||||||
|
# ── Migration 100: prod hot-fix — flip enabled=false → true ──────────────────
|
||||||
|
|
||||||
|
_MIGRATION_100 = _SQL_DIR / "100_enable_deactivate_stale_avito.sql"
|
||||||
|
|
||||||
|
|
||||||
|
def test_migration_100_exists() -> None:
|
||||||
|
"""Prod hot-fix migration must be present so deploy auto-applies it."""
|
||||||
|
assert _MIGRATION_100.is_file(), f"missing migration: {_MIGRATION_100}"
|
||||||
|
|
||||||
|
|
||||||
|
def test_migration_100_updates_correct_source() -> None:
|
||||||
|
sql = _MIGRATION_100.read_text("utf-8")
|
||||||
|
assert "'deactivate_stale_avito'" in sql
|
||||||
|
|
||||||
|
|
||||||
|
def test_migration_100_sets_enabled_true() -> None:
|
||||||
|
sql = _MIGRATION_100.read_text("utf-8")
|
||||||
|
# UPDATE … SET enabled = true
|
||||||
|
assert re.search(
|
||||||
|
r"enabled\s*=\s*true", sql, re.IGNORECASE
|
||||||
|
), "migration 100 must SET enabled = true"
|
||||||
|
|
||||||
|
|
||||||
|
def test_migration_100_is_idempotent() -> None:
|
||||||
|
"""WHERE enabled = false ensures re-running after fix → 0 rows matched."""
|
||||||
|
sql = _MIGRATION_100.read_text("utf-8")
|
||||||
|
assert re.search(
|
||||||
|
r"enabled\s*=\s*false", sql, re.IGNORECASE
|
||||||
|
), "migration 100 must guard with AND enabled = false for idempotency"
|
||||||
|
|
||||||
|
|
||||||
|
def test_migration_100_is_transactional() -> None:
|
||||||
|
sql = _MIGRATION_100.read_text("utf-8")
|
||||||
|
assert "BEGIN;" in sql
|
||||||
|
assert "COMMIT;" in sql
|
||||||
|
|
||||||
|
|
||||||
|
def test_migration_100_no_psycopg_trap() -> None:
|
||||||
|
"""Plain DML — no bind params — guard :x::type trap anyway."""
|
||||||
|
sql = _MIGRATION_100.read_text("utf-8")
|
||||||
|
assert not re.search(r":\w+::", sql)
|
||||||
|
|
||||||
|
|
||||||
|
def test_migration_100_also_resets_next_run_at() -> None:
|
||||||
|
"""next_run_at = NOW() ensures the scheduler fires on the next tick."""
|
||||||
|
sql = _MIGRATION_100.read_text("utf-8")
|
||||||
|
assert "next_run_at" in sql
|
||||||
|
assert "NOW()" in sql
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue