|
|
|
@@ -11,7 +11,7 @@ def test_normalize_11_digits_with_7():
|
|
|
|
|
assert pc.normalize_phone("79139884603") == ("79139884603", "")
|
|
|
|
|
|
|
|
|
|
def test_normalize_10_digits_mobile():
|
|
|
|
|
assert pc.normalize_phone("9990000009") == ("79990000009", "")
|
|
|
|
|
assert pc.normalize_phone("9990000001") == ("79990000001", "")
|
|
|
|
|
|
|
|
|
|
def test_normalize_10_digits_landline():
|
|
|
|
|
assert pc.normalize_phone("3812361211") == ("73812361211", "")
|
|
|
|
@@ -50,24 +50,24 @@ def test_prepare_drops_tail_fragments():
|
|
|
|
|
assert [g["phone"] for g in got] == ["79059223914"]
|
|
|
|
|
|
|
|
|
|
def test_prepare_keeps_order_and_notes():
|
|
|
|
|
got = pc.prepare_phones(["9990000009", "3812361211"], city="Омск")
|
|
|
|
|
assert [g["phone"] for g in got] == ["79990000009", "73812361211"]
|
|
|
|
|
got = pc.prepare_phones(["9990000001", "3812361211"], city="Омск")
|
|
|
|
|
assert [g["phone"] for g in got] == ["79990000001", "73812361211"]
|
|
|
|
|
assert all(g["note"] == "" for g in got)
|
|
|
|
|
|
|
|
|
|
def test_prepare_real_screenshot_case():
|
|
|
|
|
raws = ["9990000009", "9139884603", "3812361211", "89139884603",
|
|
|
|
|
raws = ["9990000001", "9139884603", "3812361211", "89139884603",
|
|
|
|
|
"89059223914", "9223914", "3812302920", "3812606679"]
|
|
|
|
|
got = pc.prepare_phones(raws, city="Омск")
|
|
|
|
|
# 9139884603 выброшен как хвост 89139884603 (остаётся ВТОРОЕ вхождение),
|
|
|
|
|
# 9223914 — хвост 89059223914
|
|
|
|
|
assert [g["phone"] for g in got] == [
|
|
|
|
|
"79990000009", "73812361211", "79139884603",
|
|
|
|
|
"79990000001", "73812361211", "79139884603",
|
|
|
|
|
"79059223914", "73812302920", "73812606679"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# --- parse_clean_response / clean_phone: ДаДата «Стандартизация телефона» ---
|
|
|
|
|
|
|
|
|
|
CLEAN_MOBILE = [{"source": "+79990000009", "type": "Мобильный", "phone": "+7 999 000-00-09",
|
|
|
|
|
CLEAN_MOBILE = [{"source": "+79990000001", "type": "Мобильный", "phone": "+7 999 000-00-01",
|
|
|
|
|
"provider": "ПАО \"МТС\"", "city": None, "region": "Омская",
|
|
|
|
|
"qc": 0}]
|
|
|
|
|
CLEAN_LANDLINE = [{"source": "+73812361211", "type": "Стационарный", "phone": "+7 3812 36-12-11",
|
|
|
|
@@ -99,15 +99,15 @@ def test_clean_phone_uses_cache(tmp_path):
|
|
|
|
|
calls.append(url)
|
|
|
|
|
return CLEAN_MOBILE
|
|
|
|
|
keys = ("api", "secret")
|
|
|
|
|
a = pc.clean_phone("+79990000009", keys, fetch=fake_fetch, cache_dir=str(tmp_path))
|
|
|
|
|
b = pc.clean_phone("+79990000009", keys, fetch=fake_fetch, cache_dir=str(tmp_path))
|
|
|
|
|
a = pc.clean_phone("+79990000001", keys, fetch=fake_fetch, cache_dir=str(tmp_path))
|
|
|
|
|
b = pc.clean_phone("+79990000001", keys, fetch=fake_fetch, cache_dir=str(tmp_path))
|
|
|
|
|
assert a["kind"] == b["kind"] == "mobile"
|
|
|
|
|
assert len(calls) == 1 # второй раз — из кэша, денег не тратим
|
|
|
|
|
|
|
|
|
|
def test_clean_phone_network_error_is_unknown(tmp_path):
|
|
|
|
|
def boom(url, headers, body):
|
|
|
|
|
raise IOError("сеть упала")
|
|
|
|
|
got = pc.clean_phone("+79990000009", ("a", "s"), fetch=boom, cache_dir=str(tmp_path))
|
|
|
|
|
got = pc.clean_phone("+79990000001", ("a", "s"), fetch=boom, cache_dir=str(tmp_path))
|
|
|
|
|
assert got["kind"] == "unknown"
|
|
|
|
|
|
|
|
|
|
CLEAN_VIRTUAL = [{"source": "+79001112233", "type": "Виртуальный", "phone": "+7 900 111-22-33",
|
|
|
|
@@ -140,8 +140,8 @@ def test_clean_phone_does_not_cache_service_error(tmp_path):
|
|
|
|
|
def fake_fetch(url, headers, body):
|
|
|
|
|
calls.append(url)
|
|
|
|
|
return responses[len(calls) - 1]
|
|
|
|
|
a = pc.clean_phone("+79990000009", ("a", "s"), fetch=fake_fetch, cache_dir=str(tmp_path))
|
|
|
|
|
b = pc.clean_phone("+79990000009", ("a", "s"), fetch=fake_fetch, cache_dir=str(tmp_path))
|
|
|
|
|
a = pc.clean_phone("+79990000001", ("a", "s"), fetch=fake_fetch, cache_dir=str(tmp_path))
|
|
|
|
|
b = pc.clean_phone("+79990000001", ("a", "s"), fetch=fake_fetch, cache_dir=str(tmp_path))
|
|
|
|
|
assert a["kind"] == "unknown" and b["kind"] == "mobile"
|
|
|
|
|
assert len(calls) == 2 # сбой сервиса не закэшировался — со второй попытки добрали
|
|
|
|
|
|
|
|
|
@@ -179,7 +179,7 @@ def test_hlr_alive_send_then_poll(tmp_path):
|
|
|
|
|
def fake_fetch(url, params):
|
|
|
|
|
calls.append(url)
|
|
|
|
|
return seq[len(calls) - 1]
|
|
|
|
|
v = pc.hlr_alive("+79990000009", ("login", "psw"),
|
|
|
|
|
v = pc.hlr_alive("+79990000001", ("login", "psw"),
|
|
|
|
|
fetch=fake_fetch, cache_dir=str(tmp_path), pause=0)
|
|
|
|
|
assert v == "alive"
|
|
|
|
|
assert calls[0].endswith("send.php") and calls[1].endswith("status.php")
|
|
|
|
@@ -199,7 +199,7 @@ def test_hlr_alive_cached_no_second_charge(tmp_path):
|
|
|
|
|
def test_hlr_alive_timeout_is_unknown_and_not_cached(tmp_path):
|
|
|
|
|
def fake_fetch(url, params):
|
|
|
|
|
return {"id": 77, "cnt": 1} if url.endswith("send.php") else {"status": 0, "err": 0}
|
|
|
|
|
v = pc.hlr_alive("+79990000009", ("l", "p"), fetch=fake_fetch,
|
|
|
|
|
v = pc.hlr_alive("+79990000001", ("l", "p"), fetch=fake_fetch,
|
|
|
|
|
cache_dir=str(tmp_path), tries=2, pause=0)
|
|
|
|
|
assert v == "unknown"
|
|
|
|
|
assert not list(tmp_path.iterdir()) # unknown не кэшируем
|
|
|
|
@@ -207,7 +207,7 @@ def test_hlr_alive_timeout_is_unknown_and_not_cached(tmp_path):
|
|
|
|
|
def test_hlr_alive_network_error_is_unknown(tmp_path):
|
|
|
|
|
def boom(url, params):
|
|
|
|
|
raise IOError()
|
|
|
|
|
assert pc.hlr_alive("+79990000009", ("l", "p"), fetch=boom,
|
|
|
|
|
assert pc.hlr_alive("+79990000001", ("l", "p"), fetch=boom,
|
|
|
|
|
cache_dir=str(tmp_path)) == "unknown"
|
|
|
|
|
|
|
|
|
|
def test_parse_hlr_string_fields():
|
|
|
|
@@ -229,12 +229,12 @@ def test_hlr_alive_id_zero_is_valid(tmp_path):
|
|
|
|
|
def fake_fetch(url, params):
|
|
|
|
|
calls.append(url)
|
|
|
|
|
return seq[len(calls) - 1]
|
|
|
|
|
v = pc.hlr_alive("+79990000009", ("l", "p"), fetch=fake_fetch,
|
|
|
|
|
v = pc.hlr_alive("+79990000001", ("l", "p"), fetch=fake_fetch,
|
|
|
|
|
cache_dir=str(tmp_path), pause=0)
|
|
|
|
|
assert v == "alive" # id=0 — успешная оплаченная отправка, опрашиваем статус
|
|
|
|
|
|
|
|
|
|
def test_hlr_alive_no_id_is_unknown(tmp_path):
|
|
|
|
|
v = pc.hlr_alive("+79990000009", ("l", "p"),
|
|
|
|
|
v = pc.hlr_alive("+79990000001", ("l", "p"),
|
|
|
|
|
fetch=lambda url, params: {"cnt": 1},
|
|
|
|
|
cache_dir=str(tmp_path), pause=0)
|
|
|
|
|
assert v == "unknown"
|
|
|
|
@@ -253,11 +253,11 @@ def _hlr_stub(phone, creds, **kw):
|
|
|
|
|
|
|
|
|
|
def test_check_full_happy_path():
|
|
|
|
|
checked, warn = pc.check_contact_phones(
|
|
|
|
|
["9990000009", "9139884603", "3812361211"], "Омск",
|
|
|
|
|
["9990000001", "9139884603", "3812361211"], "Омск",
|
|
|
|
|
("api", "secret"), ("login", "psw"), clean=_clean_stub, hlr=_hlr_stub)
|
|
|
|
|
assert warn is None
|
|
|
|
|
by = {c["phone"]: c for c in checked}
|
|
|
|
|
assert by["79990000009"]["kind"] == "mobile" and by["79990000009"]["alive"] == "alive"
|
|
|
|
|
assert by["79990000001"]["kind"] == "mobile" and by["79990000001"]["alive"] == "alive"
|
|
|
|
|
assert by["79139884603"]["alive"] == "dead"
|
|
|
|
|
assert by["73812361211"]["kind"] == "landline" and by["73812361211"]["alive"] is None
|
|
|
|
|
|
|
|
|
@@ -271,7 +271,7 @@ def test_check_no_dadata_keys_still_normalizes():
|
|
|
|
|
|
|
|
|
|
def test_check_no_smsc_creds_types_only():
|
|
|
|
|
checked, warn = pc.check_contact_phones(
|
|
|
|
|
["9990000009"], "Омск", ("api", "secret"), None,
|
|
|
|
|
["9990000001"], "Омск", ("api", "secret"), None,
|
|
|
|
|
clean=_clean_stub, hlr=_hlr_stub)
|
|
|
|
|
assert checked[0]["kind"] == "mobile" and checked[0]["alive"] is None
|
|
|
|
|
assert "smsc.txt" in warn
|
|
|
|
@@ -281,51 +281,51 @@ def test_check_hlr_only_for_mobiles():
|
|
|
|
|
def hlr_spy(phone, creds, **kw):
|
|
|
|
|
calls.append(phone)
|
|
|
|
|
return "alive"
|
|
|
|
|
pc.check_contact_phones(["9990000009", "3812361211"], "Омск",
|
|
|
|
|
pc.check_contact_phones(["9990000001", "3812361211"], "Омск",
|
|
|
|
|
("a", "s"), ("l", "p"), clean=_clean_stub, hlr=hlr_spy)
|
|
|
|
|
assert calls == ["79990000009"] # городской HLR-ом не проверяем (деньги)
|
|
|
|
|
assert calls == ["79990000001"] # городской HLR-ом не проверяем (деньги)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_sort_mobiles_first_gone_last():
|
|
|
|
|
checked = [
|
|
|
|
|
{"phone": "73812361211", "kind": "landline", "alive": None},
|
|
|
|
|
{"phone": "79139884603", "kind": "mobile", "alive": "dead"},
|
|
|
|
|
{"phone": "79990000009", "kind": "mobile", "alive": "alive"},
|
|
|
|
|
{"phone": "79990000001", "kind": "mobile", "alive": "alive"},
|
|
|
|
|
{"phone": "79000000000", "kind": "mobile", "alive": "gone"},
|
|
|
|
|
]
|
|
|
|
|
got = [c["phone"] for c in pc.sort_for_display(checked)]
|
|
|
|
|
assert got == ["79990000009", "79139884603", "73812361211", "79000000000"]
|
|
|
|
|
assert got == ["79990000001", "79139884603", "73812361211", "79000000000"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_check_junk_not_hlr_checked_and_one_raise_does_not_kill_batch():
|
|
|
|
|
# мусорный номер не гоняем через HLR (деньги), а упавший clean не роняет остальные
|
|
|
|
|
calls = []
|
|
|
|
|
def clean_stub(phone, keys, **kw):
|
|
|
|
|
if phone == "79990000009":
|
|
|
|
|
if phone == "79990000001":
|
|
|
|
|
raise IOError("сервис лёг")
|
|
|
|
|
return {"kind": "junk", "provider": "", "city": "", "qc": 2}
|
|
|
|
|
def hlr_spy(phone, creds, **kw):
|
|
|
|
|
calls.append(phone)
|
|
|
|
|
return "alive"
|
|
|
|
|
checked, warn = pc.check_contact_phones(
|
|
|
|
|
["9990000009", "9139884603"], "Омск", ("a", "s"), ("l", "p"),
|
|
|
|
|
["9990000001", "9139884603"], "Омск", ("a", "s"), ("l", "p"),
|
|
|
|
|
clean=clean_stub, hlr=hlr_spy)
|
|
|
|
|
assert [c["phone"] for c in checked] == ["79990000009", "79139884603"]
|
|
|
|
|
assert [c["phone"] for c in checked] == ["79990000001", "79139884603"]
|
|
|
|
|
assert checked[0]["kind"] == "unknown" # упавший clean → номер без пометки
|
|
|
|
|
assert checked[1]["kind"] == "junk"
|
|
|
|
|
assert calls == [] # ни junk, ни unknown в HLR не ушли
|
|
|
|
|
|
|
|
|
|
def test_check_both_warnings_joined():
|
|
|
|
|
_, warn = pc.check_contact_phones(["9990000009"], "Омск", None, None,
|
|
|
|
|
_, warn = pc.check_contact_phones(["9990000001"], "Омск", None, None,
|
|
|
|
|
clean=_clean_stub, hlr=_hlr_stub)
|
|
|
|
|
assert "dadata_clean_keys.txt" in warn and "smsc.txt" in warn and "; " in warn
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_check_unrecognized_raws_reported_not_lost():
|
|
|
|
|
checked, warn = pc.check_contact_phones(
|
|
|
|
|
["9990000009", "12345"], "Омск", None, None,
|
|
|
|
|
["9990000001", "12345"], "Омск", None, None,
|
|
|
|
|
clean=_clean_stub, hlr=_hlr_stub)
|
|
|
|
|
assert [c["phone"] for c in checked] == ["79990000009"]
|
|
|
|
|
assert [c["phone"] for c in checked] == ["79990000001"]
|
|
|
|
|
assert "не распознаны и скрыты: 12345" in warn
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -342,9 +342,9 @@ def test_sort_junk_ranks_above_gone():
|
|
|
|
|
# --- label / is_dead: одна строка для показа ---
|
|
|
|
|
|
|
|
|
|
def test_label_mobile_alive():
|
|
|
|
|
c = {"phone": "79990000009", "kind": "mobile", "provider": "МТС",
|
|
|
|
|
c = {"phone": "79990000001", "kind": "mobile", "provider": "МТС",
|
|
|
|
|
"city": "", "alive": "alive", "note": ""}
|
|
|
|
|
assert pc.label(c) == "79990000009 · МТС"
|
|
|
|
|
assert pc.label(c) == "79990000001 · МТС"
|
|
|
|
|
|
|
|
|
|
def test_label_landline():
|
|
|
|
|
c = {"phone": "73812361211", "kind": "landline", "provider": "Ростелеком",
|
|
|
|
|