Skip to content

Commit 4912dca

Browse files
test(runtimes): add validate() branch coverage in runtime extensions (#518)
* Initial plan * test(runtimes): add validate() branch tests in runtime extensions Agent-Logs-Url: https://github.com/githubnext/ado-aw/sessions/9568a265-a2fd-4403-90d2-456246734c24 Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
1 parent 6e1734e commit 4912dca

4 files changed

Lines changed: 260 additions & 0 deletions

File tree

src/runtimes/dotnet/extension.rs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,3 +147,111 @@ in the repository.\n"
147147
Ok(warnings)
148148
}
149149
}
150+
151+
#[cfg(test)]
152+
mod tests {
153+
use super::*;
154+
use crate::compile::parse_markdown;
155+
156+
fn ctx_from(front_matter: &crate::compile::types::FrontMatter) -> CompileContext<'_> {
157+
CompileContext::for_test(front_matter)
158+
}
159+
160+
#[test]
161+
fn test_validate_bash_disabled_warning() {
162+
let (fm, _) =
163+
parse_markdown("---\nname: test\ndescription: test\ntools:\n bash: []\n---\n")
164+
.unwrap();
165+
let ext = DotnetExtension::new(DotnetRuntimeConfig::Enabled(true));
166+
let warnings = ext.validate(&ctx_from(&fm)).unwrap();
167+
assert!(!warnings.is_empty());
168+
assert!(warnings[0].contains("tools.bash is empty"));
169+
}
170+
171+
#[test]
172+
fn test_validate_config_and_feed_url_are_mutually_exclusive() {
173+
let (fm, _) = parse_markdown(
174+
"---\nname: test\ndescription: test\nruntimes:\n dotnet:\n config: 'nuget.config'\n feed-url: 'https://pkgs.dev.azure.com/myorg/_packaging/myfeed/nuget/v3/index.json'\n---\n",
175+
)
176+
.unwrap();
177+
let dotnet = fm.runtimes.as_ref().unwrap().dotnet.as_ref().unwrap();
178+
let ext = DotnetExtension::new(dotnet.clone());
179+
let err = ext.validate(&ctx_from(&fm)).unwrap_err();
180+
assert!(err.to_string().contains("mutually exclusive"));
181+
}
182+
183+
#[test]
184+
fn test_validate_invalid_feed_url_rejected() {
185+
let (fm, _) = parse_markdown(
186+
"---\nname: test\ndescription: test\nruntimes:\n dotnet:\n feed-url: 'https://example.com/$(SECRET)/nuget'\n---\n",
187+
)
188+
.unwrap();
189+
let dotnet = fm.runtimes.as_ref().unwrap().dotnet.as_ref().unwrap();
190+
let ext = DotnetExtension::new(dotnet.clone());
191+
assert!(ext.validate(&ctx_from(&fm)).is_err());
192+
}
193+
194+
#[test]
195+
fn test_validate_version_injection_rejected() {
196+
let (fm, _) = parse_markdown(
197+
"---\nname: test\ndescription: test\nruntimes:\n dotnet:\n version: '$(SECRET)'\n---\n",
198+
)
199+
.unwrap();
200+
let dotnet = fm.runtimes.as_ref().unwrap().dotnet.as_ref().unwrap();
201+
let ext = DotnetExtension::new(dotnet.clone());
202+
assert!(ext.validate(&ctx_from(&fm)).is_err());
203+
}
204+
205+
#[test]
206+
fn test_validate_global_json_sentinel_skips_injection_check() {
207+
let (fm, _) = parse_markdown(
208+
"---\nname: test\ndescription: test\nruntimes:\n dotnet:\n version: 'global.json'\n---\n",
209+
)
210+
.unwrap();
211+
let dotnet = fm.runtimes.as_ref().unwrap().dotnet.as_ref().unwrap();
212+
let ext = DotnetExtension::new(dotnet.clone());
213+
assert!(ext.validate(&ctx_from(&fm)).is_ok());
214+
}
215+
216+
#[test]
217+
fn test_validate_global_json_conflict_bails() {
218+
let tmp = tempfile::tempdir().unwrap();
219+
std::fs::write(tmp.path().join("global.json"), r#"{"sdk":{"version":"8.0.100"}}"#).unwrap();
220+
221+
let (fm, _) = parse_markdown(
222+
"---\nname: test\ndescription: test\nruntimes:\n dotnet:\n version: '9.0.x'\n---\n",
223+
)
224+
.unwrap();
225+
let dotnet = fm.runtimes.as_ref().unwrap().dotnet.as_ref().unwrap();
226+
let ext = DotnetExtension::new(dotnet.clone());
227+
let ctx = CompileContext::for_test_with_compile_dir(&fm, tmp.path());
228+
let err = ext.validate(&ctx).unwrap_err();
229+
assert!(err.to_string().contains("global.json"));
230+
}
231+
232+
#[test]
233+
fn test_validate_global_json_sentinel_accepted_with_file_present() {
234+
let tmp = tempfile::tempdir().unwrap();
235+
std::fs::write(tmp.path().join("global.json"), r#"{"sdk":{"version":"8.0.100"}}"#).unwrap();
236+
237+
let (fm, _) = parse_markdown(
238+
"---\nname: test\ndescription: test\nruntimes:\n dotnet:\n version: 'global.json'\n---\n",
239+
)
240+
.unwrap();
241+
let dotnet = fm.runtimes.as_ref().unwrap().dotnet.as_ref().unwrap();
242+
let ext = DotnetExtension::new(dotnet.clone());
243+
let ctx = CompileContext::for_test_with_compile_dir(&fm, tmp.path());
244+
assert!(ext.validate(&ctx).is_ok());
245+
}
246+
247+
#[test]
248+
fn test_validate_config_injection_rejected() {
249+
let (fm, _) = parse_markdown(
250+
"---\nname: test\ndescription: test\nruntimes:\n dotnet:\n config: '$(SECRET)/nuget.config'\n---\n",
251+
)
252+
.unwrap();
253+
let dotnet = fm.runtimes.as_ref().unwrap().dotnet.as_ref().unwrap();
254+
let ext = DotnetExtension::new(dotnet.clone());
255+
assert!(ext.validate(&ctx_from(&fm)).is_err());
256+
}
257+
}

src/runtimes/lean/extension.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,21 @@ the toolchain. Lean files use the `.lean` extension.\n"
8585
Ok(warnings)
8686
}
8787
}
88+
89+
#[cfg(test)]
90+
mod tests {
91+
use super::*;
92+
use crate::compile::parse_markdown;
93+
94+
#[test]
95+
fn test_validate_lean_bash_disabled_emits_warning() {
96+
let (fm, _) =
97+
parse_markdown("---\nname: test\ndescription: test\ntools:\n bash: []\n---\n")
98+
.unwrap();
99+
let ext = LeanExtension::new(LeanRuntimeConfig::Enabled(true));
100+
let ctx = CompileContext::for_test(&fm);
101+
let warnings = ext.validate(&ctx).unwrap();
102+
assert!(!warnings.is_empty());
103+
assert!(warnings[0].contains("tools.bash is empty"));
104+
}
105+
}

src/runtimes/node/extension.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,70 @@ Node.js is installed and available. Use `node` to run scripts, \
122122
Ok(warnings)
123123
}
124124
}
125+
126+
#[cfg(test)]
127+
mod tests {
128+
use super::*;
129+
use crate::compile::parse_markdown;
130+
131+
fn ctx_from(front_matter: &crate::compile::types::FrontMatter) -> CompileContext<'_> {
132+
CompileContext::for_test(front_matter)
133+
}
134+
135+
#[test]
136+
fn test_validate_bash_disabled_warning() {
137+
let (fm, _) =
138+
parse_markdown("---\nname: test\ndescription: test\ntools:\n bash: []\n---\n")
139+
.unwrap();
140+
let ext = NodeExtension::new(NodeRuntimeConfig::Enabled(true));
141+
let warnings = ext.validate(&ctx_from(&fm)).unwrap();
142+
assert!(!warnings.is_empty());
143+
assert!(warnings[0].contains("tools.bash is empty"));
144+
}
145+
146+
#[test]
147+
fn test_validate_config_and_feed_url_are_mutually_exclusive() {
148+
let (fm, _) = parse_markdown(
149+
"---\nname: test\ndescription: test\nruntimes:\n node:\n config: '.npmrc'\n feed-url: 'https://pkgs.dev.azure.com/org/project/_packaging/feed/npm/registry/'\n---\n",
150+
)
151+
.unwrap();
152+
let node = fm.runtimes.as_ref().unwrap().node.as_ref().unwrap();
153+
let ext = NodeExtension::new(node.clone());
154+
let err = ext.validate(&ctx_from(&fm)).unwrap_err();
155+
assert!(err.to_string().contains("mutually exclusive"));
156+
}
157+
158+
#[test]
159+
fn test_validate_config_only_emits_warning() {
160+
let (fm, _) = parse_markdown(
161+
"---\nname: test\ndescription: test\nruntimes:\n node:\n config: '.npmrc'\n---\n",
162+
)
163+
.unwrap();
164+
let node = fm.runtimes.as_ref().unwrap().node.as_ref().unwrap();
165+
let ext = NodeExtension::new(node.clone());
166+
let warnings = ext.validate(&ctx_from(&fm)).unwrap();
167+
assert!(warnings.iter().any(|w| w.contains("will not be available")));
168+
}
169+
170+
#[test]
171+
fn test_validate_invalid_feed_url_rejected() {
172+
let (fm, _) = parse_markdown(
173+
"---\nname: test\ndescription: test\nruntimes:\n node:\n feed-url: 'pkgs.dev.azure.com/no-scheme'\n---\n",
174+
)
175+
.unwrap();
176+
let node = fm.runtimes.as_ref().unwrap().node.as_ref().unwrap();
177+
let ext = NodeExtension::new(node.clone());
178+
assert!(ext.validate(&ctx_from(&fm)).is_err());
179+
}
180+
181+
#[test]
182+
fn test_validate_version_injection_rejected() {
183+
let (fm, _) = parse_markdown(
184+
"---\nname: test\ndescription: test\nruntimes:\n node:\n version: '$(SECRET)'\n---\n",
185+
)
186+
.unwrap();
187+
let node = fm.runtimes.as_ref().unwrap().node.as_ref().unwrap();
188+
let ext = NodeExtension::new(node.clone());
189+
assert!(ext.validate(&ctx_from(&fm)).is_err());
190+
}
191+
}

src/runtimes/python/extension.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,70 @@ management, install it first with `pip install uv`.\n"
124124
Ok(warnings)
125125
}
126126
}
127+
128+
#[cfg(test)]
129+
mod tests {
130+
use super::*;
131+
use crate::compile::parse_markdown;
132+
133+
fn ctx_from(front_matter: &crate::compile::types::FrontMatter) -> CompileContext<'_> {
134+
CompileContext::for_test(front_matter)
135+
}
136+
137+
#[test]
138+
fn test_validate_bash_disabled_warning() {
139+
let (fm, _) =
140+
parse_markdown("---\nname: test\ndescription: test\ntools:\n bash: []\n---\n")
141+
.unwrap();
142+
let ext = PythonExtension::new(PythonRuntimeConfig::Enabled(true));
143+
let warnings = ext.validate(&ctx_from(&fm)).unwrap();
144+
assert!(!warnings.is_empty());
145+
assert!(warnings[0].contains("tools.bash is empty"));
146+
}
147+
148+
#[test]
149+
fn test_validate_config_and_feed_url_are_mutually_exclusive() {
150+
let (fm, _) = parse_markdown(
151+
"---\nname: test\ndescription: test\nruntimes:\n python:\n config: 'pip.conf'\n feed-url: 'https://pkgs.dev.azure.com/org/_packaging/feed/pypi/simple/'\n---\n",
152+
)
153+
.unwrap();
154+
let python = fm.runtimes.as_ref().unwrap().python.as_ref().unwrap();
155+
let ext = PythonExtension::new(python.clone());
156+
let err = ext.validate(&ctx_from(&fm)).unwrap_err();
157+
assert!(err.to_string().contains("mutually exclusive"));
158+
}
159+
160+
#[test]
161+
fn test_validate_config_only_emits_warning() {
162+
let (fm, _) = parse_markdown(
163+
"---\nname: test\ndescription: test\nruntimes:\n python:\n config: 'pip.conf'\n---\n",
164+
)
165+
.unwrap();
166+
let python = fm.runtimes.as_ref().unwrap().python.as_ref().unwrap();
167+
let ext = PythonExtension::new(python.clone());
168+
let warnings = ext.validate(&ctx_from(&fm)).unwrap();
169+
assert!(warnings.iter().any(|w| w.contains("will not be available")));
170+
}
171+
172+
#[test]
173+
fn test_validate_invalid_feed_url_rejected() {
174+
let (fm, _) = parse_markdown(
175+
"---\nname: test\ndescription: test\nruntimes:\n python:\n feed-url: 'pkgs.dev.azure.com/no-scheme'\n---\n",
176+
)
177+
.unwrap();
178+
let python = fm.runtimes.as_ref().unwrap().python.as_ref().unwrap();
179+
let ext = PythonExtension::new(python.clone());
180+
assert!(ext.validate(&ctx_from(&fm)).is_err());
181+
}
182+
183+
#[test]
184+
fn test_validate_version_injection_rejected() {
185+
let (fm, _) = parse_markdown(
186+
"---\nname: test\ndescription: test\nruntimes:\n python:\n version: '$(SECRET)'\n---\n",
187+
)
188+
.unwrap();
189+
let python = fm.runtimes.as_ref().unwrap().python.as_ref().unwrap();
190+
let ext = PythonExtension::new(python.clone());
191+
assert!(ext.validate(&ctx_from(&fm)).is_err());
192+
}
193+
}

0 commit comments

Comments
 (0)