diff --git a/app/adapters/cloudflare.py b/app/adapters/cloudflare.py index e0b9b83..e03b6e2 100644 --- a/app/adapters/cloudflare.py +++ b/app/adapters/cloudflare.py @@ -42,16 +42,22 @@ class CloudflareAdapter(BaseAdapter): return {"ok": False, "message": str(e)} def list_domains(self) -> list: - data = self._get("/zones?per_page=50") result = [] - for z in data.get("result", []): - result.append( - NormalizedDomain( - external_id=z.get("id"), - domain_name=z.get("name"), - registrar="cloudflare", - status="active" if z.get("status") == "active" else (z.get("status") or "unknown"), - raw=z, + page = 1 + while True: + data = self._get(f"/zones?per_page=50&page={page}") + for z in data.get("result", []): + result.append( + NormalizedDomain( + external_id=z.get("id"), + domain_name=z.get("name"), + registrar="cloudflare", + status="active" if z.get("status") == "active" else (z.get("status") or "unknown"), + raw=z, + ) ) - ) + info = data.get("result_info", {}) + if page >= info.get("total_pages", 1): + break + page += 1 return result diff --git a/app/adapters/digitalocean.py b/app/adapters/digitalocean.py index bdc21a2..256f9c3 100644 --- a/app/adapters/digitalocean.py +++ b/app/adapters/digitalocean.py @@ -52,38 +52,50 @@ class DigitalOceanAdapter(BaseAdapter): return None def list_vps(self) -> list: - data = self._get("/droplets") result = [] - for d in data.get("droplets", []): - image = d.get("image", {}) - os_name = f"{image.get('distribution', '')} {image.get('name', '')}".strip() - mem_mb = d.get("memory") or 0 - region = d.get("region", {}) - result.append( - NormalizedVPS( - external_id=str(d.get("id")), - name=d.get("name"), - ip_address=self._main_ip(d), - region=region.get("slug") or region.get("name"), - os=os_name or None, - cpu_cores=d.get("vcpus"), - memory_gb=round(mem_mb / 1024, 1) if mem_mb else None, - disk_gb=d.get("disk"), - status="active" if d.get("status") == "active" else (d.get("status") or "unknown"), - currency="USD", - raw=d, + page = 1 + while True: + data = self._get(f"/droplets?per_page=100&page={page}") + for d in data.get("droplets", []): + image = d.get("image", {}) + os_name = f"{image.get('distribution', '')} {image.get('name', '')}".strip() + mem_mb = d.get("memory") or 0 + region = d.get("region", {}) + result.append( + NormalizedVPS( + external_id=str(d.get("id")), + name=d.get("name"), + ip_address=self._main_ip(d), + region=region.get("slug") or region.get("name"), + os=os_name or None, + cpu_cores=d.get("vcpus"), + memory_gb=round(mem_mb / 1024, 1) if mem_mb else None, + disk_gb=d.get("disk"), + status="active" if d.get("status") == "active" else (d.get("status") or "unknown"), + currency="USD", + raw=d, + ) ) - ) + if not data.get("links", {}).get("pages", {}).get("next"): + break + page += 1 return result def list_domains(self) -> list: - data = self._get("/domains") - return [ - NormalizedDomain( - external_id=d.get("name"), - domain_name=d.get("name"), - registrar="digitalocean", - raw=d, - ) - for d in data.get("domains", []) - ] + result = [] + page = 1 + while True: + data = self._get(f"/domains?per_page=100&page={page}") + for d in data.get("domains", []): + result.append( + NormalizedDomain( + external_id=d.get("name"), + domain_name=d.get("name"), + registrar="digitalocean", + raw=d, + ) + ) + if not data.get("links", {}).get("pages", {}).get("next"): + break + page += 1 + return result diff --git a/app/adapters/vultr.py b/app/adapters/vultr.py index 435f066..1a05881 100644 --- a/app/adapters/vultr.py +++ b/app/adapters/vultr.py @@ -49,36 +49,54 @@ class VultrAdapter(BaseAdapter): ) def list_vps(self) -> list: - data = self._get("/instances") result = [] - for inst in data.get("instances", []): - ram_mb = inst.get("ram") or 0 - result.append( - NormalizedVPS( - external_id=inst.get("id"), - name=inst.get("label") or inst.get("id"), - ip_address=inst.get("main_ip"), - region=inst.get("region"), - os=inst.get("os"), - cpu_cores=inst.get("vcpu_count"), - memory_gb=round(ram_mb / 1024, 1) if ram_mb else None, - disk_gb=inst.get("disk"), - status="active" if inst.get("status") == "active" else (inst.get("status") or "unknown"), - monthly_cost=inst.get("monthly_cost"), - currency="USD", - raw=inst, + cursor = "" + while True: + path = "/instances?per_page=100" + if cursor: + path += f"&cursor={cursor}" + data = self._get(path) + for inst in data.get("instances", []): + ram_mb = inst.get("ram") or 0 + result.append( + NormalizedVPS( + external_id=inst.get("id"), + name=inst.get("label") or inst.get("id"), + ip_address=inst.get("main_ip"), + region=inst.get("region"), + os=inst.get("os"), + cpu_cores=inst.get("vcpu_count"), + memory_gb=round(ram_mb / 1024, 1) if ram_mb else None, + disk_gb=inst.get("disk"), + status="active" if inst.get("status") == "active" else (inst.get("status") or "unknown"), + monthly_cost=inst.get("monthly_cost"), + currency="USD", + raw=inst, + ) ) - ) + cursor = data.get("meta", {}).get("links", {}).get("next") or "" + if not cursor: + break return result def list_domains(self) -> list: - data = self._get("/domains") - return [ - NormalizedDomain( - external_id=d.get("domain"), - domain_name=d.get("domain"), - registrar="vultr", - raw=d, - ) - for d in data.get("domains", []) - ] + result = [] + cursor = "" + while True: + path = "/domains?per_page=100" + if cursor: + path += f"&cursor={cursor}" + data = self._get(path) + for d in data.get("domains", []): + result.append( + NormalizedDomain( + external_id=d.get("domain"), + domain_name=d.get("domain"), + registrar="vultr", + raw=d, + ) + ) + cursor = data.get("meta", {}).get("links", {}).get("next") or "" + if not cursor: + break + return result diff --git a/tests/test_adapters.py b/tests/test_adapters.py index 02846f9..446c23d 100644 --- a/tests/test_adapters.py +++ b/tests/test_adapters.py @@ -220,3 +220,34 @@ def test_ai_capabilities_account_only(): def test_registry_supports_ai_types(): for t in ["deepseek-api", "moonshot-api", "openai-api", "minimax-api"]: assert registry.is_supported(t), f"{t} 应被支持" + + +# --------------------------- 分页处理 --------------------------- # +def test_vultr_pagination_cursor(): + adapter = VultrAdapter({"api_key": "x"}) + page1 = {"instances": [{"id": "a", "label": "a", "ram": 1024}], "meta": {"links": {"next": "cursor2"}}} + page2 = {"instances": [{"id": "b", "label": "b", "ram": 2048}], "meta": {"links": {"next": ""}}} + with patch.object(VultrAdapter, "_get", side_effect=[page1, page2]): + result = adapter.list_vps() + assert len(result) == 2 + assert {v.external_id for v in result} == {"a", "b"} + + +def test_do_pagination_page(): + adapter = DigitalOceanAdapter({"api_key": "x"}) + page1 = {"droplets": [{"id": 1, "name": "d1", "memory": 1024, "networks": {"v4": []}}], "links": {"pages": {"next": "x"}}} + page2 = {"droplets": [{"id": 2, "name": "d2", "memory": 1024, "networks": {"v4": []}}], "links": {}} + with patch.object(DigitalOceanAdapter, "_get", side_effect=[page1, page2]): + result = adapter.list_vps() + assert len(result) == 2 + assert {v.external_id for v in result} == {"1", "2"} + + +def test_cloudflare_pagination_result_info(): + adapter = CloudflareAdapter({"api_token": "x"}) + page1 = {"result": [{"id": "z1", "name": "a.com", "status": "active"}], "result_info": {"total_pages": 2, "page": 1}} + page2 = {"result": [{"id": "z2", "name": "b.com", "status": "active"}], "result_info": {"total_pages": 2, "page": 2}} + with patch.object(CloudflareAdapter, "_get", side_effect=[page1, page2]): + result = adapter.list_domains() + assert len(result) == 2 + assert {d.domain_name for d in result} == {"a.com", "b.com"}