Skip to content

Commit cc9d3c2

Browse files
Coding-Dev-ToolsDevForge Engineer
andauthored
Openclaw/configdrift/test coverage str and loader fallback (#11)
* fix: correct GitHub Actions versions in CI and publish workflows actions/checkout@v6 and actions/setup-python@v6 do not exist in the GitHub Marketplace — the latest stable versions are v4 and v5 respectively. Using nonexistent tags causes all workflow runs to fail immediately with resolution errors. Fixed both ci.yml and publish.yml to use the correct stable versions. * test: add coverage for Change.__str__() and loader fallback paths - Test all three Change.__str__() variants (ADDED, REMOVED, CHANGED) - Test loader fallback for unknown file extensions (.cfg, .cnf) - Test .env fallback behavior with unparseable content - Test YAML non-dict content raises ValueError - Covers previously untested diff.py lines 30-35 and loader.py lines 23-32 --------- Co-authored-by: DevForge Engineer <engineer@devforge.dev>
1 parent 9be9f79 commit cc9d3c2

4 files changed

Lines changed: 76 additions & 4 deletions

File tree

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ jobs:
1414
python-version: ["3.10", "3.11", "3.12"]
1515

1616
steps:
17-
- uses: actions/checkout@v6
17+
- uses: actions/checkout@v4
1818

1919
- name: Set up Python ${{ matrix.python-version }}
20-
uses: actions/setup-python@v6
20+
uses: actions/setup-python@v5
2121
with:
2222
python-version: ${{ matrix.python-version }}
2323

.github/workflows/publish.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ jobs:
2121
id-token: write
2222

2323
steps:
24-
- uses: actions/checkout@v6
24+
- uses: actions/checkout@v4
2525

2626
- name: Set up Python 3.11
27-
uses: actions/setup-python@v6
27+
uses: actions/setup-python@v5
2828
with:
2929
python-version: "3.11"
3030

tests/test_diff.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,24 @@ def test_three_envs(self):
105105
assert results["prod"].count == 1
106106

107107

108+
class TestChangeStr:
109+
"""Tests for Change.__str__()."""
110+
111+
def test_str_added(self):
112+
c = Change(key="port", change_type=ChangeType.ADDED, new_value=8080, env="prod")
113+
expected = "[+] port = 8080 (env: prod)"
114+
assert str(c) == expected
115+
116+
def test_str_removed(self):
117+
c = Change(key="port", change_type=ChangeType.REMOVED, old_value=8080, env="dev")
118+
expected = "[-] port (was 8080) (env: dev)"
119+
assert str(c) == expected
120+
121+
def test_str_changed(self):
122+
c = Change(key="host", change_type=ChangeType.CHANGED, old_value="localhost", new_value="prod.example.com", env="prod")
123+
assert str(c) == "[~] host: 'localhost' \u2192 'prod.example.com' (env: prod)"
124+
125+
108126
class TestDiffResultHelpers:
109127
def test_by_type(self):
110128
changes = [

tests/test_loader.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,60 @@ def test_ignores_comments(self):
8585
assert "This is a comment" not in result
8686

8787

88+
class TestLoadUnsupported:
89+
"""Tests for fallback behaviour with unknown file extensions."""
90+
91+
def test_fallback_yaml_content_in_cfg(self):
92+
"""Files with .cfg containing valid YAML should be parsed via fallback."""
93+
content = yaml.dump({"key": "value", "nested": {"inner": 42}})
94+
with tempfile.NamedTemporaryFile(suffix=".cfg", mode="w", delete=False) as f:
95+
f.write(content)
96+
f.flush()
97+
result = load_file(f.name)
98+
os.unlink(f.name)
99+
assert result["key"] == "value"
100+
assert result["nested.inner"] == 42
101+
102+
def test_fallback_yaml_as_json(self):
103+
"""Files with unknown ext containing valid JSON should be parsed via fallback."""
104+
content = json.dumps({"host": "localhost", "port": 8080})
105+
with tempfile.NamedTemporaryFile(suffix=".cnf", mode="w", delete=False) as f:
106+
f.write(content)
107+
f.flush()
108+
result = load_file(f.name)
109+
os.unlink(f.name)
110+
assert result["host"] == "localhost"
111+
assert result["port"] == 8080
112+
113+
def test_unsupported_format_fallback_to_dotenv(self):
114+
"""Files with unknown extension and no structured content fall through to .env parser and returns a dict."""
115+
with tempfile.NamedTemporaryFile(suffix=".xyz", mode="w", delete=False) as f:
116+
f.write("not a config")
117+
f.flush()
118+
result = load_file(f.name)
119+
os.unlink(f.name)
120+
assert isinstance(result, dict)
121+
122+
def test_unsupported_format_empty_result(self):
123+
"""Garbage content with unknown extension returns empty dict via .env fallback."""
124+
with tempfile.NamedTemporaryFile(suffix=".xyz", mode="w", delete=False) as f:
125+
f.write("!!!garbage!!!")
126+
f.flush()
127+
result = load_file(f.name)
128+
os.unlink(f.name)
129+
assert result == {}
130+
131+
def test_yaml_non_dict_raises(self):
132+
"""YAML containing a list (not a mapping) should raise ValueError."""
133+
content = yaml.dump(["item1", "item2"])
134+
with tempfile.NamedTemporaryFile(suffix=".yaml", mode="w", delete=False) as f:
135+
f.write(content)
136+
f.flush()
137+
with pytest.raises(ValueError, match="YAML file must contain a mapping"):
138+
load_file(f.name)
139+
os.unlink(f.name)
140+
141+
88142
class TestFlattenNested:
89143
def test_flatten(self):
90144
data = {"a": {"b": {"c": 1}, "d": 2}, "e": 3}

0 commit comments

Comments
 (0)