gendesign/tradein-mvp/backend/tests/test_admin_proxies.py
lekss361 90e4a741f7
Some checks failed
Deploy Trade-In / changes (push) Successful in 12s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-frontend (push) Successful in 33s
Deploy Trade-In / test (push) Failing after 1m32s
Deploy Trade-In / build-backend (push) Has been skipped
Deploy Trade-In / deploy (push) Has been skipped
feat(proxy-pool): scrape_proxies table + admin bulk-load/list endpoints (#2161)
2026-07-02 16:31:21 +00:00

199 lines
6.8 KiB
Python

"""Offline-тесты admin proxy-pool ручек (#2161).
Покрытие (db мокается, NO live network/DB):
- POST /api/v1/admin/proxies/bulk — UPSERT-счётчики, валидация affinity/kind
- GET /api/v1/admin/proxies — маскировка пароля, фильтры
- PATCH /api/v1/admin/proxies/{id} — enable/disable, 404
"""
from __future__ import annotations
import os
os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost:5432/test")
from datetime import UTC, datetime
from typing import Any
from unittest.mock import MagicMock
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
@pytest.fixture
def db() -> MagicMock:
return MagicMock()
@pytest.fixture
def client(db: MagicMock) -> TestClient:
from app.api.v1 import admin as admin_module
from app.core.db import get_db
app = FastAPI()
app.include_router(admin_module.router, prefix="/api/v1/admin")
def fake_db() -> Any:
yield db
app.dependency_overrides[get_db] = fake_db
return TestClient(app)
def _scalar_result(value: object) -> MagicMock:
res = MagicMock()
res.scalar.return_value = value
return res
# ── _mask_proxy_url unit ─────────────────────────────────────────────────────
def test_mask_proxy_url_hides_password() -> None:
from app.api.v1.admin import _mask_proxy_url
assert _mask_proxy_url("socks5://user:secret@host:1080") == "socks5://user:***@host:1080"
assert _mask_proxy_url("http://u:p@1.2.3.4:8080") == "http://u:***@1.2.3.4:8080"
def test_mask_proxy_url_no_password_untouched() -> None:
from app.api.v1.admin import _mask_proxy_url
assert _mask_proxy_url("http://host:8080") == "http://host:8080"
assert _mask_proxy_url(None) is None
assert _mask_proxy_url("") == ""
# ── POST /proxies/bulk ───────────────────────────────────────────────────────
def test_bulk_upsert_counts_inserted_and_updated(client: TestClient, db: MagicMock) -> None:
# первая строка вставлена (xmax=0 → True), вторая обновлена (False)
db.execute.side_effect = [_scalar_result(True), _scalar_result(False)]
r = client.post(
"/api/v1/admin/proxies/bulk",
json={
"proxies": [
{"url": "http://u:p@host1:8080", "provider_affinity": "avito"},
{"url": "http://u:p@host2:8080", "provider_affinity": "cian", "kind": "socks5"},
]
},
)
assert r.status_code == 200, r.text
assert r.json() == {"inserted": 1, "updated": 1}
assert db.commit.called
def test_bulk_invalid_affinity_422(client: TestClient) -> None:
r = client.post(
"/api/v1/admin/proxies/bulk",
json={"proxies": [{"url": "http://host:8080", "provider_affinity": "garbage"}]},
)
assert r.status_code == 422
def test_bulk_invalid_kind_422(client: TestClient) -> None:
r = client.post(
"/api/v1/admin/proxies/bulk",
json={"proxies": [{"url": "http://host:8080", "kind": "ftp"}]},
)
assert r.status_code == 422
def test_bulk_empty_list_422(client: TestClient) -> None:
r = client.post("/api/v1/admin/proxies/bulk", json={"proxies": []})
assert r.status_code == 422
def test_bulk_defaults_affinity_any(client: TestClient, db: MagicMock) -> None:
db.execute.side_effect = [_scalar_result(True)]
r = client.post(
"/api/v1/admin/proxies/bulk",
json={"proxies": [{"url": "http://host:8080"}]},
)
assert r.status_code == 200
# affinity по умолчанию 'any' попал в параметры INSERT
_, kwargs = db.execute.call_args
assert kwargs is not None
params = db.execute.call_args.args[1]
assert params["aff"] == "any"
assert params["kind"] == "http"
# ── GET /proxies ─────────────────────────────────────────────────────────────
def _proxy_db_row(**over: Any) -> dict[str, Any]:
base: dict[str, Any] = {
"id": 1,
"label": "test",
"url": "socks5://user:secret@host:1080",
"kind": "socks5",
"provider_affinity": "avito",
"rotate_url": "http://user:secret@rot:9000/changeip",
"enabled": True,
"consecutive_fails": 0,
"exit_ip": "1.2.3.4",
"latency_ms": 120,
"last_check_at": datetime(2026, 7, 1, tzinfo=UTC),
"last_ok_at": datetime(2026, 7, 1, tzinfo=UTC),
"leased_by": None,
"leased_at": None,
"geo": "RU",
"operator": "beeline",
"expires_at": None,
"created_at": datetime(2026, 6, 1, tzinfo=UTC),
"updated_at": datetime(2026, 7, 1, tzinfo=UTC),
}
base.update(over)
return base
def test_list_masks_password(client: TestClient, db: MagicMock) -> None:
result = MagicMock()
result.mappings.return_value.all.return_value = [_proxy_db_row()]
db.execute.return_value = result
r = client.get("/api/v1/admin/proxies")
assert r.status_code == 200, r.text
rows = r.json()
assert len(rows) == 1
assert rows[0]["url"] == "socks5://user:***@host:1080"
assert rows[0]["rotate_url"] == "http://user:***@rot:9000/changeip"
assert "secret" not in r.text
def test_list_passes_filters(client: TestClient, db: MagicMock) -> None:
result = MagicMock()
result.mappings.return_value.all.return_value = []
db.execute.return_value = result
r = client.get("/api/v1/admin/proxies", params={"provider": "cian", "enabled": "false"})
assert r.status_code == 200
params = db.execute.call_args.args[1]
assert params["provider"] == "cian"
assert params["enabled"] is False
# ── PATCH /proxies/{id} ──────────────────────────────────────────────────────
def test_patch_disable(client: TestClient, db: MagicMock) -> None:
result = MagicMock()
result.mappings.return_value.fetchone.return_value = _proxy_db_row(enabled=False)
db.execute.return_value = result
r = client.patch("/api/v1/admin/proxies/1", json={"enabled": False})
assert r.status_code == 200, r.text
assert r.json()["enabled"] is False
assert db.commit.called
def test_patch_not_found_404(client: TestClient, db: MagicMock) -> None:
result = MagicMock()
result.mappings.return_value.fetchone.return_value = None
db.execute.return_value = result
r = client.patch("/api/v1/admin/proxies/999", json={"enabled": True})
assert r.status_code == 404