"""Ключ синглтон-лока kn-свипа зависит от множества, а не от порядка (#2464). `developers` приходит прямо из тела запроса (`admin_scrape`), порядок произвольный. Раньше список джойнился как пришёл: один и тот же набор в другом порядке давал ДРУГОЙ ключ — синглтон молча переставал быть синглтоном, два свипа шли параллельно по одним и тем же разработчикам. Второе следствие того же корня: `force_release_lock` строит ключ этой же функцией. Оператор, снимающий залипший лок и перечисливший разработчиков в ином порядке, молча не снимал ничего. Тесты сравнивают ЗНАЧЕНИЯ ключей — на origin/main они не совпадают, то есть красное означает «ключи разошлись», а не «функции нет». """ from __future__ import annotations import os os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost:5432/test") def _key(region: int, devs: list[str] | None) -> str: from app.workers.tasks.scrape_kn import _lock_key return _lock_key(region, devs) def test_same_set_different_order_gives_same_key() -> None: """Порядок не должен создавать второй лок для того же набора.""" a = _key(66, ["6208_0", "1234_5", "9999_1"]) b = _key(66, ["9999_1", "6208_0", "1234_5"]) assert a == b, f"один набор дал два ключа:\n {a}\n {b}" def test_duplicates_do_not_change_the_key() -> None: """Повтор в списке — тот же набор, тот же лок.""" assert _key(66, ["6208_0", "6208_0"]) == _key(66, ["6208_0"]) def test_different_sets_stay_separate() -> None: """Контроль: разные наборы обязаны остаться разными локами. Ловит «починку» через огрубление ключа (напр. отбросить developers вовсе) — тогда свипы по разным разработчикам блокировали бы друг друга. """ assert _key(66, ["A"]) != _key(66, ["B"]) assert _key(66, ["A"]) != _key(66, ["A", "B"]) def test_region_still_separates_locks() -> None: """Контроль: регион остаётся частью ключа.""" assert _key(66, ["A"]) != _key(74, ["A"]) def test_empty_and_none_mean_all_developers() -> None: """Контроль: прежняя семантика '*' для пустого/None сохранена.""" assert _key(66, None).endswith(":*") assert _key(66, []).endswith(":*")