From 207a8ca57f7f0298ed3846a99d69d4ce98b2d0b9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 8 May 2026 12:08:03 +0000 Subject: [PATCH 1/2] Initial plan From 05317e2d8d082aaf8df74b6855b33c74d1b06496 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 8 May 2026 12:15:14 +0000 Subject: [PATCH 2/2] 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> --- src/runtimes/dotnet/mod.rs | 111 +++++++++++++++++++++++++++ tests/compiler_tests.rs | 153 +++++++++++++++++++++++++++++++++++++ 2 files changed, 264 insertions(+) diff --git a/src/runtimes/dotnet/mod.rs b/src/runtimes/dotnet/mod.rs index 04d58ec0..d91e705d 100644 --- a/src/runtimes/dotnet/mod.rs +++ b/src/runtimes/dotnet/mod.rs @@ -226,3 +226,114 @@ pub fn generate_ensure_nuget_config(config: &DotnetRuntimeConfig) -> String { displayName: 'Ensure nuget.config exists'" ) } + +#[cfg(test)] +mod tests { + use super::*; + + // ── generate_dotnet_install ──────────────────────────────────── + + #[test] + fn test_generate_dotnet_install_default() { + let config = DotnetRuntimeConfig::Enabled(true); + let step = generate_dotnet_install(&config); + assert!(step.contains("UseDotNet@2"), "should use UseDotNet@2 task"); + assert!( + step.contains("packageType: 'sdk'"), + "should pin packageType to 'sdk'" + ); + assert!( + step.contains("version: '8.0.x'"), + "default version should be 8.0.x: {step}" + ); + assert!( + !step.contains("useGlobalJson"), + "should not emit useGlobalJson for default" + ); + } + + #[test] + fn test_generate_dotnet_install_explicit_version() { + let config = DotnetRuntimeConfig::WithOptions(DotnetOptions { + version: Some("9.0.x".to_string()), + ..Default::default() + }); + let step = generate_dotnet_install(&config); + assert!( + step.contains("version: '9.0.x'"), + "should use specified version: {step}" + ); + assert!( + !step.contains("useGlobalJson"), + "should not emit useGlobalJson with explicit version" + ); + } + + #[test] + fn test_generate_dotnet_install_global_json() { + let config = DotnetRuntimeConfig::WithOptions(DotnetOptions { + version: Some("global.json".to_string()), + ..Default::default() + }); + let step = generate_dotnet_install(&config); + assert!( + step.contains("useGlobalJson: true"), + "should emit useGlobalJson: true: {step}" + ); + assert!( + !step.contains("version: '"), + "should not emit explicit version with useGlobalJson: {step}" + ); + } + + #[test] + fn test_generate_dotnet_install_global_json_case_insensitive() { + let config = DotnetRuntimeConfig::WithOptions(DotnetOptions { + version: Some("Global.JSON".to_string()), + ..Default::default() + }); + let step = generate_dotnet_install(&config); + assert!( + step.contains("useGlobalJson: true"), + "sentinel should be case-insensitive: {step}" + ); + } + + // ── generate_ensure_nuget_config ────────────────────────────── + + #[test] + fn test_generate_ensure_nuget_config_contains_feed_url() { + let feed = "https://pkgs.dev.azure.com/myorg/_packaging/myfeed/nuget/v3/index.json"; + let config = DotnetRuntimeConfig::WithOptions(DotnetOptions { + feed_url: Some(feed.to_string()), + ..Default::default() + }); + let step = generate_ensure_nuget_config(&config); + assert!( + step.contains(feed), + "step should interpolate the configured feed URL: {step}" + ); + assert!( + step.contains(""), + "should emit valid nuget.config XML" + ); + assert!( + step.contains("nuget.config"), + "step should reference nuget.config" + ); + assert!( + step.contains("displayName: 'Ensure nuget.config exists'"), + "step should carry the expected displayName" + ); + } + + #[test] + fn test_generate_ensure_nuget_config_default_feed() { + let config = DotnetRuntimeConfig::Enabled(true); + let step = generate_ensure_nuget_config(&config); + assert!( + step.contains("https://api.nuget.org/v3/index.json"), + "default feed should be the public nuget.org v3 index: {step}" + ); + } +} diff --git a/tests/compiler_tests.rs b/tests/compiler_tests.rs index 05ea4fad..ce332681 100644 --- a/tests/compiler_tests.rs +++ b/tests/compiler_tests.rs @@ -2794,6 +2794,159 @@ Prove theorems and build Lean 4 projects. let _ = fs::remove_dir_all(&temp_dir); } +/// Integration test: `runtimes: dotnet: true` end-to-end compilation +/// +/// Verifies that the dotnet runtime, when enabled with the simple boolean +/// form, produces a pipeline that includes the `UseDotNet@2` install task, +/// the `dotnet` bash command in the allow-list, and the .NET ecosystem +/// domains in the AWF allow-domains list. +#[test] +fn test_dotnet_runtime_compiled_output() { + let temp_dir = std::env::temp_dir().join(format!( + "agentic-pipeline-dotnet-{}", + std::process::id() + )); + fs::create_dir_all(&temp_dir).expect("Failed to create temp directory"); + + let input = r#"--- +name: "Dotnet Agent" +description: "Agent with .NET runtime" +runtimes: + dotnet: true +--- + +## Dotnet Agent + +Build and test .NET projects. +"#; + + let input_path = temp_dir.join("dotnet-agent.md"); + let output_path = temp_dir.join("dotnet-agent.yml"); + fs::write(&input_path, input).expect("Failed to write test input"); + + let binary_path = PathBuf::from(env!("CARGO_BIN_EXE_ado-aw")); + let output = std::process::Command::new(&binary_path) + .args([ + "compile", + input_path.to_str().unwrap(), + "-o", + output_path.to_str().unwrap(), + ]) + .output() + .expect("Failed to run compiler"); + + assert!( + output.status.success(), + "Compiler should succeed: {}", + String::from_utf8_lossy(&output.stderr) + ); + assert!(output_path.exists(), "Compiled YAML should exist"); + + let compiled = fs::read_to_string(&output_path).expect("Should read compiled YAML"); + + // The dotnet runtime installs the SDK via UseDotNet@2. + assert!( + compiled.contains("UseDotNet@2"), + "Should include UseDotNet@2 install task" + ); + + // The default (no version specified) should pin to 8.0.x. + assert!( + compiled.contains("8.0.x"), + "Default version should be 8.0.x" + ); + + // The dotnet command should be referenced (e.g. via the bash allow-list + // or the install step displayName). + assert!( + compiled.contains("dotnet"), + "Should include dotnet command" + ); + + // .NET ecosystem domains (e.g. nuget.org) should be in the AWF + // allow-domains list. + assert!( + compiled.contains("nuget.org"), + "Should include the .NET ecosystem domains in the AWF allow-list" + ); + + // Verify no unreplaced {{ markers }} remain. + for line in compiled.lines() { + let stripped = line.replace("${{", ""); + assert!( + !stripped.contains("{{ "), + "Compiled output should not contain unreplaced marker: {}", + line.trim() + ); + } + + let _ = fs::remove_dir_all(&temp_dir); +} + +/// Integration test: `runtimes: dotnet:` with `feed-url:` end-to-end compilation +/// +/// Verifies that when `feed-url` is set, the compiler emits both the +/// ensure-nuget.config shim and the `NuGetAuthenticate@1` step in addition +/// to the install task. +#[test] +fn test_dotnet_runtime_with_feed_url_compiled_output() { + let temp_dir = std::env::temp_dir().join(format!( + "agentic-pipeline-dotnet-feed-{}", + std::process::id() + )); + fs::create_dir_all(&temp_dir).expect("Failed to create temp directory"); + + let input = r#"--- +name: "Dotnet Feed Agent" +description: "Agent with .NET runtime + private NuGet feed" +runtimes: + dotnet: + version: "8.0.x" + feed-url: "https://pkgs.dev.azure.com/myorg/_packaging/myfeed/nuget/v3/index.json" +--- + +## Dotnet Feed Agent +"#; + + let input_path = temp_dir.join("dotnet-feed-agent.md"); + let output_path = temp_dir.join("dotnet-feed-agent.yml"); + fs::write(&input_path, input).expect("Failed to write test input"); + + let binary_path = PathBuf::from(env!("CARGO_BIN_EXE_ado-aw")); + let output = std::process::Command::new(&binary_path) + .args([ + "compile", + input_path.to_str().unwrap(), + "-o", + output_path.to_str().unwrap(), + ]) + .output() + .expect("Failed to run compiler"); + + assert!( + output.status.success(), + "Compiler should succeed: {}", + String::from_utf8_lossy(&output.stderr) + ); + + let compiled = fs::read_to_string(&output_path).expect("Should read compiled YAML"); + + assert!( + compiled.contains("UseDotNet@2"), + "Should include UseDotNet@2" + ); + assert!( + compiled.contains("NuGetAuthenticate@1"), + "Should include NuGetAuthenticate@1 when feed-url is set" + ); + assert!( + compiled.contains("nuget.config"), + "Should emit ensure-nuget.config step when feed-url is set" + ); + + let _ = fs::remove_dir_all(&temp_dir); +} + /// Integration test: `schedule:` object form with `branches:` end-to-end compilation /// /// Verifies that a pipeline compiled with the object-form schedule containing