test_backend_clients.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. from __future__ import annotations
  2. from pathlib import Path
  3. from core.settings import AppSettings
  4. from dashboard.api import _build_background_tasks
  5. from ops.cleanup import cleanup_once
  6. from ops.rotate import rotate_once
  7. from ops.update_priority import update_priority_once
  8. from ops.validate import validate_once
  9. class FakeBackendClient:
  10. def __init__(self) -> None:
  11. self.deleted: list[list[str]] = []
  12. self.uploads: list[tuple[str, dict]] = []
  13. self.files = {
  14. "expired@example.com.json": {
  15. "email": "expired@example.com",
  16. "refresh_token": "",
  17. "expired": "2000-01-01T00:00:00+00:00",
  18. },
  19. "priority@example.com.json": {
  20. "email": "priority@example.com",
  21. "refresh_token": "rt",
  22. "priority": 100,
  23. },
  24. "invalid@example.com.json": {
  25. "email": "invalid@example.com",
  26. "refresh_token": "rt",
  27. "account_id": "acct-1",
  28. },
  29. }
  30. def health_check(self) -> bool:
  31. return True
  32. def list_auth_files(self) -> list[dict[str, object]]:
  33. return [{"name": name} for name in self.files]
  34. def get_auth_file(self, name: str) -> dict | None:
  35. payload = self.files.get(name)
  36. return dict(payload) if isinstance(payload, dict) else None
  37. def delete_auth_file(self, name: str) -> bool:
  38. self.deleted.append([name])
  39. self.files.pop(name, None)
  40. return True
  41. def upload_auth_file(self, name: str, payload: dict) -> bool:
  42. self.uploads.append((name, dict(payload)))
  43. self.files[name] = dict(payload)
  44. return True
  45. def test_cleanup_once_accepts_backend_client(tmp_path: Path) -> None:
  46. client = FakeBackendClient()
  47. checked, deleted, refreshed = cleanup_once(client=client, proxy=None, pool_dir=tmp_path)
  48. assert checked == 3
  49. assert deleted == 1
  50. assert refreshed == 0
  51. assert client.deleted == [["expired@example.com.json"]]
  52. def test_validate_once_accepts_backend_client(monkeypatch, tmp_path: Path) -> None:
  53. client = FakeBackendClient()
  54. for name in client.files:
  55. (tmp_path / name).write_text("{}", encoding="utf-8")
  56. monkeypatch.setattr(
  57. "ops.validate._validate_file",
  58. lambda path, _auth_meta: type(
  59. "FakeEntry",
  60. (),
  61. {
  62. "name": path.name,
  63. "status_code": 401,
  64. "action": "delete",
  65. "detail": "invalid",
  66. "auth_index": "",
  67. "account_id": "acct-1",
  68. "to_dict": lambda self: {
  69. "name": path.name,
  70. "status_code": 401,
  71. "action": "delete",
  72. "detail": "invalid",
  73. "auth_index": "",
  74. "account_id": "acct-1",
  75. },
  76. },
  77. )(),
  78. )
  79. summary = validate_once(client=client, proxy=None, dry_run=False, max_workers=1, scope="all", pool_dir=tmp_path)
  80. assert summary["checked"] == 3
  81. assert summary["deleted"] == 3
  82. assert client.deleted == [
  83. ["expired@example.com.json"],
  84. ["invalid@example.com.json"],
  85. ["priority@example.com.json"],
  86. ]
  87. assert list(tmp_path.glob("*.json")) == []
  88. def test_update_priority_once_accepts_backend_client() -> None:
  89. client = FakeBackendClient()
  90. summary = update_priority_once(client=client, target_priority=500, dry_run=False, limit=1)
  91. assert summary["total"] == 1
  92. assert summary["modified"] == 1
  93. assert client.uploads[0][0] == "expired@example.com.json"
  94. assert client.uploads[0][1]["priority"] == 500
  95. def test_rotate_once_accepts_backend_client(tmp_path: Path) -> None:
  96. client = FakeBackendClient()
  97. result = rotate_once(pool_dir=tmp_path, client=client)
  98. assert result.main_pool_before == 3
  99. assert result.main_pool_after == 3
  100. assert result.deleted_401 == 0
  101. def test_build_background_tasks_uses_backend_client_factory(monkeypatch, tmp_path: Path) -> None:
  102. created: list[str] = []
  103. fake_client = object()
  104. seen: list[tuple[str, object]] = []
  105. monkeypatch.setattr("dashboard.api.create_backend_client", lambda settings: created.append(settings.backend) or fake_client)
  106. monkeypatch.setattr("dashboard.api._cleanup_once", lambda **kwargs: seen.append(("cleanup", kwargs["client"])))
  107. monkeypatch.setattr(
  108. "dashboard.api._validate_once",
  109. lambda **kwargs: {"checked": 0, "deleted": 0} if not seen.append(("validate", kwargs["client"])) else None,
  110. )
  111. monkeypatch.setattr("dashboard.api._print_validate_summary", lambda summary: summary)
  112. monkeypatch.setattr(
  113. "dashboard.api._rotate_once",
  114. lambda **kwargs: {"main_pool_before": 0} if not seen.append(("rotate", kwargs["client"])) else None,
  115. )
  116. monkeypatch.setattr("dashboard.api._print_rotate_summary", lambda summary: summary)
  117. settings = AppSettings(
  118. runtime_mode="full",
  119. backend="sub2api",
  120. cleanup_enabled=True,
  121. validate_enabled=True,
  122. rotate_enabled=True,
  123. d1_cleanup_enabled=False,
  124. account_survival_enabled=False,
  125. pool_dir=tmp_path,
  126. )
  127. tasks = _build_background_tasks(settings)
  128. for task in tasks:
  129. task.fn()
  130. assert created == ["sub2api", "sub2api", "sub2api"]
  131. assert seen == [("cleanup", fake_client), ("validate", fake_client), ("rotate", fake_client)]