test_validate.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. from pathlib import Path
  2. from ops.validate import validate_once
  3. class FakeClient:
  4. def __init__(self, files: dict[str, dict]):
  5. self.files = {name: dict(payload) for name, payload in files.items()}
  6. self.deleted: list[str] = []
  7. def health_check(self) -> bool:
  8. return True
  9. def list_auth_files(self) -> list[dict[str, object]]:
  10. return [{"name": name} for name in self.files]
  11. def get_auth_file(self, name: str) -> dict | None:
  12. payload = self.files.get(name)
  13. return dict(payload) if isinstance(payload, dict) else None
  14. def delete_auth_file(self, name: str) -> bool:
  15. self.deleted.append(name)
  16. self.files.pop(name, None)
  17. return True
  18. def _file(email: str, account_id: str) -> dict:
  19. return {"email": email, "access_token": f"token-{account_id}", "account_id": account_id}
  20. def test_validate_once_scope_used_limits_files_by_management(monkeypatch, tmp_path: Path) -> None:
  21. client = FakeClient(
  22. {
  23. "active@example.com.json": _file("active@example.com", "acct-active"),
  24. "idle@example.com.json": _file("idle@example.com", "acct-idle"),
  25. }
  26. )
  27. monkeypatch.setattr(
  28. "ops.validate._fetch_management_auth_files",
  29. lambda management_base_url, management_key=None: (
  30. True,
  31. {
  32. "active@example.com.json": {"auth_index": "2"},
  33. "idle@example.com.json": {"auth_index": "9"},
  34. },
  35. ),
  36. )
  37. monkeypatch.setattr(
  38. "ops.validate._fetch_used_auth_indexes",
  39. lambda management_base_url, management_key=None: (True, {"2"}),
  40. )
  41. seen: list[str] = []
  42. def fake_validate_file(path: Path, auth_meta: dict | None): # type: ignore[no-untyped-def]
  43. seen.append(path.name)
  44. assert auth_meta == {"auth_index": "2"}
  45. return type(
  46. "FakeEntry",
  47. (),
  48. {
  49. "name": path.name,
  50. "status_code": 200,
  51. "action": "keep",
  52. "detail": "ok",
  53. "auth_index": "2",
  54. "account_id": "acct-active",
  55. "to_dict": lambda self: {
  56. "name": path.name,
  57. "status_code": 200,
  58. "action": "keep",
  59. "detail": "ok",
  60. "auth_index": "2",
  61. "account_id": "acct-active",
  62. },
  63. },
  64. )()
  65. monkeypatch.setattr("ops.validate._validate_file", fake_validate_file)
  66. summary = validate_once(client=client, proxy=None, dry_run=True, max_workers=2, scope="used")
  67. assert summary["validation_limited"] is False
  68. assert summary["selection_reason"] is None
  69. assert summary["selected"] == 1
  70. assert summary["checked"] == 1
  71. assert seen == ["active@example.com.json"]
  72. def test_validate_once_scope_used_stops_when_management_unavailable(monkeypatch) -> None:
  73. client = FakeClient({"active@example.com.json": _file("active@example.com", "acct-active")})
  74. monkeypatch.setattr("ops.validate._fetch_management_auth_files", lambda management_base_url, management_key=None: (False, {}))
  75. monkeypatch.setattr("ops.validate._fetch_used_auth_indexes", lambda management_base_url, management_key=None: (False, set()))
  76. summary = validate_once(client=client, proxy=None, dry_run=False, max_workers=2, scope="used")
  77. assert summary["validation_limited"] is True
  78. assert summary["selection_reason"] == "management_data_unavailable"
  79. assert summary["selected"] == 0
  80. assert summary["checked"] == 0
  81. assert summary["deleted"] == 0
  82. def test_validate_once_scope_all_does_not_require_management(monkeypatch) -> None:
  83. client = FakeClient({"active@example.com.json": _file("active@example.com", "acct-1")})
  84. monkeypatch.setattr(
  85. "ops.validate._validate_file",
  86. lambda path, auth_meta: type(
  87. "FakeEntry",
  88. (),
  89. {
  90. "name": path.name,
  91. "status_code": 401,
  92. "action": "delete",
  93. "detail": "invalid",
  94. "auth_index": "",
  95. "account_id": "acct-1",
  96. "to_dict": lambda self: {
  97. "name": path.name,
  98. "status_code": 401,
  99. "action": "delete",
  100. "detail": "invalid",
  101. "auth_index": "",
  102. "account_id": "acct-1",
  103. },
  104. },
  105. )(),
  106. )
  107. summary = validate_once(client=client, proxy=None, dry_run=False, max_workers=1, scope="all")
  108. assert summary["validation_limited"] is False
  109. assert summary["checked"] == 1
  110. assert summary["deleted"] == 1
  111. assert client.deleted == ["active@example.com.json"]
  112. def test_validate_once_deletes_pool_backup_together(monkeypatch, tmp_path: Path) -> None:
  113. client = FakeClient({"active@example.com.json": _file("active@example.com", "acct-1")})
  114. pool_file = tmp_path / "active@example.com.json"
  115. pool_file.write_text("{}", encoding="utf-8")
  116. monkeypatch.setattr(
  117. "ops.validate._validate_file",
  118. lambda path, auth_meta: type(
  119. "FakeEntry",
  120. (),
  121. {
  122. "name": path.name,
  123. "status_code": 401,
  124. "action": "delete",
  125. "detail": "invalid",
  126. "auth_index": "",
  127. "account_id": "acct-1",
  128. "to_dict": lambda self: {
  129. "name": path.name,
  130. "status_code": 401,
  131. "action": "delete",
  132. "detail": "invalid",
  133. "auth_index": "",
  134. "account_id": "acct-1",
  135. },
  136. },
  137. )(),
  138. )
  139. summary = validate_once(client=client, proxy=None, dry_run=False, max_workers=1, scope="all", pool_dir=tmp_path)
  140. assert summary["deleted"] == 1
  141. assert client.deleted == ["active@example.com.json"]
  142. assert not pool_file.exists()