test_provider_base_simple.py 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. # coding=utf-8
  2. """
  3. Unit tests for SimpleProvider
  4. @author: GitHub Copilot
  5. """
  6. from base_test import BaseProviderTestCase, unittest, MagicMock
  7. from ddns.provider._base import SimpleProvider, TYPE_FORM, encode_params
  8. class _TestableSimpleProvider(SimpleProvider):
  9. """Test implementation of SimpleProvider for testing purposes"""
  10. endpoint = "https://api.example.com"
  11. def set_record(self, domain, value, record_type="A", ttl=None, line=None, **extra):
  12. """Test implementation of set_record"""
  13. self.logger.debug("_TestableSimpleProvider: %s(%s) => %s", domain, record_type, value)
  14. return True
  15. class _TestableSimpleProviderClass(BaseProviderTestCase):
  16. """Test cases for SimpleProvider base class"""
  17. def setUp(self):
  18. """Set up test fixtures"""
  19. super(_TestableSimpleProviderClass, self).setUp()
  20. def test_init_with_basic_config(self):
  21. """Test SimpleProvider initialization with basic configuration"""
  22. provider = _TestableSimpleProvider(self.id, self.token)
  23. self.assertEqual(provider.id, self.id)
  24. self.assertEqual(provider.token, self.token)
  25. self.assertEqual(provider.endpoint, "https://api.example.com")
  26. self.assertEqual(provider.content_type, TYPE_FORM)
  27. self.assertTrue(provider.decode_response)
  28. self.assertEqual(provider._ssl, "auto") # Default verify_ssl should be "auto"
  29. self.assertEqual(provider._zone_map, {}) # Should initialize empty zone map
  30. def test_init_with_logger(self):
  31. """Test SimpleProvider initialization with logger"""
  32. logger = MagicMock()
  33. provider = _TestableSimpleProvider(self.id, self.token, logger=logger)
  34. logger.getChild.assert_called_once_with("_TestableSimpleProvider")
  35. self.assertIsNotNone(provider.logger)
  36. def test_init_with_options(self):
  37. """Test SimpleProvider initialization with additional options"""
  38. options = {"debug": True, "timeout": 30}
  39. provider = _TestableSimpleProvider(self.id, self.token, ssl=False, **options)
  40. self.assertEqual(provider.options, options)
  41. self.assertFalse(provider._ssl) # Should respect verify_ssl parameter
  42. def test_init_with_verify_ssl_string(self):
  43. """Test SimpleProvider initialization with verify_ssl as string"""
  44. provider = _TestableSimpleProvider(self.id, self.token, ssl="/path/to/cert")
  45. self.assertEqual(provider._ssl, "/path/to/cert")
  46. def test_init_with_verify_ssl_false(self):
  47. """Test SimpleProvider initialization with verify_ssl as False"""
  48. provider = _TestableSimpleProvider(self.id, self.token, ssl=False)
  49. self.assertFalse(provider._ssl)
  50. def test_init_with_verify_ssl_truthy_value(self):
  51. """Test SimpleProvider initialization with verify_ssl as truthy value"""
  52. provider = _TestableSimpleProvider(self.id, self.token, ssl=1) # type: ignore
  53. self.assertEqual(provider._ssl, 1) # Should preserve the exact value
  54. def test_init_with_verify_ssl_falsy_value(self):
  55. """Test SimpleProvider initialization with verify_ssl as falsy value"""
  56. provider = _TestableSimpleProvider(self.id, self.token, ssl=0) # type: ignore
  57. self.assertEqual(provider._ssl, 0) # Should preserve the exact value
  58. def test_validate_missing_id(self):
  59. """Test _validate method with missing id"""
  60. with self.assertRaises(ValueError) as cm:
  61. _TestableSimpleProvider(None, self.token) # type: ignore
  62. self.assertIn("id must be configured", str(cm.exception))
  63. def test_validate_missing_token(self):
  64. """Test _validate method with missing token"""
  65. with self.assertRaises(ValueError) as cm:
  66. _TestableSimpleProvider(self.id, None) # type: ignore
  67. self.assertIn("token must be configured", str(cm.exception))
  68. def test_validate_empty_id(self):
  69. """Test _validate method with empty id"""
  70. with self.assertRaises(ValueError) as cm:
  71. _TestableSimpleProvider("", self.token)
  72. self.assertIn("id must be configured", str(cm.exception))
  73. def test_validate_empty_token(self):
  74. """Test _validate method with empty token"""
  75. with self.assertRaises(ValueError) as cm:
  76. _TestableSimpleProvider(self.id, "")
  77. self.assertIn("token must be configured", str(cm.exception))
  78. def test_encode_dict(self):
  79. """Test _encode method with dictionary"""
  80. params = {"key1": "value1", "key2": "value2"}
  81. result = encode_params(params)
  82. # Result should be URL-encoded string
  83. self.assertIn("key1=value1", result)
  84. self.assertIn("key2=value2", result)
  85. self.assertIn("&", result)
  86. def test_mask_sensitive_data_basic(self):
  87. """Test _mask_sensitive_data method with basic token"""
  88. provider = _TestableSimpleProvider(self.id, "secret123")
  89. data = "url?token=secret123&other=value"
  90. result = provider._mask_sensitive_data(data) # type: str # type: ignore
  91. self.assertNotIn("secret123", result)
  92. self.assertIn("se***23", result)
  93. def test_mask_sensitive_data_short_token(self):
  94. """Test _mask_sensitive_data method with short token"""
  95. provider = _TestableSimpleProvider(self.id, "abc")
  96. data = "url?token=abc&other=value"
  97. result = provider._mask_sensitive_data(data) # type: str # type: ignore
  98. self.assertNotIn("abc", result)
  99. self.assertIn("***", result)
  100. def test_mask_sensitive_data_empty_data(self):
  101. """Test _mask_sensitive_data method with empty data"""
  102. provider = _TestableSimpleProvider(self.id, self.token)
  103. result = provider._mask_sensitive_data("")
  104. self.assertEqual(result, "")
  105. def test_mask_sensitive_data_none_data(self):
  106. """Test _mask_sensitive_data method with None data"""
  107. provider = _TestableSimpleProvider(self.id, self.token)
  108. result = provider._mask_sensitive_data(None)
  109. self.assertIsNone(result)
  110. def test_mask_sensitive_data_no_token(self):
  111. """Test _mask_sensitive_data method with no token"""
  112. # Create provider normally first, then modify token
  113. provider = _TestableSimpleProvider(self.id, self.token)
  114. provider.token = "" # Override after init
  115. data = "url?token=secret123&other=value"
  116. result = provider._mask_sensitive_data(data)
  117. self.assertEqual(result, data) # Should be unchanged
  118. def test_mask_sensitive_data_long_token(self):
  119. """Test _mask_sensitive_data method with long token"""
  120. provider = _TestableSimpleProvider(self.id, "verylongsecrettoken123")
  121. data = "url?token=verylongsecrettoken123&other=value"
  122. result = provider._mask_sensitive_data(data) # type: str # type: ignore
  123. self.assertNotIn("verylongsecrettoken123", result)
  124. self.assertIn("ve***23", result)
  125. def test_mask_sensitive_data_url_encoded(self):
  126. """Test _mask_sensitive_data method with URL encoded sensitive data"""
  127. from ddns.provider._base import quote
  128. provider = _TestableSimpleProvider("[email protected]", "secret_token_123")
  129. # 测试URL编码的token
  130. token_encoded = quote("secret_token_123", safe="")
  131. id_encoded = quote("[email protected]", safe="")
  132. data = "url?token={}&id={}&other=value".format(token_encoded, id_encoded)
  133. result = provider._mask_sensitive_data(data)
  134. self.assertIsNotNone(result)
  135. self.assertIsInstance(result, str)
  136. # Cast result to str for type checking
  137. result_str = str(result)
  138. # 验证原始敏感token信息不泄露
  139. self.assertNotIn("secret_token_123", result_str)
  140. # 验证URL编码的敏感token信息也不泄露
  141. self.assertNotIn(token_encoded, result_str)
  142. # 验证包含打码信息
  143. self.assertIn("se***23", result_str)
  144. # id 不再被打码,应该保持原样(URL编码形式)
  145. self.assertIn(id_encoded, result_str) # user%40example.com
  146. def test_mask_sensitive_data_bytes_url_encoded(self):
  147. """Test _mask_sensitive_data method with bytes containing URL encoded data"""
  148. from ddns.provider._base import quote
  149. provider = _TestableSimpleProvider("[email protected]", "token123")
  150. # 测试字节数据包含URL编码的敏感信息
  151. token_encoded = quote("token123", safe="")
  152. data = "url?token={}&data=something".format(token_encoded).encode()
  153. result = provider._mask_sensitive_data(data)
  154. self.assertIsNotNone(result)
  155. self.assertIsInstance(result, bytes)
  156. # Cast result to bytes for type checking
  157. result_bytes = bytes(result) if isinstance(result, bytes) else result.encode() if result else b""
  158. # 验证原始和URL编码的token都不泄露
  159. self.assertNotIn(b"token123", result_bytes)
  160. self.assertNotIn(token_encoded.encode(), result_bytes)
  161. # 验证包含打码信息
  162. self.assertIn(b"to***23", result_bytes)
  163. def test_set_record_abstract_method(self):
  164. """Test that set_record is implemented in test class"""
  165. provider = _TestableSimpleProvider(self.id, self.token)
  166. result = provider.set_record("example.com", "192.168.1.1")
  167. self.assertTrue(result)
  168. class _TestableSimpleProviderWithNoAPI(SimpleProvider):
  169. """Test implementation without API defined"""
  170. def set_record(self, domain, value, record_type="A", ttl=None, line=None, **extra):
  171. return True
  172. class _TestableSimpleProviderValidation(BaseProviderTestCase):
  173. """Additional validation tests for SimpleProvider"""
  174. def setUp(self):
  175. """Set up test fixtures"""
  176. super(_TestableSimpleProviderValidation, self).setUp()
  177. def test_validate_missing_api(self):
  178. """Test _validate method when API is not defined"""
  179. with self.assertRaises(ValueError) as cm:
  180. _TestableSimpleProviderWithNoAPI(self.id, self.token)
  181. self.assertIn("API endpoint must be defined", str(cm.exception))
  182. self.assertIn("_TestableSimpleProviderWithNoAPI", str(cm.exception))
  183. if __name__ == "__main__":
  184. unittest.main()