test_ip.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. # -*- coding:utf-8 -*-
  2. """
  3. Tests for ddns.ip module including integration tests
  4. """
  5. from __init__ import unittest, patch, MagicMock
  6. from ddns import ip
  7. from ddns.__main__ import get_ip
  8. from ddns.util.http import HttpResponse
  9. class TestIpModule(unittest.TestCase):
  10. """测试IP获取模块"""
  11. def setUp(self):
  12. """设置测试环境"""
  13. self.original_ssl_verify = ip.ssl_verify
  14. def tearDown(self):
  15. """清理测试环境"""
  16. ip.ssl_verify = self.original_ssl_verify
  17. @patch("ddns.ip.request")
  18. def test_url_v4_success(self, mock_request):
  19. """测试自定义URL获取IPv4 - 成功"""
  20. # 模拟成功响应
  21. mock_response = MagicMock()
  22. mock_response.body = "1.2.3.4"
  23. mock_request.return_value = mock_response
  24. result = ip.public_v4("https://test.example.com/ip")
  25. self.assertEqual(result, "1.2.3.4")
  26. mock_request.assert_called_once_with("GET", "https://test.example.com/ip", verify=ip.ssl_verify, retries=2)
  27. @patch("ddns.ip.request")
  28. def test_url_v6_success(self, mock_request):
  29. """测试自定义URL获取IPv6 - 成功"""
  30. # 模拟成功响应
  31. mock_response = MagicMock()
  32. mock_response.body = "2001:db8::1"
  33. mock_request.return_value = mock_response
  34. result = ip.public_v6("https://test.example.com/ipv6")
  35. self.assertEqual(result, "2001:db8::1")
  36. mock_request.assert_called_once_with("GET", "https://test.example.com/ipv6", verify=ip.ssl_verify, retries=2)
  37. @patch("ddns.ip.request")
  38. def test_url_v4_request_failure(self, mock_request):
  39. """测试自定义URL获取IPv4 - 请求失败"""
  40. # 模拟请求异常
  41. mock_request.side_effect = Exception("Network error")
  42. result = ip.public_v4("https://test.example.com/ip")
  43. self.assertIsNone(result)
  44. mock_request.assert_called_once_with("GET", "https://test.example.com/ip", verify=ip.ssl_verify, retries=2)
  45. @patch("ddns.ip.request")
  46. def test_url_v4_invalid_response(self, mock_request):
  47. """测试自定义URL获取IPv4 - 无效响应"""
  48. # 模拟无效响应
  49. mock_response = MagicMock()
  50. mock_response.body = "invalid response"
  51. mock_request.return_value = mock_response
  52. result = ip.public_v4("https://test.example.com/ip")
  53. self.assertIsNone(result)
  54. mock_request.assert_called_once_with("GET", "https://test.example.com/ip", verify=ip.ssl_verify, retries=2)
  55. @patch("ddns.ip.request")
  56. def test_public_v4_multiple_apis_first_success(self, mock_request):
  57. """测试公网IPv4获取 - 多个API第一个成功"""
  58. # 模拟第一个API成功
  59. mock_response = MagicMock()
  60. mock_response.body = "1.2.3.4"
  61. mock_request.return_value = mock_response
  62. result = ip.public_v4()
  63. self.assertEqual(result, "1.2.3.4")
  64. # 应该只调用第一个API
  65. mock_request.assert_called_once_with("GET", ip.PUBLIC_IPV4_APIS[0], verify=ip.ssl_verify, retries=2)
  66. @patch("ddns.ip.request")
  67. def test_public_v4_multiple_apis_fallback_success(self, mock_request):
  68. """测试公网IPv4获取 - 多个API第一个失败第二个成功"""
  69. def mock_request_side_effect(method, url, **kwargs):
  70. if url == ip.PUBLIC_IPV4_APIS[0]:
  71. raise Exception("First API failed")
  72. else:
  73. mock_response = MagicMock()
  74. mock_response.body = "1.2.3.4"
  75. return mock_response
  76. mock_request.side_effect = mock_request_side_effect
  77. result = ip.public_v4()
  78. self.assertEqual(result, "1.2.3.4")
  79. # 应该调用前两个API
  80. self.assertEqual(mock_request.call_count, 2)
  81. mock_request.assert_any_call("GET", ip.PUBLIC_IPV4_APIS[0], verify=ip.ssl_verify, retries=2)
  82. mock_request.assert_any_call("GET", ip.PUBLIC_IPV4_APIS[1], verify=ip.ssl_verify, retries=2)
  83. @patch("ddns.ip.request")
  84. def test_public_v4_multiple_apis_all_fail(self, mock_request):
  85. """测试公网IPv4获取 - 多个API全部失败"""
  86. # 模拟所有API都失败
  87. mock_request.side_effect = Exception("All APIs failed")
  88. result = ip.public_v4()
  89. self.assertIsNone(result)
  90. # 应该调用所有API
  91. self.assertEqual(mock_request.call_count, len(ip.PUBLIC_IPV4_APIS))
  92. @patch("ddns.ip.request")
  93. def test_public_v6_multiple_apis_first_success(self, mock_request):
  94. """测试公网IPv6获取 - 多个API第一个成功"""
  95. # 模拟第一个API成功
  96. mock_response = MagicMock()
  97. mock_response.body = "2001:db8::1"
  98. mock_request.return_value = mock_response
  99. result = ip.public_v6()
  100. self.assertEqual(result, "2001:db8::1")
  101. # 应该只调用第一个API
  102. mock_request.assert_called_once_with("GET", ip.PUBLIC_IPV6_APIS[0], verify=ip.ssl_verify, retries=2)
  103. @patch("ddns.ip.request")
  104. def test_public_v6_multiple_apis_fallback_success(self, mock_request):
  105. """测试公网IPv6获取 - 多个API第一个失败第二个成功"""
  106. def mock_request_side_effect(method, url, **kwargs):
  107. if url == ip.PUBLIC_IPV6_APIS[0]:
  108. raise Exception("First API failed")
  109. else:
  110. mock_response = MagicMock()
  111. mock_response.body = "2001:db8::1"
  112. return mock_response
  113. mock_request.side_effect = mock_request_side_effect
  114. result = ip.public_v6()
  115. self.assertEqual(result, "2001:db8::1")
  116. # 应该调用前两个API
  117. self.assertEqual(mock_request.call_count, 2)
  118. mock_request.assert_any_call("GET", ip.PUBLIC_IPV6_APIS[0], verify=ip.ssl_verify, retries=2)
  119. mock_request.assert_any_call("GET", ip.PUBLIC_IPV6_APIS[1], verify=ip.ssl_verify, retries=2)
  120. def test_public_ipv4_apis_list_exists(self):
  121. """测试IPv4 API列表存在并包含所需的API"""
  122. expected_apis = [
  123. "https://api.ipify.cn",
  124. "https://api.ipify.org",
  125. "https://4.ipw.cn/",
  126. "https://ipinfo.io/ip",
  127. "https://api-ipv4.ip.sb/ip",
  128. "http://checkip.amazonaws.com",
  129. ]
  130. self.assertEqual(ip.PUBLIC_IPV4_APIS, expected_apis)
  131. def test_public_ipv6_apis_list_exists(self):
  132. """测试IPv6 API列表存在并包含所需的API"""
  133. expected_apis = [
  134. "https://api6.ipify.org/",
  135. "https://6.ipw.cn/",
  136. "https://api-ipv6.ip.sb/ip",
  137. "http://ipv6.icanhazip.com",
  138. ]
  139. self.assertEqual(ip.PUBLIC_IPV6_APIS, expected_apis)
  140. @patch("ddns.ip.request")
  141. def test_get_ip_public_mode_fallback(self, mock_request):
  142. """测试通过get_ip使用public模式的自动fallback功能"""
  143. # 模拟第一个API失败,第二个成功
  144. def mock_request_side_effect(method, url, **kwargs):
  145. if "api.ipify.cn" in url:
  146. raise Exception("First API failed")
  147. elif "api.ipify.org" in url:
  148. mock_response = MagicMock()
  149. mock_response.body = "1.2.3.4"
  150. return mock_response
  151. else:
  152. raise Exception("Unexpected URL")
  153. mock_request.side_effect = mock_request_side_effect
  154. # 使用"public"规则获取IPv4地址
  155. result = get_ip("4", ["public"])
  156. self.assertEqual(result, "1.2.3.4")
  157. # 应该调用了前两个API
  158. self.assertEqual(mock_request.call_count, 2)
  159. @patch("ddns.ip.request")
  160. def test_get_ip_url_mode_backward_compatibility(self, mock_request):
  161. """测试通过get_ip使用url:模式的向后兼容性"""
  162. # 模拟成功响应
  163. mock_response = MagicMock()
  164. mock_response.body = "1.2.3.4"
  165. mock_request.return_value = mock_response
  166. # 使用"url:"规则获取IPv4地址
  167. result = get_ip("4", ["url:https://custom.api.com/ip"])
  168. self.assertEqual(result, "1.2.3.4")
  169. # 应该只调用指定的API
  170. mock_request.assert_called_once()
  171. args, kwargs = mock_request.call_args
  172. self.assertEqual(args[1], "https://custom.api.com/ip")
  173. @patch("ddns.ip.request")
  174. def test_get_ip_multiple_rules_limitation(self, mock_request):
  175. """测试get_ip的多规则限制 - 当前实现的限制"""
  176. # 注意:当前get_ip实现有限制 - 如果一个规则返回None,它不会尝试下一个规则
  177. # 只有当规则抛出异常时才会尝试下一个规则
  178. # 这是一个已知限制,不在本次功能实现范围内
  179. # 模拟所有public API都失败(返回无效响应)
  180. mock_response = MagicMock()
  181. mock_response.body = "invalid response"
  182. mock_request.return_value = mock_response
  183. # 使用多个规则:先尝试public,失败后应该尝试url:指定的API
  184. # 但由于当前实现限制,public返回None后不会尝试下一个规则
  185. result = get_ip("4", ["public", "url:https://backup.api.com/ip"])
  186. # 由于当前实现限制,返回None而不是继续尝试backup API
  187. self.assertIsNone(result)
  188. # 只调用了public APIs,没有调用backup API
  189. self.assertEqual(mock_request.call_count, len(ip.PUBLIC_IPV4_APIS))
  190. if __name__ == "__main__":
  191. unittest.main()