mirror of
https://github.com/NewFuture/DDNS.git
synced 2026-10-19 16:15:45 +00:00
* Initial plan * refactor(tests): replace authid with id in all tests for consistency Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>
544 lines
22 KiB
Python
544 lines
22 KiB
Python
# coding=utf-8
|
|
"""
|
|
Unit tests for AlidnsProvider
|
|
|
|
@author: Github Copilot
|
|
"""
|
|
|
|
from base_test import BaseProviderTestCase, unittest, patch
|
|
from ddns.provider.alidns import AlidnsProvider
|
|
|
|
|
|
class TestAlidnsProvider(BaseProviderTestCase):
|
|
"""Test cases for AlidnsProvider"""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures"""
|
|
super(TestAlidnsProvider, self).setUp()
|
|
self.id = "test_access_key_id"
|
|
self.token = "test_access_key_secret"
|
|
|
|
def test_class_constants(self):
|
|
"""Test AlidnsProvider class constants"""
|
|
self.assertEqual(AlidnsProvider.endpoint, "https://alidns.aliyuncs.com")
|
|
self.assertEqual(AlidnsProvider.content_type, "application/x-www-form-urlencoded")
|
|
self.assertTrue(AlidnsProvider.decode_response)
|
|
|
|
def test_init_with_basic_config(self):
|
|
"""Test AlidnsProvider initialization with basic configuration"""
|
|
provider = AlidnsProvider(self.id, self.token)
|
|
self.assertEqual(provider.id, self.id)
|
|
self.assertEqual(provider.token, self.token)
|
|
self.assertEqual(provider.endpoint, "https://alidns.aliyuncs.com")
|
|
|
|
def test_request_basic(self):
|
|
"""Test _request method with basic parameters"""
|
|
provider = AlidnsProvider(self.id, self.token)
|
|
|
|
# Only mock the HTTP call to avoid actual network requests
|
|
with patch.object(provider, "_http") as mock_http:
|
|
mock_http.return_value = {"Success": True}
|
|
|
|
result = provider._request("TestAction", DomainName="example.com")
|
|
|
|
# Verify HTTP call was made with correct method and path
|
|
mock_http.assert_called_once()
|
|
call_args = mock_http.call_args
|
|
self.assertEqual(call_args[0], ("POST", "/"))
|
|
|
|
# Verify body and headers are present
|
|
self.assertIn("body", call_args[1])
|
|
self.assertIn("headers", call_args[1])
|
|
|
|
# Verify headers contain required v3 signature fields
|
|
headers = call_args[1]["headers"]
|
|
self.assertIn("Authorization", headers)
|
|
self.assertIn("x-acs-action", headers)
|
|
self.assertIn("x-acs-date", headers)
|
|
self.assertIn("x-acs-version", headers)
|
|
self.assertEqual(headers["x-acs-action"], "TestAction")
|
|
|
|
# Verify body is form-encoded
|
|
body = call_args[1]["body"]
|
|
self.assertIn("DomainName=example.com", body)
|
|
|
|
self.assertEqual(result, {"Success": True})
|
|
|
|
def test_request_filters_none_params(self):
|
|
"""Test _request method filters out None parameters"""
|
|
provider = AlidnsProvider(self.id, self.token)
|
|
|
|
# Only mock the HTTP call
|
|
with patch.object(provider, "_http") as mock_http:
|
|
mock_http.return_value = {}
|
|
|
|
provider._request("TestAction", DomainName="example.com", TTL=None, Line=None)
|
|
|
|
# Verify HTTP call was made
|
|
mock_http.assert_called_once()
|
|
|
|
# Body should not contain None parameters
|
|
call_args = mock_http.call_args[1]
|
|
body = call_args.get("body", "")
|
|
self.assertNotIn("TTL=None", body)
|
|
self.assertNotIn("Line=None", body)
|
|
self.assertIn("DomainName=example.com", body)
|
|
|
|
def test_split_zone_and_sub_success(self):
|
|
"""Test _split_zone_and_sub method with successful response"""
|
|
provider = AlidnsProvider(self.id, self.token)
|
|
|
|
with patch.object(provider, "_http") as mock_http:
|
|
mock_http.return_value = {"DomainName": "example.com", "RR": "sub"}
|
|
|
|
main, sub, zone_id = provider._split_zone_and_sub("sub.example.com")
|
|
|
|
mock_http.assert_called_once()
|
|
# Verify GetMainDomainName API was called via headers
|
|
call_headers = mock_http.call_args[1]["headers"]
|
|
self.assertEqual(call_headers["x-acs-action"], "GetMainDomainName")
|
|
|
|
# Verify the input parameter in body
|
|
call_body = mock_http.call_args[1]["body"]
|
|
self.assertIn("InputString=sub.example.com", call_body)
|
|
|
|
self.assertEqual(main, "example.com")
|
|
self.assertEqual(sub, "sub")
|
|
self.assertEqual(zone_id, "example.com")
|
|
|
|
def test_split_zone_and_sub_not_found(self):
|
|
"""Test _split_zone_and_sub method when domain is not found"""
|
|
provider = AlidnsProvider(self.id, self.token)
|
|
|
|
with patch.object(provider, "_http") as mock_http:
|
|
mock_http.return_value = {}
|
|
|
|
main, sub, zone_id = provider._split_zone_and_sub("notfound.com")
|
|
|
|
mock_http.assert_called_once()
|
|
self.assertIsNone(main)
|
|
self.assertIsNone(sub)
|
|
self.assertEqual(zone_id, "notfound.com")
|
|
|
|
def test_query_record_success_single(self):
|
|
"""Test _query_record method with single record found"""
|
|
provider = AlidnsProvider(self.id, self.token)
|
|
|
|
with patch.object(provider, "_http") as mock_http:
|
|
mock_http.return_value = {
|
|
"DomainRecords": {"Record": [{"RR": "www", "RecordId": "123", "Value": "1.2.3.4", "Type": "A"}]}
|
|
}
|
|
|
|
result = provider._query_record("example.com", "www", "example.com", "A", None, {})
|
|
|
|
mock_http.assert_called_once()
|
|
# Verify DescribeSubDomainRecords API was called via headers
|
|
call_headers = mock_http.call_args[1]["headers"]
|
|
self.assertEqual(call_headers["x-acs-action"], "DescribeSubDomainRecords")
|
|
|
|
# Verify parameters in body
|
|
call_body = mock_http.call_args[1]["body"]
|
|
self.assertIn("SubDomain=www.example.com", call_body)
|
|
self.assertIn("DomainName=example.com", call_body)
|
|
self.assertIn("Type=A", call_body)
|
|
|
|
self.assertIsNotNone(result)
|
|
if result: # Type narrowing for mypy
|
|
self.assertEqual(result["RecordId"], "123")
|
|
self.assertEqual(result["RR"], "www")
|
|
|
|
def test_query_record_not_found(self):
|
|
"""Test _query_record method when no matching record is found"""
|
|
provider = AlidnsProvider(self.id, self.token)
|
|
|
|
with patch.object(provider, "_http") as mock_http:
|
|
mock_http.return_value = {"DomainRecords": {"Record": []}}
|
|
|
|
result = provider._query_record("example.com", "www", "example.com", "A", None, {})
|
|
|
|
self.assertIsNone(result)
|
|
|
|
def test_query_record_empty_response(self):
|
|
"""Test _query_record method with empty records response"""
|
|
provider = AlidnsProvider(self.id, self.token)
|
|
|
|
with patch.object(provider, "_http") as mock_http:
|
|
mock_http.return_value = {"DomainRecords": {"Record": []}}
|
|
|
|
result = provider._query_record("example.com", "www", "example.com", "A", None, {})
|
|
|
|
self.assertIsNone(result)
|
|
|
|
def test_query_record_with_extra_params(self):
|
|
"""Test _query_record method with extra parameters"""
|
|
provider = AlidnsProvider(self.id, self.token)
|
|
|
|
with patch.object(provider, "_http") as mock_http:
|
|
mock_http.return_value = {"DomainRecords": {"Record": []}}
|
|
|
|
extra = {"Lang": "en", "Status": "Enable"}
|
|
provider._query_record("example.com", "www", "example.com", "A", "default", extra)
|
|
|
|
mock_http.assert_called_once()
|
|
# Verify extra parameters are included in the request
|
|
call_body = mock_http.call_args[1]["body"]
|
|
self.assertIn("Lang=en", call_body)
|
|
self.assertIn("Status=Enable", call_body)
|
|
self.assertIn("Line=default", call_body)
|
|
|
|
def test_create_record_success(self):
|
|
"""Test _create_record method with successful creation"""
|
|
provider = AlidnsProvider(self.id, self.token)
|
|
|
|
with patch.object(provider, "_request") as mock_request:
|
|
mock_request.return_value = {"RecordId": "123456"}
|
|
|
|
result = provider._create_record("example.com", "www", "example.com", "1.2.3.4", "A", 300, "default", {})
|
|
|
|
mock_request.assert_called_once_with(
|
|
"AddDomainRecord",
|
|
DomainName="example.com",
|
|
RR="www",
|
|
Value="1.2.3.4",
|
|
Type="A",
|
|
TTL=300,
|
|
Line="default",
|
|
)
|
|
self.assertTrue(result)
|
|
|
|
def test_create_record_failure(self):
|
|
"""Test _create_record method with failed creation"""
|
|
provider = AlidnsProvider(self.id, self.token)
|
|
|
|
with patch.object(provider, "_request") as mock_request:
|
|
mock_request.return_value = {"Error": "Invalid domain"}
|
|
|
|
result = provider._create_record("example.com", "www", "example.com", "1.2.3.4", "A", None, None, {})
|
|
|
|
self.assertFalse(result)
|
|
|
|
def test_create_record_with_extra_params(self):
|
|
"""Test _create_record method with extra parameters"""
|
|
provider = AlidnsProvider(self.id, self.token)
|
|
|
|
with patch.object(provider, "_request") as mock_request:
|
|
mock_request.return_value = {"RecordId": "123456"}
|
|
|
|
extra = {"Priority": 10, "Remark": "Test record"}
|
|
result = provider._create_record("t.com", "www", "t.com", "1.2.3.4", "A", 300, "default", extra)
|
|
|
|
mock_request.assert_called_once_with(
|
|
"AddDomainRecord",
|
|
DomainName="t.com",
|
|
RR="www",
|
|
Value="1.2.3.4",
|
|
Type="A",
|
|
TTL=300,
|
|
Line="default",
|
|
Priority=10,
|
|
Remark="Test record",
|
|
)
|
|
self.assertTrue(result)
|
|
|
|
def test_update_record_success(self):
|
|
"""Test _update_record method with successful update"""
|
|
provider = AlidnsProvider(self.id, self.token)
|
|
|
|
old_record = {"RecordId": "123456", "RR": "www", "Line": "default"}
|
|
|
|
with patch.object(provider, "_request") as mock_request:
|
|
mock_request.return_value = {"RecordId": "123456"}
|
|
|
|
result = provider._update_record("example.com", old_record, "5.6.7.8", "A", 600, "unicom", {})
|
|
|
|
mock_request.assert_called_once_with(
|
|
"UpdateDomainRecord", RecordId="123456", Value="5.6.7.8", RR="www", Type="A", TTL=600, Line="unicom"
|
|
)
|
|
self.assertTrue(result)
|
|
|
|
def test_update_record_with_fallback_line(self):
|
|
"""Test _update_record method uses old record's line when line is None"""
|
|
provider = AlidnsProvider(self.id, self.token)
|
|
|
|
old_record = {"RecordId": "123456", "RR": "www", "Line": "default"}
|
|
|
|
with patch.object(provider, "_request") as mock_request:
|
|
mock_request.return_value = {"RecordId": "123456"}
|
|
|
|
result = provider._update_record("example.com", old_record, "5.6.7.8", "A", None, None, {})
|
|
|
|
mock_request.assert_called_once_with(
|
|
"UpdateDomainRecord",
|
|
RecordId="123456",
|
|
Value="5.6.7.8",
|
|
RR="www",
|
|
Type="A",
|
|
TTL=None,
|
|
Line="default", # Should use old record's line
|
|
)
|
|
self.assertTrue(result)
|
|
|
|
def test_update_record_failure(self):
|
|
"""Test _update_record method with failed update"""
|
|
provider = AlidnsProvider(self.id, self.token)
|
|
|
|
old_record = {"RecordId": "123456", "RR": "www", "Line": "default"}
|
|
|
|
with patch.object(provider, "_request") as mock_request:
|
|
mock_request.return_value = {"Error": "Record not found"}
|
|
|
|
result = provider._update_record("example.com", old_record, "5.6.7.8", "A", None, None, {})
|
|
|
|
self.assertFalse(result)
|
|
|
|
def test_update_record_no_changes(self):
|
|
"""Test _update_record method when no changes are detected"""
|
|
provider = AlidnsProvider(self.id, self.token)
|
|
|
|
old_record = {"RecordId": "123456", "RR": "www", "Value": "1.2.3.4", "Type": "A", "TTL": 300, "Line": "default"}
|
|
|
|
with patch.object(provider, "_request") as mock_request:
|
|
# Same value, type, and TTL should skip update
|
|
result = provider._update_record("example.com", old_record, "1.2.3.4", "A", 300, "default", {})
|
|
|
|
# Should return True without making any API calls
|
|
self.assertTrue(result)
|
|
mock_request.assert_not_called()
|
|
|
|
def test_update_record_with_extra_params(self):
|
|
"""Test _update_record method with extra parameters"""
|
|
provider = AlidnsProvider(self.id, self.token)
|
|
|
|
old_record = {"RecordId": "123456", "RR": "www", "Line": "default"}
|
|
|
|
with patch.object(provider, "_request") as mock_request:
|
|
mock_request.return_value = {"RecordId": "123456"}
|
|
|
|
extra = {"Priority": 20, "Remark": "Updated record"}
|
|
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,
|
|
Remark="Updated record",
|
|
)
|
|
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.id, 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.id, self.token)
|
|
|
|
old_record = {"RecordId": "123456", "RR": "www", "Line": "default"}
|
|
|
|
with patch.object(provider, "_request") as mock_request:
|
|
mock_request.return_value = {"RecordId": "123456"}
|
|
|
|
# Test with custom line parameter
|
|
result = provider._update_record("example.com", old_record, "5.6.7.8", "A", 600, "telecom", {})
|
|
|
|
mock_request.assert_called_once_with(
|
|
"UpdateDomainRecord",
|
|
RecordId="123456",
|
|
Value="5.6.7.8",
|
|
RR="www",
|
|
Type="A",
|
|
TTL=600,
|
|
Line="telecom", # Should use the provided line parameter
|
|
)
|
|
self.assertTrue(result)
|
|
|
|
def test_create_record_with_line(self):
|
|
"""Test _create_record method with line parameter"""
|
|
provider = AlidnsProvider(self.id, self.token)
|
|
|
|
with patch.object(provider, "_request") as mock_request:
|
|
mock_request.return_value = {"RecordId": "123456"}
|
|
|
|
result = provider._create_record("example.com", "www", "example.com", "1.2.3.4", "A", 300, "unicom", {})
|
|
|
|
mock_request.assert_called_once_with(
|
|
"AddDomainRecord", DomainName="example.com", RR="www", Value="1.2.3.4", Type="A", TTL=300, Line="unicom"
|
|
)
|
|
self.assertTrue(result)
|
|
|
|
|
|
class TestAlidnsProviderIntegration(BaseProviderTestCase):
|
|
"""Integration test cases for AlidnsProvider - testing with minimal mocking"""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures"""
|
|
super(TestAlidnsProviderIntegration, self).setUp()
|
|
self.id = "test_access_key_id"
|
|
self.token = "test_access_key_secret"
|
|
|
|
def test_full_workflow_create_new_record(self):
|
|
"""Test complete workflow for creating a new record"""
|
|
provider = AlidnsProvider(self.id, self.token)
|
|
|
|
# Mock only the HTTP layer to simulate API responses
|
|
with patch.object(provider, "_http") as mock_http:
|
|
# Simulate API responses in order: GetMainDomainName, DescribeSubDomainRecords, AddDomainRecord
|
|
mock_http.side_effect = [
|
|
{"DomainName": "example.com", "RR": "www"}, # _split_zone_and_sub response
|
|
{"DomainRecords": {"Record": []}}, # _query_record response (no existing record)
|
|
{"RecordId": "123456"}, # _create_record response
|
|
]
|
|
|
|
result = provider.set_record("www.example.com", "1.2.3.4", "A", 300, "default")
|
|
|
|
self.assertTrue(result)
|
|
# Verify the actual HTTP calls made - should be 3 calls
|
|
self.assertEqual(mock_http.call_count, 3)
|
|
|
|
# Check that proper API actions were called by examining request headers
|
|
call_actions = [call[1]["headers"]["x-acs-action"] for call in mock_http.call_args_list]
|
|
self.assertIn("GetMainDomainName", call_actions)
|
|
self.assertIn("DescribeSubDomainRecords", call_actions)
|
|
self.assertIn("AddDomainRecord", call_actions)
|
|
|
|
def test_full_workflow_update_existing_record(self):
|
|
"""Test complete workflow for updating an existing record"""
|
|
provider = AlidnsProvider(self.id, self.token)
|
|
|
|
with patch.object(provider, "_http") as mock_http:
|
|
# Simulate API responses: GetMainDomainName, DescribeSubDomainRecords, UpdateDomainRecord
|
|
mock_http.side_effect = [
|
|
{"DomainName": "example.com", "RR": "www"}, # _split_zone_and_sub response
|
|
{ # _query_record response (existing record found)
|
|
"DomainRecords": {
|
|
"Record": [
|
|
{"RecordId": "123456", "RR": "www", "Value": "5.6.7.8", "Type": "A", "Line": "default"}
|
|
]
|
|
}
|
|
},
|
|
{"RecordId": "123456"}, # _update_record response
|
|
]
|
|
|
|
result = provider.set_record("www.example.com", "1.2.3.4", "A", 300, "default")
|
|
|
|
self.assertTrue(result)
|
|
# Verify 3 HTTP calls were made
|
|
self.assertEqual(mock_http.call_count, 3)
|
|
|
|
# Check that UpdateDomainRecord was called
|
|
call_actions = [call[1]["headers"]["x-acs-action"] for call in mock_http.call_args_list]
|
|
self.assertIn("UpdateDomainRecord", call_actions)
|
|
|
|
def test_full_workflow_zone_not_found(self):
|
|
"""Test complete workflow when zone is not found"""
|
|
provider = AlidnsProvider(self.id, self.token)
|
|
|
|
with patch.object(provider, "_http") as mock_http:
|
|
# Simulate API returning empty response for zone query
|
|
mock_http.return_value = {}
|
|
|
|
# Should return False when zone not found
|
|
result = provider.set_record("www.nonexistent.com", "1.2.3.4", "A")
|
|
self.assertFalse(result)
|
|
|
|
def test_full_workflow_create_failure(self):
|
|
"""Test complete workflow when record creation fails"""
|
|
provider = AlidnsProvider(self.id, self.token)
|
|
|
|
with patch.object(provider, "_http") as mock_http:
|
|
# Simulate responses: zone found, no existing record, creation fails
|
|
mock_http.side_effect = [
|
|
{"DomainName": "example.com", "RR": "www"}, # _split_zone_and_sub response
|
|
{"DomainRecords": {"Record": []}}, # _query_record response (no existing record)
|
|
{"Error": "API error", "Code": "InvalidParameter"}, # _create_record fails
|
|
]
|
|
|
|
result = provider.set_record("www.example.com", "1.2.3.4", "A")
|
|
|
|
self.assertFalse(result)
|
|
|
|
def test_full_workflow_update_failure(self):
|
|
"""Test complete workflow when record update fails"""
|
|
provider = AlidnsProvider(self.id, self.token)
|
|
|
|
with patch.object(provider, "_http") as mock_http:
|
|
# Simulate responses: zone found, existing record found, update fails
|
|
mock_http.side_effect = [
|
|
{"DomainName": "example.com", "RR": "www"}, # _split_zone_and_sub response
|
|
{ # _query_record response (existing record found)
|
|
"DomainRecords": {
|
|
"Record": [
|
|
{"RecordId": "123456", "RR": "www", "Value": "5.6.7.8", "Type": "A", "Line": "default"}
|
|
]
|
|
}
|
|
},
|
|
{"Error": "API error", "Code": "InvalidParameter"}, # _update_record fails
|
|
]
|
|
|
|
result = provider.set_record("www.example.com", "1.2.3.4", "A")
|
|
|
|
self.assertFalse(result)
|
|
|
|
def test_full_workflow_with_options(self):
|
|
"""Test complete workflow with additional options like ttl and line"""
|
|
provider = AlidnsProvider(self.id, self.token)
|
|
|
|
with patch.object(provider, "_http") as mock_http:
|
|
# Simulate successful creation with custom options
|
|
mock_http.side_effect = [
|
|
{"DomainName": "example.com", "RR": "www"}, # _split_zone_and_sub response
|
|
{"DomainRecords": {"Record": []}}, # _query_record response (no existing record)
|
|
{"RecordId": "123456"}, # _create_record response
|
|
]
|
|
|
|
result = provider.set_record("www.example.com", "1.2.3.4", "A", 600, "unicom")
|
|
|
|
self.assertTrue(result)
|
|
# Verify that extra parameters are passed through correctly
|
|
self.assertEqual(mock_http.call_count, 3)
|
|
|
|
# Check that the create call contains the correct parameters
|
|
# Find the AddDomainRecord call (should be the last one)
|
|
add_record_call = None
|
|
for call in mock_http.call_args_list:
|
|
if call[1]["headers"]["x-acs-action"] == "AddDomainRecord":
|
|
add_record_call = call
|
|
break
|
|
|
|
self.assertIsNotNone(add_record_call)
|
|
if add_record_call:
|
|
create_body = add_record_call[1]["body"]
|
|
self.assertIn("TTL=600", create_body)
|
|
self.assertIn("Line=unicom", create_body)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|