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 load_json, save_json_atomic KEY = {"X-API-Key": "secret"} @pytest.fixture def env(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")) monkeypatch.setenv("RIPE_API_TOKEN", "secret") save_json_atomic(cc.CONFIG_FILE, {"asns": [], "fqdns": [], "ttl_days": 90}) return TestClient(api_server.app) def test_manage_asns_and_fqdns(env): # Без ключа - 401; ASN добавляется один раз (201, затем 200) assert env.post("/asns", json={"asn": 62041}).status_code == 401 assert env.post("/asns", json={"asn": 62041}, headers=KEY).status_code == 201 assert env.post("/asns", json={"asn": 62041}, headers=KEY).status_code == 200 assert env.get("/asns").json() == {"asns": [62041]} assert env.post("/asns", json={"asn": 0}, headers=KEY).status_code == 422 # FQDN нормализуется; IP-литералы и неверные метки отклоняются resp = env.post("/fqdns", json={"fqdn": " Example.COM. "}, headers=KEY) assert resp.status_code == 201 and resp.json()["fqdn"] == "example.com" for bad in ("1.2.3.4", "bad_name.com", "-a.com", "a..com"): assert env.post("/fqdns", json={"fqdn": bad}, headers=KEY).status_code == 422 assert env.get("/fqdns").json() == {"fqdns": ["example.com"]} # Удаление: 404 для неизвестного, 200 для существующего, конфиг обновлён assert env.delete("/asns/1", headers=KEY).status_code == 404 assert env.delete("/fqdns/example.com", headers=KEY).status_code == 200 assert env.delete("/asns/62041", headers=KEY).status_code == 200 assert load_json(cc.CONFIG_FILE, {})["asns"] == [] and load_json(cc.CONFIG_FILE, {})["fqdns"] == [] def sources(): with db.session() as conn: return {s for (s,) in conn.execute("SELECT DISTINCT source FROM addresses")} def test_removed_source_data_purge_and_ttl(env, monkeypatch): now = datetime.datetime.now() with db.session() as conn: for source, days_ago in (("1", 1), ("2", 100), ("3", 1)): stamp = (now - datetime.timedelta(days=days_ago)).isoformat(timespec="seconds") conn.execute("INSERT INTO addresses VALUES ('asn', ?, '10.0.0.0/24', ?, ?)", (source, stamp, stamp)) save_json_atomic(cc.CONFIG_FILE, {"asns": [1, 2, 3], "ttl_days": 90}) # Без purge данные остаются, purge=true удаляет сразу assert env.delete("/asns/2", headers=KEY).status_code == 200 assert env.delete("/asns/3?purge=true", headers=KEY).json() == {"asn": 3, "removed": True, "purged": True} assert sources() == {"1", "2"} # Сбор: у неопрашиваемого источника 2 адрес просрочен (100 > 90 дней) - исчезает collector = cc.CIDRCollector() monkeypatch.setattr(collector, "fetch_prefixes", lambda asn: ["10.0.0.0/24"]) collector.run_collection() assert sources() == {"1"} def test_addresses_diff(env): now = datetime.datetime.now() with db.session() as conn, db.transaction(conn): db.merge_source(conn, "asn", "1", {"1.0.0.0/24", "2001:db8::/32"}, now, 90) db.merge_source(conn, "fqdn", "example.com", {"9.9.9.9"}, now, 90) horizon = dict(conn.execute("SELECT key, value FROM meta"))["horizon_ts"] body = env.get("/addresses/diff", params={"since": "0"}).json() assert body["added"] == ["1.0.0.0/24", "2001:db8::/32", "9.9.9.9"] and body["removed"] == [] assert env.get("/addresses/diff", params={"since": "0", "type": "cidr", "ip_version": "6"}).json()["added"] == [ "2001:db8::/32"] assert env.get("/addresses").headers["X-Changes-Cursor"] == str(body["cursor"]) assert env.get("/addresses", params={"format": "nftables"}).headers["X-Changes-Cursor"] == str(body["cursor"]) # Время вместо курсора; с полученным курсором изменений нет assert env.get("/addresses/diff", params={"since": horizon}).json()["added"] == body["added"] assert env.get("/addresses/diff", params={"since": body["cursor"]}).json()["added"] == [] # Неверное значение - 400; вне журнала (курсор из будущего, время до создания журнала) - 410 assert env.get("/addresses/diff", params={"since": "yesterday"}).status_code == 400 assert env.get("/addresses/diff", params={"since": body["cursor"] + 1}).status_code == 410 assert env.get("/addresses/diff", params={"since": "2000-01-01T00:00:00Z"}).status_code == 410 assert env.get("/addresses/diff").status_code == 422