When ripe.db is corrupted and no valid backup exists, a new empty database is created with a db_recreated.json marker; /addresses and /addresses/diff answer 503 until the collector gathers data again, /health reports db_recreated. Tests now isolate all state files via conftest. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
100 lines
4.4 KiB
Python
100 lines
4.4 KiB
Python
import datetime
|
|
import os
|
|
import sys
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
import api_server
|
|
import cidr_collector as cc
|
|
import db
|
|
from storage import StorageError, load_json
|
|
|
|
NOW = datetime.datetime.now()
|
|
|
|
|
|
@pytest.fixture
|
|
def files(tmp_path, monkeypatch):
|
|
monkeypatch.setattr(cc, "CONFIG_FILE", str(tmp_path / "config.json"))
|
|
monkeypatch.setattr(cc, "DATA_FILE", str(tmp_path / "data.json"))
|
|
monkeypatch.setattr(cc, "FQDN_DATA_FILE", str(tmp_path / "fqdn_data.json"))
|
|
monkeypatch.setattr(cc, "DB_FILE", str(tmp_path / "ripe.db"))
|
|
return tmp_path
|
|
|
|
|
|
def stored(kind="asn"):
|
|
with db.session() as conn:
|
|
return sorted(db.get_values(conn, kind))
|
|
|
|
|
|
def test_ttl_merge_and_failure_safety(files, monkeypatch):
|
|
(files / "config.json").write_text('{"asns": [1], "ttl_days": 90}')
|
|
collector = cc.CIDRCollector()
|
|
old = (NOW - datetime.timedelta(days=100)).isoformat(timespec="seconds")
|
|
with db.session() as conn:
|
|
conn.execute("INSERT INTO addresses VALUES ('asn', '1', '9.9.9.0/24', ?, ?)", (old, old))
|
|
|
|
# Ошибка источника: ничего не удаляется, даже просроченное
|
|
monkeypatch.setattr(collector, "fetch_prefixes", lambda asn: None)
|
|
collector.run_collection()
|
|
assert stored() == ["9.9.9.0/24"]
|
|
|
|
# Успешный ответ: просроченный удаляется, новый добавляется
|
|
monkeypatch.setattr(collector, "fetch_prefixes", lambda asn: ["1.1.1.0/24"])
|
|
collector.run_collection()
|
|
assert stored() == ["1.1.1.0/24"]
|
|
|
|
|
|
def test_corrupted_storage_is_preserved(files):
|
|
# Битый JSON (конфигурация)
|
|
path = files / "data.json"
|
|
path.write_text("{broken")
|
|
with pytest.raises(StorageError):
|
|
load_json(str(path), {})
|
|
assert not path.exists()
|
|
assert len(list(files.glob("data.json.corrupt-*"))) == 1
|
|
|
|
# Битая база без копий: оригинал убран в сторону, создана новая пустая база и поставлена метка пересоздания
|
|
(files / "ripe.db").write_bytes(b"this is not a sqlite database" * 100)
|
|
with db.session() as conn:
|
|
assert db.get_values(conn) == []
|
|
assert len(list(files.glob("ripe.db.corrupt-*"))) == 1
|
|
assert (files / "db_recreated.json").exists()
|
|
|
|
|
|
def test_post_schedule_auth(files, monkeypatch):
|
|
(files / "config.json").write_text('{"asns": [], "fqdns": []}')
|
|
client = TestClient(api_server.app)
|
|
body = {"type": "asn", "cron": "*/5 * * * *"}
|
|
|
|
monkeypatch.delenv("RIPE_API_TOKEN", raising=False)
|
|
assert client.post("/schedule", json=body).status_code == 503
|
|
|
|
monkeypatch.setenv("RIPE_API_TOKEN", "secret")
|
|
assert client.post("/schedule", json=body).status_code == 401
|
|
assert client.post("/schedule", json=body, headers={"X-API-Key": "wrong"}).status_code == 401
|
|
assert client.post("/schedule", json=body, headers={"X-API-Key": "secret"}).status_code == 200
|
|
assert load_json(cc.CONFIG_FILE, {})["schedule"]["asn"] == "*/5 * * * *"
|
|
assert client.get("/addresses").status_code == 200
|
|
|
|
|
|
def test_fqdn_keeps_only_global_addresses(files, monkeypatch):
|
|
answers = [(2, 1, 6, "", (address, 0)) for address in
|
|
("93.184.216.34", "127.0.0.1", "10.0.0.5", "0.0.0.0", "fe80::1%eth0", "2606:2800:220:1::1")]
|
|
monkeypatch.setattr(cc.socket, "getaddrinfo", lambda *args, **kwargs: answers)
|
|
|
|
(files / "config.json").write_text('{"fqdns": ["x.example"]}')
|
|
assert sorted(cc.FQDNCollector().resolve_fqdn("x.example")) == ["2606:2800:220:1::1", "93.184.216.34"]
|
|
|
|
# Внутренние имена: фильтр отключается, зона IPv6 отбрасывается
|
|
(files / "config.json").write_text('{"fqdns": ["x.example"], "allow_non_global_ips": true}')
|
|
everything = cc.FQDNCollector().resolve_fqdn("x.example")
|
|
assert {"127.0.0.1", "10.0.0.5", "fe80::1"} <= set(everything) and len(everything) == 6
|
|
|
|
# Глобальных адресов нет - результат пуст, как при ошибке DNS (сбор ничего не удаляет)
|
|
monkeypatch.setattr(cc.socket, "getaddrinfo", lambda *args, **kwargs: [answers[1]])
|
|
(files / "config.json").write_text('{"fqdns": ["x.example"]}')
|
|
assert cc.FQDNCollector().resolve_fqdn("x.example") == []
|