Skip to content

Commit 4ed8bc2

Browse files
committed
test(cli): end-to-end --extra-yaml merge behavior
Exercises the full path from resolve_extra_yaml through CliRuntime.reload_project_config into BaseProjectConfig.get_task: - Option override preserves sibling keys (deep merge) - Multi-file last-wins ordering - New task definition via --extra-yaml is resolvable - class_path override imports the replacement class (documents the trust posture rather than preventing it)
1 parent d2b2436 commit 4ed8bc2

1 file changed

Lines changed: 99 additions & 0 deletions

File tree

cumulusci/cli/tests/test_extra_yaml.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
import textwrap
2+
from contextlib import contextmanager
3+
14
import pytest
25

36
from cumulusci.cli.extra_yaml import resolve_extra_yaml
7+
from cumulusci.cli.runtime import CliRuntime
48
from cumulusci.core.exceptions import CumulusCIUsageError
59

610

@@ -87,3 +91,98 @@ def test_resolve_extra_yaml__empty_env_var_segments_ignored(tmp_path, monkeypatc
8791
result = resolve_extra_yaml(())
8892
assert result is not None
8993
assert "project: {}" in result
94+
95+
96+
@contextmanager
97+
def _minimal_project(tmp_path, monkeypatch):
98+
"""Create a minimal cci project dir and chdir into it."""
99+
monkeypatch.chdir(tmp_path)
100+
(tmp_path / ".git").mkdir()
101+
(tmp_path / "cumulusci.yml").write_text(
102+
textwrap.dedent(
103+
"""\
104+
minimum_cumulusci_version: '3.0.0'
105+
project:
106+
name: extra_yaml_test
107+
package:
108+
name: ExtraYamlTest
109+
api_version: '58.0'
110+
git:
111+
default_branch: main
112+
tasks:
113+
existing_task:
114+
description: From cumulusci.yml
115+
class_path: cumulusci.tasks.util.Sleep
116+
options:
117+
seconds: 1
118+
"""
119+
)
120+
)
121+
yield tmp_path
122+
123+
124+
def test_extra_yaml__overrides_existing_task_option(tmp_path, monkeypatch):
125+
monkeypatch.delenv("CUMULUSCI_EXTRA_YAML", raising=False)
126+
with _minimal_project(tmp_path, monkeypatch):
127+
extra = tmp_path / "extra.yml"
128+
extra.write_text("tasks:\n existing_task:\n options:\n seconds: 99\n")
129+
runtime = CliRuntime(load_keychain=False)
130+
runtime.reload_project_config(additional_yaml=resolve_extra_yaml((str(extra),)))
131+
assert runtime.project_config is not None
132+
task_cfg = runtime.project_config.get_task("existing_task")
133+
assert task_cfg.options["seconds"] == 99
134+
# Description from cumulusci.yml still present (deep merge).
135+
assert task_cfg.description == "From cumulusci.yml"
136+
137+
138+
def test_extra_yaml__multi_file_last_wins(tmp_path, monkeypatch):
139+
monkeypatch.delenv("CUMULUSCI_EXTRA_YAML", raising=False)
140+
with _minimal_project(tmp_path, monkeypatch):
141+
a = tmp_path / "a.yml"
142+
a.write_text("tasks:\n existing_task:\n options:\n seconds: 10\n")
143+
b = tmp_path / "b.yml"
144+
b.write_text("tasks:\n existing_task:\n options:\n seconds: 20\n")
145+
runtime = CliRuntime(load_keychain=False)
146+
runtime.reload_project_config(
147+
additional_yaml=resolve_extra_yaml((str(a), str(b)))
148+
)
149+
assert runtime.project_config is not None
150+
task_cfg = runtime.project_config.get_task("existing_task")
151+
assert task_cfg.options["seconds"] == 20
152+
153+
154+
def test_extra_yaml__defines_new_task(tmp_path, monkeypatch):
155+
monkeypatch.delenv("CUMULUSCI_EXTRA_YAML", raising=False)
156+
with _minimal_project(tmp_path, monkeypatch):
157+
extra = tmp_path / "extra.yml"
158+
extra.write_text(
159+
"tasks:\n"
160+
" brand_new_task:\n"
161+
" description: defined in extra\n"
162+
" class_path: cumulusci.tasks.util.Sleep\n"
163+
" options:\n"
164+
" seconds: 0\n"
165+
)
166+
runtime = CliRuntime(load_keychain=False)
167+
runtime.reload_project_config(additional_yaml=resolve_extra_yaml((str(extra),)))
168+
assert runtime.project_config is not None
169+
task_cfg = runtime.project_config.get_task("brand_new_task")
170+
assert task_cfg.description == "defined in extra"
171+
172+
173+
def test_extra_yaml__class_path_override_imports_new_class(tmp_path, monkeypatch):
174+
"""Extra YAML can swap class_path to any importable class.
175+
176+
Demonstrates (rather than prevents) the documented trust posture.
177+
"""
178+
monkeypatch.delenv("CUMULUSCI_EXTRA_YAML", raising=False)
179+
with _minimal_project(tmp_path, monkeypatch):
180+
extra = tmp_path / "extra.yml"
181+
extra.write_text(
182+
"tasks:\n existing_task:\n class_path: cumulusci.tasks.util.LogLine\n"
183+
)
184+
runtime = CliRuntime(load_keychain=False)
185+
runtime.reload_project_config(additional_yaml=resolve_extra_yaml((str(extra),)))
186+
assert runtime.project_config is not None
187+
task_cfg = runtime.project_config.get_task("existing_task")
188+
assert task_cfg.class_path == "cumulusci.tasks.util.LogLine"

0 commit comments

Comments
 (0)