Files
DDNS/tests/test_config_extra.py
f99fd8f0b3 feat(provider): add EdgeOne DNS provider for non-accelerated domain records (#588)
* Initial plan

* feat(provider): add EdgeOne DNS provider for non-accelerated domains

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* docs(provider): add EdgeOne DNS documentation for non-accelerated domains

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* fix(provider): update EdgeOne provider aliases per feedback

- Acceleration domains: edgeone, edgeone_acc, teo_acc
- DNS records: edgeone_dns, teo_dns, edgeone_noacc
- Removed: teo, tencentedgeone, neo_acc, neo
- Updated documentation to reflect new aliases

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* feat(provider): add extra parameter support and update documentation index

- Add edgeone_dns to provider index documentation (README.md)
- Add "teo" alias for backward compatibility (not documented)
- Enable EdgeOneProvider to support both acceleration and DNS records via extra['dns_record'] parameter
- Update provider aliases and documentation

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* docs: create comprehensive new-provider agent guide

- Add detailed step-by-step guide for creating new DNS providers
- Include API analysis, implementation, testing, and documentation steps
- Provide code templates and examples for both BaseProvider and SimpleProvider
- Add completion checklist and common pitfalls
- Document all requirements including compatibility checks and index updates

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* refactor(provider): change extra parameter from dns_record to teoDomainType

- Change EdgeOneProvider to use teoDomainType parameter instead of dns_record
- Support values: "dns" or "acceleration" (default)
- Parameter is case-insensitive
- EdgeOneDnsProvider now uses extra parameter to control behavior
- All 55 tests passing

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* refactor(provider): simplify EdgeOneDnsProvider with helper method

- Extract common extra parameter preparation into _prepare_extra() method
- Reduces code duplication across _query_record, _create_record, _update_record
- All 55 tests passing

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* refactor(provider): make EdgeOneDnsProvider more elegant and concise

- Use class attribute _default_domain_type = "dns" for clarity
- Replace _prepare_extra() with inline setdefault() for more Pythonic code
- setdefault() allows users to override teoDomainType if needed
- All 55 tests passing

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* refactor(provider): use options instead of extra for teoDomainType

- Move teoDomainType from per-call extra to instance-level options
- More appropriate since provider type is fixed per instance
- EdgeOneProvider reads teoDomainType from self.options at initialization
- EdgeOneDnsProvider sets teoDomainType="dns" in __init__
- Simplified implementation: 28 lines vs 43 lines for EdgeOneDnsProvider
- All 55 tests passing

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* refactor(provider): support both extra and options for teoDomainType with priority

- Remove __init__ from EdgeOneProvider, read directly from self.options
- Support teoDomainType in both extra (per-call) and options (initialization)
- extra parameter has higher priority than options
- Priority: extra > options > default ("acceleration")
- All 55 tests passing

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* docs(provider): add teoDomainType parameter documentation for EdgeOne

- Document how to switch between acceleration domains and DNS records
- Explain extra.teoDomainType parameter usage
- Add configuration examples for both modes
- Update parameter tables with teoDomainType and extra
- Document priority: extra > initialization parameter
- Add bilingual documentation (Chinese and English)

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* docs(provider): remove initialization parameter config example from EdgeOne

- Remove the example showing teoDomainType via initialization parameters
- Only document usage via extra parameter
- Remove priority note since only one method is documented
- Keep recommendation to use EdgeOneDnsProvider for cleaner code
- Update both Chinese and English documentation

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* docs(agent): update new-provider guide with relative paths and schema notes

- Update CLI compatibility note to mention checking --dns parameter choices
- Change schema note to only require v4.0 (latest format) updates
- Replace "multi-provider format" with "latest schema format"
- Convert all absolute paths to relative paths from project root
- Remove /home/runner/work/DDNS/DDNS/ prefix from all file paths

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* docs(agent): correct latest schema version to v4.1

- Update schema compatibility section to reference v4.1 instead of v4.0
- v4.1 is the latest schema format that requires updates

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* feat(config): add edgeone_dns to CLI and schema v4.1

- Add edgeone_dns to CLI --dns parameter choices
- Add edgeone_dns to schema v4.1 provider enum (both legacy and multi-provider sections)
- Update provider descriptions to include EdgeOne DNS

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Refactor labels_to_prompts_mapping format

* fix(provider): filter teoDomainType from extra params before API call

- Remove teoDomainType from extra dict before passing to Tencent Cloud API
- Fixes "UnknownParameter - The parameter `extra_teoDomainType` is not recognized" error
- teoDomainType is internal parameter for routing, not for API requests
- All 55 tests passing

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* docs(provider): update schema references to v4.1.json in EdgeOne docs

- Update all $schema references from v4.0.json to v4.1.json
- Applied to edgeone.md, edgeone.en.md, edgeone_dns.md, edgeone_dns.en.md
- Ensures documentation references the latest schema format

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* Changes before error encountered

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* fix(config): preserve nested extra dict from JSON/ENV config

- Process "extra" dict before extra_ prefixed keys to avoid flattening
- Fixes issue where {"extra":{"teoDomainType":"dns"}} was incorrectly transformed to {"extra_teoDomainType":"dns"}
- Maintains backward compatibility with extra_ prefixed keys
- Priority: explicit extra dict > extra_ prefixed keys > unknown keys

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update tests/test_provider_edgeone_dns.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update tests/test_provider_edgeone_dns.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* style: apply ruff formatting fixes

- Format 9 files with ruff
- Fix 2 linting issues automatically
- One complexity warning remains (C901) but doesn't affect functionality
- All code now follows consistent formatting standards

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* docs(agent): add ruff installation instructions to new-provider guide

- Add pip3 install ruff command before formatting/linting steps
- Ensures developers have required tools before running verification
- Prevents common setup errors

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* docs(config): clarify extra dict and extra_ prefix priority within config sources

- Add comments explaining that extra_ prefixed keys override nested extra dict values within the same source
- Improves code documentation clarity for config flattening logic
- Addresses feedback about intra-source priority ordering

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* refactor(config): reduce complexity of _collect_extra method

- Extract _process_extra_from_source helper method to reduce cyclomatic complexity
- Fixes ruff C901 error (complexity was 14, now under threshold)
- Maintains all existing functionality and test coverage
- All 60 extra-related tests passing

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>
Co-authored-by: New Future <NewFuture@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-15 16:44:59 +00:00

205 lines
9.4 KiB
Python

# coding=utf-8
"""
Unit tests for extra field support in ddns.config.config module
@author: GitHub Copilot
"""
from __init__ import unittest
from ddns.config.config import Config # noqa: E402
class TestConfigExtra(unittest.TestCase):
"""Test extra field collection from various config sources"""
def test_extra_from_cli(self):
"""Test extra fields from CLI config with extra_ prefix"""
cli_config = {
"dns": "cloudflare",
"id": "test@example.com",
"extra_proxied": "true",
"extra_comment": "Test comment",
"extra_custom_field": "custom_value",
}
config = Config(cli_config=cli_config)
self.assertEqual(config.dns, "cloudflare")
self.assertIsInstance(config.extra, dict)
self.assertEqual(config.extra.get("proxied"), "true")
self.assertEqual(config.extra.get("comment"), "Test comment")
self.assertEqual(config.extra.get("custom_field"), "custom_value")
def test_extra_from_json(self):
"""Test extra fields from JSON config as extra object"""
json_config = {
"dns": "alidns",
"id": "test_id",
"extra": {"proxied": False, "comment": "JSON comment", "tags": ["tag1", "tag2"]},
}
config = Config(json_config=json_config)
self.assertEqual(config.dns, "alidns")
self.assertIsInstance(config.extra, dict)
self.assertFalse(config.extra.get("proxied"))
self.assertEqual(config.extra.get("comment"), "JSON comment")
self.assertEqual(config.extra.get("tags"), ["tag1", "tag2"])
def test_extra_from_json_undefined_fields(self):
"""Test undefined fields in JSON config are collected as extra"""
json_config = {"dns": "dnspod", "id": "test_id", "custom_field": "custom_value", "another_field": 123}
config = Config(json_config=json_config)
self.assertEqual(config.dns, "dnspod")
self.assertEqual(config.extra.get("custom_field"), "custom_value")
self.assertEqual(config.extra.get("another_field"), 123)
def test_extra_from_env(self):
"""Test extra fields from environment config"""
env_config = {"dns": "cloudflare", "extra_proxied": "true", "extra_ttl_override": "300"}
config = Config(env_config=env_config)
self.assertEqual(config.dns, "cloudflare")
self.assertEqual(config.extra.get("proxied"), "true")
self.assertEqual(config.extra.get("ttl_override"), "300")
def test_extra_from_env_undefined_fields(self):
"""Test undefined fields in env config are collected as extra"""
env_config = {"dns": "dnspod", "custom_env_field": "env_value", "another_env_field": "another_value"}
config = Config(env_config=env_config)
self.assertEqual(config.dns, "dnspod")
self.assertEqual(config.extra.get("custom_env_field"), "env_value")
self.assertEqual(config.extra.get("another_env_field"), "another_value")
def test_extra_priority_cli_over_json(self):
"""Test CLI extra fields have priority over JSON"""
cli_config = {"extra_comment": "CLI comment", "extra_field1": "cli_value"}
json_config = {"extra": {"comment": "JSON comment", "field1": "json_value", "field2": "json_only"}}
config = Config(cli_config=cli_config, json_config=json_config)
self.assertEqual(config.extra.get("comment"), "CLI comment")
self.assertEqual(config.extra.get("field1"), "cli_value")
self.assertEqual(config.extra.get("field2"), "json_only")
def test_extra_priority_json_over_env(self):
"""Test JSON extra fields have priority over ENV"""
json_config = {"extra": {"comment": "JSON comment", "field1": "json_value"}}
env_config = {"extra_comment": "ENV comment", "extra_field1": "env_value", "extra_field2": "env_only"}
config = Config(json_config=json_config, env_config=env_config)
self.assertEqual(config.extra.get("comment"), "JSON comment")
self.assertEqual(config.extra.get("field1"), "json_value")
self.assertEqual(config.extra.get("field2"), "env_only")
def test_extra_priority_all_sources(self):
"""Test complete priority chain: CLI > JSON > ENV"""
cli_config = {"extra_field1": "cli_value"}
json_config = {"extra": {"field1": "json_value", "field2": "json_value"}}
env_config = {"extra_field1": "env_value", "extra_field2": "env_value", "extra_field3": "env_value"}
config = Config(cli_config=cli_config, json_config=json_config, env_config=env_config)
self.assertEqual(config.extra.get("field1"), "cli_value")
self.assertEqual(config.extra.get("field2"), "json_value")
self.assertEqual(config.extra.get("field3"), "env_value")
def test_extra_empty_by_default(self):
"""Test extra is empty dict when no extra fields provided"""
config = Config()
self.assertIsInstance(config.extra, dict)
self.assertEqual(len(config.extra), 0)
def test_extra_does_not_include_known_fields(self):
"""Test that known configuration fields are not collected as extra"""
cli_config = {
"dns": "cloudflare",
"id": "test@example.com",
"token": "secret",
"ttl": "300",
"extra_custom": "custom_value",
}
config = Config(cli_config=cli_config)
# Known fields should not be in extra
self.assertNotIn("dns", config.extra)
self.assertNotIn("id", config.extra)
self.assertNotIn("token", config.extra)
self.assertNotIn("ttl", config.extra)
# Only custom field should be in extra
self.assertEqual(config.extra.get("custom"), "custom_value")
def test_extra_does_not_include_schema_field(self):
"""Test that $schema field from JSON config is not collected as extra"""
json_config = {
"$schema": "https://ddns.newfuture.cc/schema/v4.1.json",
"dns": "tencentcloud",
"id": "test_id",
"token": "test_token",
"ipv4": ["example.com"],
"extra": {"proxied": True},
}
config = Config(json_config=json_config)
# $schema should not be in extra
self.assertNotIn("$schema", config.extra)
# Other extra fields should be collected
self.assertTrue(config.extra.get("proxied"))
# Known fields should be accessible
self.assertEqual(config.dns, "tencentcloud")
self.assertEqual(config.id, "test_id")
def test_extra_with_json_extra_object_and_undefined_fields(self):
"""Test JSON config with both extra object and undefined fields"""
json_config = {
"dns": "cloudflare",
"extra": {"proxied": True, "comment": "From extra object"},
"custom_field": "From undefined field",
"another_field": 123,
}
config = Config(json_config=json_config)
# Both should be collected
self.assertTrue(config.extra.get("proxied"))
self.assertEqual(config.extra.get("comment"), "From extra object")
self.assertEqual(config.extra.get("custom_field"), "From undefined field")
self.assertEqual(config.extra.get("another_field"), 123)
def test_extra_in_md5_hash(self):
"""Test that extra fields are included in MD5 hash"""
config1 = Config(cli_config={"dns": "cloudflare", "extra_field": "value1"})
config2 = Config(cli_config={"dns": "cloudflare", "extra_field": "value1"})
config3 = Config(cli_config={"dns": "cloudflare", "extra_field": "value2"})
# Same extra should produce same hash
self.assertEqual(config1.md5(), config2.md5())
# Different extra should produce different hash
self.assertNotEqual(config1.md5(), config3.md5())
def test_extra_with_complex_values(self):
"""Test extra fields with complex data types"""
json_config = {
"dns": "cloudflare",
"extra": {
"tags": ["tag1", "tag2", "tag3"],
"settings": {"key1": "value1", "key2": "value2"},
"enabled": True,
"priority": 10,
},
}
config = Config(json_config=json_config)
self.assertEqual(config.extra.get("tags"), ["tag1", "tag2", "tag3"])
self.assertEqual(config.extra.get("settings"), {"key1": "value1", "key2": "value2"})
self.assertTrue(config.extra.get("enabled"))
self.assertEqual(config.extra.get("priority"), 10)
def test_extra_env_with_extra_object(self):
"""Test env config with extra object (dict)"""
env_config = {"dns": "cloudflare", "extra": {"field1": "value1", "field2": "value2"}}
config = Config(env_config=env_config)
self.assertEqual(config.extra.get("field1"), "value1")
self.assertEqual(config.extra.get("field2"), "value2")
def test_extra_mixed_prefix_and_object(self):
"""Test mixing extra_ prefix and extra object in same source"""
json_config = {
"dns": "cloudflare",
"extra": {"from_object": "object_value"},
"undefined_field": "undefined_value",
}
cli_config = {"extra_from_prefix": "prefix_value"}
config = Config(cli_config=cli_config, json_config=json_config)
self.assertEqual(config.extra.get("from_object"), "object_value")
self.assertEqual(config.extra.get("undefined_field"), "undefined_value")
self.assertEqual(config.extra.get("from_prefix"), "prefix_value")
if __name__ == "__main__":
unittest.main()