Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions src/runtimes/dotnet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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("<packageSources>"),
"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}"
);
}
}
153 changes: 153 additions & 0 deletions tests/compiler_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading