Bläddra i källkod

fix: paginate Cloudflare DNS record lookups

AI-Co-Authored-By: Codex
chendeben 1 månad sedan
förälder
incheckning
cf8be647f2
2 ändrade filer med 71 tillägg och 3 borttagningar
  1. 23 3
      src/dns-providers.js
  2. 48 0
      test/dns-providers.test.js

+ 23 - 3
src/dns-providers.js

@@ -97,9 +97,25 @@ class CloudflareProvider {
   }
 
   async listRecords(zoneId, record) {
-    const params = new URLSearchParams({ type: record.type, name: record.host });
-    const response = await this.request(`/zones/${zoneId}/dns_records?${params}`);
-    return response.result || [];
+    const records = [];
+    let page = 1;
+    let totalPages = 1;
+    do {
+      const params = new URLSearchParams({
+        type: record.type,
+        'name.exact': record.host,
+        match: 'all',
+        page: String(page),
+        per_page: '100'
+      });
+      const response = await this.request(`/zones/${zoneId}/dns_records?${params}`);
+      records.push(
+        ...(response.result || []).filter((item) => item.type === record.type && sameDnsName(item.name, record.host))
+      );
+      totalPages = Number(response.result_info?.total_pages || page);
+      page += 1;
+    } while (page <= totalPages);
+    return records;
   }
 
   async zoneId(record, domain) {
@@ -389,6 +405,10 @@ function normalizeValue(value) {
   return String(value || '').replace(/\s+/g, ' ').trim();
 }
 
+function sameDnsName(left, right) {
+  return normalizeZoneName(left) === normalizeZoneName(right);
+}
+
 function relativeName(host, zoneName) {
   const cleanHost = String(host || '').replace(/\.$/, '').toLowerCase();
   const cleanZone = String(zoneName || '').replace(/\.$/, '').toLowerCase();

+ 48 - 0
test/dns-providers.test.js

@@ -39,6 +39,54 @@ test('cloudflare provider tests credentials and replaces duplicate SPF records',
   assert.ok(calls.some((call) => call.method === 'DELETE' && call.url.includes('/dns_records/spf-2')));
 });
 
+test('cloudflare provider paginates exact record lookups before creating SPF records', async () => {
+  const calls = [];
+  globalThis.fetch = async (url, options = {}) => {
+    const urlText = String(url);
+    calls.push({ url: urlText, method: options.method || 'GET', body: options.body });
+    if (urlText.includes('/zones?name=example.com')) {
+      return json({ success: true, result: [{ id: 'zone-1', name: 'example.com' }] });
+    }
+    if (urlText.includes('/dns_records?')) {
+      const params = new URL(urlText).searchParams;
+      const page = Number(params.get('page') || 1);
+      if (page === 1) {
+        return json({
+          success: true,
+          result: [{ id: 'txt-1', type: 'TXT', name: 'example.com', content: 'google-site-verification=abc' }],
+          result_info: { page: 1, total_pages: 2 }
+        });
+      }
+      return json({
+        success: true,
+        result: [
+          { id: 'spf-1', type: 'TXT', name: 'example.com', content: 'v=spf1 include:spf.mailjet.com +include:spf.97admin.com -all' },
+          { id: 'spf-2', type: 'TXT', name: 'example.com', content: 'v=spf1 include:spf.mailjet.com include:spf.97admin.com ip4:192.0.2.10 a:in.example.com -all' }
+        ],
+        result_info: { page: 2, total_pages: 2 }
+      });
+    }
+    return json({ success: true, result: { id: 'ok' } });
+  };
+
+  const result = await applyDnsSetup(domainFixture(), cloudflareCredential(), {
+    records: [
+      {
+        key: 'spf',
+        host: 'example.com',
+        type: 'TXT',
+        value: 'v=spf1 include:spf.mailjet.com include:spf.97admin.com ip4:192.0.2.10 a:in.example.com -all'
+      }
+    ]
+  });
+
+  assert.equal(result.ok, true);
+  assert.ok(calls.some((call) => call.url.includes('name.exact=example.com')));
+  assert.ok(calls.some((call) => call.method === 'PUT' && call.url.includes('/dns_records/spf-1')));
+  assert.ok(calls.some((call) => call.method === 'DELETE' && call.url.includes('/dns_records/spf-2')));
+  assert.equal(calls.some((call) => call.method === 'POST'), false);
+});
+
 test('aliyun provider signs and sends create/update record actions', async () => {
   const actions = [];
   globalThis.fetch = async (url) => {