Skip to content

Commit 64d8aaa

Browse files
test(runtimes/dotnet): add unit and integration tests for dotnet runtime (#460)
* Initial plan * test(runtimes/dotnet): add unit + integration tests for dotnet runtime Agent-Logs-Url: https://github.com/githubnext/ado-aw/sessions/6be26aa9-cced-401f-8e64-2a878a523571 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 4abeb09 commit 64d8aaa

2 files changed

Lines changed: 264 additions & 0 deletions

File tree

src/runtimes/dotnet/mod.rs

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,3 +226,114 @@ pub fn generate_ensure_nuget_config(config: &DotnetRuntimeConfig) -> String {
226226
displayName: 'Ensure nuget.config exists'"
227227
)
228228
}
229+
230+
#[cfg(test)]
231+
mod tests {
232+
use super::*;
233+
234+
// ── generate_dotnet_install ────────────────────────────────────
235+
236+
#[test]
237+
fn test_generate_dotnet_install_default() {
238+
let config = DotnetRuntimeConfig::Enabled(true);
239+
let step = generate_dotnet_install(&config);
240+
assert!(step.contains("UseDotNet@2"), "should use UseDotNet@2 task");
241+
assert!(
242+
step.contains("packageType: 'sdk'"),
243+
"should pin packageType to 'sdk'"
244+
);
245+
assert!(
246+
step.contains("version: '8.0.x'"),
247+
"default version should be 8.0.x: {step}"
248+
);
249+
assert!(
250+
!step.contains("useGlobalJson"),
251+
"should not emit useGlobalJson for default"
252+
);
253+
}
254+
255+
#[test]
256+
fn test_generate_dotnet_install_explicit_version() {
257+
let config = DotnetRuntimeConfig::WithOptions(DotnetOptions {
258+
version: Some("9.0.x".to_string()),
259+
..Default::default()
260+
});
261+
let step = generate_dotnet_install(&config);
262+
assert!(
263+
step.contains("version: '9.0.x'"),
264+
"should use specified version: {step}"
265+
);
266+
assert!(
267+
!step.contains("useGlobalJson"),
268+
"should not emit useGlobalJson with explicit version"
269+
);
270+
}
271+
272+
#[test]
273+
fn test_generate_dotnet_install_global_json() {
274+
let config = DotnetRuntimeConfig::WithOptions(DotnetOptions {
275+
version: Some("global.json".to_string()),
276+
..Default::default()
277+
});
278+
let step = generate_dotnet_install(&config);
279+
assert!(
280+
step.contains("useGlobalJson: true"),
281+
"should emit useGlobalJson: true: {step}"
282+
);
283+
assert!(
284+
!step.contains("version: '"),
285+
"should not emit explicit version with useGlobalJson: {step}"
286+
);
287+
}
288+
289+
#[test]
290+
fn test_generate_dotnet_install_global_json_case_insensitive() {
291+
let config = DotnetRuntimeConfig::WithOptions(DotnetOptions {
292+
version: Some("Global.JSON".to_string()),
293+
..Default::default()
294+
});
295+
let step = generate_dotnet_install(&config);
296+
assert!(
297+
step.contains("useGlobalJson: true"),
298+
"sentinel should be case-insensitive: {step}"
299+
);
300+
}
301+
302+
// ── generate_ensure_nuget_config ──────────────────────────────
303+
304+
#[test]
305+
fn test_generate_ensure_nuget_config_contains_feed_url() {
306+
let feed = "https://pkgs.dev.azure.com/myorg/_packaging/myfeed/nuget/v3/index.json";
307+
let config = DotnetRuntimeConfig::WithOptions(DotnetOptions {
308+
feed_url: Some(feed.to_string()),
309+
..Default::default()
310+
});
311+
let step = generate_ensure_nuget_config(&config);
312+
assert!(
313+
step.contains(feed),
314+
"step should interpolate the configured feed URL: {step}"
315+
);
316+
assert!(
317+
step.contains("<packageSources>"),
318+
"should emit valid nuget.config XML"
319+
);
320+
assert!(
321+
step.contains("nuget.config"),
322+
"step should reference nuget.config"
323+
);
324+
assert!(
325+
step.contains("displayName: 'Ensure nuget.config exists'"),
326+
"step should carry the expected displayName"
327+
);
328+
}
329+
330+
#[test]
331+
fn test_generate_ensure_nuget_config_default_feed() {
332+
let config = DotnetRuntimeConfig::Enabled(true);
333+
let step = generate_ensure_nuget_config(&config);
334+
assert!(
335+
step.contains("https://api.nuget.org/v3/index.json"),
336+
"default feed should be the public nuget.org v3 index: {step}"
337+
);
338+
}
339+
}

tests/compiler_tests.rs

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2794,6 +2794,159 @@ Prove theorems and build Lean 4 projects.
27942794
let _ = fs::remove_dir_all(&temp_dir);
27952795
}
27962796

2797+
/// Integration test: `runtimes: dotnet: true` end-to-end compilation
2798+
///
2799+
/// Verifies that the dotnet runtime, when enabled with the simple boolean
2800+
/// form, produces a pipeline that includes the `UseDotNet@2` install task,
2801+
/// the `dotnet` bash command in the allow-list, and the .NET ecosystem
2802+
/// domains in the AWF allow-domains list.
2803+
#[test]
2804+
fn test_dotnet_runtime_compiled_output() {
2805+
let temp_dir = std::env::temp_dir().join(format!(
2806+
"agentic-pipeline-dotnet-{}",
2807+
std::process::id()
2808+
));
2809+
fs::create_dir_all(&temp_dir).expect("Failed to create temp directory");
2810+
2811+
let input = r#"---
2812+
name: "Dotnet Agent"
2813+
description: "Agent with .NET runtime"
2814+
runtimes:
2815+
dotnet: true
2816+
---
2817+
2818+
## Dotnet Agent
2819+
2820+
Build and test .NET projects.
2821+
"#;
2822+
2823+
let input_path = temp_dir.join("dotnet-agent.md");
2824+
let output_path = temp_dir.join("dotnet-agent.yml");
2825+
fs::write(&input_path, input).expect("Failed to write test input");
2826+
2827+
let binary_path = PathBuf::from(env!("CARGO_BIN_EXE_ado-aw"));
2828+
let output = std::process::Command::new(&binary_path)
2829+
.args([
2830+
"compile",
2831+
input_path.to_str().unwrap(),
2832+
"-o",
2833+
output_path.to_str().unwrap(),
2834+
])
2835+
.output()
2836+
.expect("Failed to run compiler");
2837+
2838+
assert!(
2839+
output.status.success(),
2840+
"Compiler should succeed: {}",
2841+
String::from_utf8_lossy(&output.stderr)
2842+
);
2843+
assert!(output_path.exists(), "Compiled YAML should exist");
2844+
2845+
let compiled = fs::read_to_string(&output_path).expect("Should read compiled YAML");
2846+
2847+
// The dotnet runtime installs the SDK via UseDotNet@2.
2848+
assert!(
2849+
compiled.contains("UseDotNet@2"),
2850+
"Should include UseDotNet@2 install task"
2851+
);
2852+
2853+
// The default (no version specified) should pin to 8.0.x.
2854+
assert!(
2855+
compiled.contains("8.0.x"),
2856+
"Default version should be 8.0.x"
2857+
);
2858+
2859+
// The dotnet command should be referenced (e.g. via the bash allow-list
2860+
// or the install step displayName).
2861+
assert!(
2862+
compiled.contains("dotnet"),
2863+
"Should include dotnet command"
2864+
);
2865+
2866+
// .NET ecosystem domains (e.g. nuget.org) should be in the AWF
2867+
// allow-domains list.
2868+
assert!(
2869+
compiled.contains("nuget.org"),
2870+
"Should include the .NET ecosystem domains in the AWF allow-list"
2871+
);
2872+
2873+
// Verify no unreplaced {{ markers }} remain.
2874+
for line in compiled.lines() {
2875+
let stripped = line.replace("${{", "");
2876+
assert!(
2877+
!stripped.contains("{{ "),
2878+
"Compiled output should not contain unreplaced marker: {}",
2879+
line.trim()
2880+
);
2881+
}
2882+
2883+
let _ = fs::remove_dir_all(&temp_dir);
2884+
}
2885+
2886+
/// Integration test: `runtimes: dotnet:` with `feed-url:` end-to-end compilation
2887+
///
2888+
/// Verifies that when `feed-url` is set, the compiler emits both the
2889+
/// ensure-nuget.config shim and the `NuGetAuthenticate@1` step in addition
2890+
/// to the install task.
2891+
#[test]
2892+
fn test_dotnet_runtime_with_feed_url_compiled_output() {
2893+
let temp_dir = std::env::temp_dir().join(format!(
2894+
"agentic-pipeline-dotnet-feed-{}",
2895+
std::process::id()
2896+
));
2897+
fs::create_dir_all(&temp_dir).expect("Failed to create temp directory");
2898+
2899+
let input = r#"---
2900+
name: "Dotnet Feed Agent"
2901+
description: "Agent with .NET runtime + private NuGet feed"
2902+
runtimes:
2903+
dotnet:
2904+
version: "8.0.x"
2905+
feed-url: "https://pkgs.dev.azure.com/myorg/_packaging/myfeed/nuget/v3/index.json"
2906+
---
2907+
2908+
## Dotnet Feed Agent
2909+
"#;
2910+
2911+
let input_path = temp_dir.join("dotnet-feed-agent.md");
2912+
let output_path = temp_dir.join("dotnet-feed-agent.yml");
2913+
fs::write(&input_path, input).expect("Failed to write test input");
2914+
2915+
let binary_path = PathBuf::from(env!("CARGO_BIN_EXE_ado-aw"));
2916+
let output = std::process::Command::new(&binary_path)
2917+
.args([
2918+
"compile",
2919+
input_path.to_str().unwrap(),
2920+
"-o",
2921+
output_path.to_str().unwrap(),
2922+
])
2923+
.output()
2924+
.expect("Failed to run compiler");
2925+
2926+
assert!(
2927+
output.status.success(),
2928+
"Compiler should succeed: {}",
2929+
String::from_utf8_lossy(&output.stderr)
2930+
);
2931+
2932+
let compiled = fs::read_to_string(&output_path).expect("Should read compiled YAML");
2933+
2934+
assert!(
2935+
compiled.contains("UseDotNet@2"),
2936+
"Should include UseDotNet@2"
2937+
);
2938+
assert!(
2939+
compiled.contains("NuGetAuthenticate@1"),
2940+
"Should include NuGetAuthenticate@1 when feed-url is set"
2941+
);
2942+
assert!(
2943+
compiled.contains("nuget.config"),
2944+
"Should emit ensure-nuget.config step when feed-url is set"
2945+
);
2946+
2947+
let _ = fs::remove_dir_all(&temp_dir);
2948+
}
2949+
27972950
/// Integration test: `schedule:` object form with `branches:` end-to-end compilation
27982951
///
27992952
/// Verifies that a pipeline compiled with the object-form schedule containing

0 commit comments

Comments
 (0)