Repository: NewFuture/DDNS (Python)

Issue:
_update_record 时 extra 优先级高于old record
对于所有provider _update_record 时候优先使用 extra 参数

并添加单元测试验证


Discussion:

Patch:
diff --git a/ddns/provider/cloudflare.py b/ddns/provider/cloudflare.py
index f0abea22d..0d102ae73 100644
--- a/ddns/provider/cloudflare.py
+++ b/ddns/provider/cloudflare.py
@@ -81,9 +81,9 @@ def _update_record(self, zone_id, old_record, value, record_type, ttl, line, ext
         # type: (str, dict, str, str, int | str | None, str | None, dict) -> bool
         """https://developers.cloudflare.com/api/resources/dns/subresources/records/methods/edit/"""
         extra["comment"] = extra.get("comment", self.remark)  # 注释
-        extra["proxied"] = old_record.get("proxied", extra.get("proxied"))  # 保持原有的代理状态
-        extra["tags"] = old_record.get("tags", extra.get("tags"))  # 保持原有的标签
-        extra["settings"] = old_record.get("settings", extra.get("settings"))  # 保持原有的设置
+        extra["proxied"] = extra.get("proxied", old_record.get("proxied"))  # extra优先，保持原有的代理状态作为默认值
+        extra["tags"] = extra.get("tags", old_record.get("tags"))  # extra优先，保持原有的标签作为默认值
+        extra["settings"] = extra.get("settings", old_record.get("settings"))  # extra优先，保持原有的设置作为默认值
         data = self._request(
             "PUT",
             "/{}/dns_records/{}".format(zone_id, old_record["id"]),


Test patch:
diff --git a/tests/test_provider_alidns.py b/tests/test_provider_alidns.py
index f1cfdaec8..e387079c6 100644
--- a/tests/test_provider_alidns.py
+++ b/tests/test_provider_alidns.py
@@ -330,6 +330,32 @@ def test_update_record_with_extra_params(self):
             )
             self.assertTrue(result)
 
+    def test_update_record_extra_priority_over_old_record(self):
+        """Test that extra parameters take priority over old_record values"""
+        provider = AlidnsProvider(self.authid, self.token)
+
+        old_record = {"RecordId": "123456", "RR": "www", "Line": "default", "Priority": 10, "Remark": "Old remark"}
+
+        with patch.object(provider, "_request") as mock_request:
+            mock_request.return_value = {"RecordId": "123456"}
+
+            # extra should override old_record's Priority and Remark
+            extra = {"Priority": 20, "Remark": "New remark from extra"}
+            result = provider._update_record("example.com", old_record, "5.6.7.8", "A", 600, "unicom", extra)
+
+            mock_request.assert_called_once_with(
+                "UpdateDomainRecord",
+                RecordId="123456",
+                Value="5.6.7.8",
+                RR="www",
+                Type="A",
+                TTL=600,
+                Line="unicom",
+                Priority=20,  # Should use extra, not old_record's 10
+                Remark="New remark from extra",  # Should use extra, not old_record's "Old remark"
+            )
+            self.assertTrue(result)
+
     def test_line_configuration_support(self):
         """Test that AlidnsProvider supports line configuration"""
         provider = AlidnsProvider(self.authid, self.token)
diff --git a/tests/test_provider_aliesa.py b/tests/test_provider_aliesa.py
index 8e6e2871b..e15d183af 100644
--- a/tests/test_provider_aliesa.py
+++ b/tests/test_provider_aliesa.py
@@ -250,6 +250,33 @@ def test_update_record_change_detection(self):
         )
         self.assertFalse(needs_update)
 
+    def test_update_record_extra_priority_over_old_record(self):
+        """Test that extra parameters take priority over old_record values in _update_record"""
+        from base_test import patch
+
+        old_record = {
+            "RecordId": "123456",
+            "RecordName": "www.example.com",
+            "Data": {"Value": "192.168.1.100"},
+            "RecordType": "A/AAAA",
+            "Ttl": 300,
+            "Proxied": False,
+        }
+
+        with patch.object(self.provider, "_request") as mock_request:
+            mock_request.return_value = {"RecordId": "123456"}
+
+            # extra should override old_record's Proxied value
+            extra = {"Proxied": True, "Comment": "New comment"}
+            result = self.provider._update_record("zone123", old_record, "192.168.1.200", "A", 600, None, extra)
+
+            # Verify the call was made with extra taking priority
+            mock_request.assert_called_once()
+            call_kwargs = mock_request.call_args[1]
+            self.assertEqual(call_kwargs["Proxied"], True)  # Should use extra, not old_record's False
+            self.assertEqual(call_kwargs["Comment"], "New comment")
+            self.assertTrue(result)
+
     def test_domain_formatting(self):
         """Test domain formatting for various subdomain and main domain combinations"""
         # Import the helper function
diff --git a/tests/test_provider_cloudflare.py b/tests/test_provider_cloudflare.py
index e3f83d970..a3f9936ac 100644
--- a/tests/test_provider_cloudflare.py
+++ b/tests/test_provider_cloudflare.py
@@ -323,15 +323,15 @@ def test_update_record_failure(self):
             self.assertFalse(result)
 
     def test_update_record_with_extra_params(self):
-        """Test _update_record method with extra parameters overriding defaults"""
+        """Test _update_record method with extra parameters overriding old_record values"""
         provider = CloudflareProvider(self.authid, self.token)
 
-        old_record = {"id": "rec123", "name": "www.example.com", "comment": "Old comment", "proxied": False}
+        old_record = {"id": "rec123", "name": "www.example.com", "comment": "Old comment", "proxied": False, "tags": ["old_tag"], "settings": {"old": "setting"}}
 
         with patch.object(provider, "_request") as mock_request:
             mock_request.return_value = {"id": "rec123"}
 
-            extra = {"comment": "New comment", "proxied": True, "priority": 20}
+            extra = {"comment": "New comment", "proxied": True, "priority": 20, "tags": ["new_tag"]}
             result = provider._update_record("zone123", old_record, "5.6.7.8", "A", 600, None, extra)
 
             mock_request.assert_called_once_with(
@@ -342,10 +342,10 @@ def test_update_record_with_extra_params(self):
                 content="5.6.7.8",
                 ttl=600,
                 comment="New comment",  # extra.get("comment", self.remark)
-                proxied=False,  # old_record.get("proxied", extra.get("proxied"))
+                proxied=True,  # extra.get("proxied", old_record.get("proxied")) - extra takes priority
                 priority=20,  # From extra
-                tags=None,
-                settings=None,
+                tags=["new_tag"],  # extra.get("tags", old_record.get("tags")) - extra takes priority
+                settings={"old": "setting"},  # extra.get("settings", old_record.get("settings")) - falls back to old_record
             )
             self.assertTrue(result)
 
diff --git a/tests/test_provider_dnscom.py b/tests/test_provider_dnscom.py
index 9afb3a281..72af26b66 100644
--- a/tests/test_provider_dnscom.py
+++ b/tests/test_provider_dnscom.py
@@ -286,6 +286,23 @@ def test_update_record_with_extra_params(self):
             )
             self.assertTrue(result)
 
+    def test_update_record_extra_priority_over_old_record(self):
+        """Test that extra parameters take priority over old_record values"""
+        provider = DnscomProvider(self.authid, self.token)
+
+        old_record = {"recordID": "123456", "remark": "Old remark"}
+
+        with patch.object(provider, "_request") as mock_request:
+            mock_request.return_value = {"success": True}
+
+            # extra should override old_record's remark
+            extra = {"remark": "New remark from extra"}
+            result = provider._update_record("example.com", old_record, "5.6.7.8", "A", 600, "1", extra)
+
+            # Verify that extra["remark"] was set correctly with priority
+            self.assertEqual(extra["remark"], "New remark from extra")
+            self.assertTrue(result)
+
     def test_update_record_failure(self):
         """Test _update_record method with failed update"""
         provider = DnscomProvider(self.authid, self.token)
diff --git a/tests/test_provider_dnspod.py b/tests/test_provider_dnspod.py
index 98c773c5b..f9d472244 100644
--- a/tests/test_provider_dnspod.py
+++ b/tests/test_provider_dnspod.py
@@ -320,6 +320,24 @@ def test_update_record_with_extra_params(self, mock_request):
         call_args = mock_request.call_args[1]
         self.assertEqual(call_args["extra"], extra)
 
+    @patch("ddns.provider.dnspod.DnspodProvider._request")
+    def test_update_record_extra_priority_over_old_record(self, mock_request):
+        """Test that extra parameters take priority over old_record values"""
+        mock_request.return_value = {"record": {"id": "12345", "name": "www", "value": "192.168.1.2"}}
+
+        old_record = {"id": "12345", "name": "www", "line": "电信", "weight": 10}
+        # extra should override old_record's weight
+        extra = {"weight": 20, "mx": 5}
+
+        result = self.provider._update_record("zone123", old_record, "192.168.1.2", "A", None, None, extra)
+
+        self.assertTrue(result)
+        call_args = mock_request.call_args[1]
+        self.assertEqual(call_args["extra"], extra)
+        # Verify extra contains the new weight value
+        self.assertEqual(call_args["extra"]["weight"], 20)
+        self.assertEqual(call_args["extra"]["mx"], 5)
+
     def test_request_with_none_response(self):
         """Test _request method when HTTP returns None"""
         with patch("ddns.provider.dnspod.DnspodProvider._http") as mock_http:
diff --git a/tests/test_provider_huaweidns.py b/tests/test_provider_huaweidns.py
index 325f82a24..ed6eb17c1 100644
--- a/tests/test_provider_huaweidns.py
+++ b/tests/test_provider_huaweidns.py
@@ -282,6 +282,30 @@ def test_update_record_with_extra_params(self):
             )
             self.assertTrue(result)
 
+    def test_update_record_extra_priority_over_old_record(self):
+        """Test that extra parameters take priority over old_record values"""
+        provider = HuaweiDNSProvider(self.authid, self.token)
+
+        old_record = {"id": "rec123", "name": "www.example.com.", "ttl": 300, "description": "Old description"}
+
+        with patch.object(provider, "_request") as mock_request:
+            mock_request.return_value = {"id": "rec123"}
+
+            # extra should override old_record's description
+            extra = {"description": "New description from extra"}
+            result = provider._update_record("zone123", old_record, "5.6.7.8", "A", 600, None, extra)
+
+            mock_request.assert_called_once_with(
+                "PUT",
+                "/v2.1/zones/zone123/recordsets/rec123",
+                name="www.example.com.",
+                type="A",
+                records=["5.6.7.8"],
+                ttl=600,
+                description="New description from extra",  # Should use extra, not old_record
+            )
+            self.assertTrue(result)
+
     def test_update_record_failure(self):
         """Test _update_record method with failed update"""
         provider = HuaweiDNSProvider(self.authid, self.token)
diff --git a/tests/test_provider_namesilo.py b/tests/test_provider_namesilo.py
index 05e0e8f12..4d23da01d 100644
--- a/tests/test_provider_namesilo.py
+++ b/tests/test_provider_namesilo.py
@@ -247,6 +247,29 @@ def test_update_record_failure(self, mock_request):
 
         self.assertFalse(result)
 
+    @patch.object(NamesiloProvider, "_request")
+    def test_update_record_extra_priority_over_old_record(self, mock_request):
+        """Test that extra parameters take priority over old_record values"""
+        mock_request.return_value = {"reply": {"code": "300"}}
+
+        old_record = {"record_id": "12345", "host": "test", "type": "A", "value": "1.2.3.4", "ttl": "3600"}
+
+        # NameSilo doesn't use many extra params in update, but we verify it doesn't break
+        extra = {"custom_field": "custom_value"}
+        result = self.provider._update_record("example.com", old_record, "5.6.7.8", "A", 7200, None, extra)
+
+        # Verify the basic call was made (NameSilo doesn't support extra params in update)
+        mock_request.assert_called_once_with(
+            "dnsUpdateRecord",
+            rrid="12345",
+            domain="example.com",
+            rrhost="test",
+            rrvalue="5.6.7.8",
+            rrtype="A",
+            rrttl=7200,
+        )
+        self.assertTrue(result)
+
     @patch.object(NamesiloProvider, "_http")
     def test_integration_set_record_update_flow(self, mock_http):
         """Integration test for complete set_record flow with update"""
diff --git a/tests/test_provider_tencentcloud.py b/tests/test_provider_tencentcloud.py
index eb818d118..1f1e07873 100644
--- a/tests/test_provider_tencentcloud.py
+++ b/tests/test_provider_tencentcloud.py
@@ -229,6 +229,27 @@ def test_update_record_failure(self, mock_http):
 
         self.assertFalse(result)
 
+    @patch.object(TencentCloudProvider, "_http")
+    def test_update_record_extra_priority_over_old_record(self, mock_http):
+        """Test that extra parameters take priority over old_record values"""
+        import json
+
+        mock_http.return_value = {"Response": {"RecordId": 123456}}
+
+        old_record = {"RecordId": 123456, "Domain": "example.com", "DomainId": 12345678, "Name": "www", "Line": "默认", "Remark": "Old remark"}
+
+        # extra should override old_record's Remark
+        extra = {"Remark": "New remark from extra", "Status": "ENABLE"}
+        result = self.provider._update_record("12345678", old_record, "5.6.7.8", "A", 600, "联通", extra)
+
+        # Verify the call was made with correct parameters
+        self.assertTrue(result)
+        call_kwargs = mock_http.call_args[1]
+        # extra["Remark"] should be set with priority
+        body_dict = json.loads(call_kwargs["body"])  # Parse the JSON body
+        self.assertEqual(body_dict["Remark"], "New remark from extra")
+        self.assertEqual(body_dict["Status"], "ENABLE")
+
     @patch("ddns.provider.tencentcloud.strftime")
     @patch("ddns.provider.tencentcloud.time")
     @patch.object(TencentCloudProvider, "_http")