Skip to content
Draft
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
108 changes: 108 additions & 0 deletions src/runtimes/dotnet/extension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,111 @@ in the repository.\n"
Ok(warnings)
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::compile::parse_markdown;

fn ctx_from(front_matter: &crate::compile::types::FrontMatter) -> CompileContext<'_> {
CompileContext::for_test(front_matter)
}

#[test]
fn test_validate_bash_disabled_warning() {
let (fm, _) =
parse_markdown("---\nname: test\ndescription: test\ntools:\n bash: []\n---\n")
.unwrap();
let ext = DotnetExtension::new(DotnetRuntimeConfig::Enabled(true));
let warnings = ext.validate(&ctx_from(&fm)).unwrap();
assert!(!warnings.is_empty());
assert!(warnings[0].contains("tools.bash is empty"));
}

#[test]
fn test_validate_config_and_feed_url_are_mutually_exclusive() {
let (fm, _) = parse_markdown(
"---\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",
)
.unwrap();
let dotnet = fm.runtimes.as_ref().unwrap().dotnet.as_ref().unwrap();
let ext = DotnetExtension::new(dotnet.clone());
let err = ext.validate(&ctx_from(&fm)).unwrap_err();
assert!(err.to_string().contains("mutually exclusive"));
}

#[test]
fn test_validate_invalid_feed_url_rejected() {
let (fm, _) = parse_markdown(
"---\nname: test\ndescription: test\nruntimes:\n dotnet:\n feed-url: 'https://example.com/$(SECRET)/nuget'\n---\n",
)
.unwrap();
let dotnet = fm.runtimes.as_ref().unwrap().dotnet.as_ref().unwrap();
let ext = DotnetExtension::new(dotnet.clone());
assert!(ext.validate(&ctx_from(&fm)).is_err());
}

#[test]
fn test_validate_version_injection_rejected() {
let (fm, _) = parse_markdown(
"---\nname: test\ndescription: test\nruntimes:\n dotnet:\n version: '$(SECRET)'\n---\n",
)
.unwrap();
let dotnet = fm.runtimes.as_ref().unwrap().dotnet.as_ref().unwrap();
let ext = DotnetExtension::new(dotnet.clone());
assert!(ext.validate(&ctx_from(&fm)).is_err());
}

#[test]
fn test_validate_global_json_sentinel_skips_injection_check() {
let (fm, _) = parse_markdown(
"---\nname: test\ndescription: test\nruntimes:\n dotnet:\n version: 'global.json'\n---\n",
)
.unwrap();
let dotnet = fm.runtimes.as_ref().unwrap().dotnet.as_ref().unwrap();
let ext = DotnetExtension::new(dotnet.clone());
assert!(ext.validate(&ctx_from(&fm)).is_ok());
}

#[test]
fn test_validate_global_json_conflict_bails() {
let tmp = tempfile::tempdir().unwrap();
std::fs::write(tmp.path().join("global.json"), r#"{"sdk":{"version":"8.0.100"}}"#).unwrap();

let (fm, _) = parse_markdown(
"---\nname: test\ndescription: test\nruntimes:\n dotnet:\n version: '9.0.x'\n---\n",
)
.unwrap();
let dotnet = fm.runtimes.as_ref().unwrap().dotnet.as_ref().unwrap();
let ext = DotnetExtension::new(dotnet.clone());
let ctx = CompileContext::for_test_with_compile_dir(&fm, tmp.path());
let err = ext.validate(&ctx).unwrap_err();
assert!(err.to_string().contains("global.json"));
}

#[test]
fn test_validate_global_json_sentinel_accepted_with_file_present() {
let tmp = tempfile::tempdir().unwrap();
std::fs::write(tmp.path().join("global.json"), r#"{"sdk":{"version":"8.0.100"}}"#).unwrap();

let (fm, _) = parse_markdown(
"---\nname: test\ndescription: test\nruntimes:\n dotnet:\n version: 'global.json'\n---\n",
)
.unwrap();
let dotnet = fm.runtimes.as_ref().unwrap().dotnet.as_ref().unwrap();
let ext = DotnetExtension::new(dotnet.clone());
let ctx = CompileContext::for_test_with_compile_dir(&fm, tmp.path());
assert!(ext.validate(&ctx).is_ok());
}

#[test]
fn test_validate_config_injection_rejected() {
let (fm, _) = parse_markdown(
"---\nname: test\ndescription: test\nruntimes:\n dotnet:\n config: '$(SECRET)/nuget.config'\n---\n",
)
.unwrap();
let dotnet = fm.runtimes.as_ref().unwrap().dotnet.as_ref().unwrap();
let ext = DotnetExtension::new(dotnet.clone());
assert!(ext.validate(&ctx_from(&fm)).is_err());
}
}
18 changes: 18 additions & 0 deletions src/runtimes/lean/extension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,21 @@ the toolchain. Lean files use the `.lean` extension.\n"
Ok(warnings)
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::compile::parse_markdown;

#[test]
fn test_validate_lean_bash_disabled_emits_warning() {
let (fm, _) =
parse_markdown("---\nname: test\ndescription: test\ntools:\n bash: []\n---\n")
.unwrap();
let ext = LeanExtension::new(LeanRuntimeConfig::Enabled(true));
let ctx = CompileContext::for_test(&fm);
let warnings = ext.validate(&ctx).unwrap();
assert!(!warnings.is_empty());
assert!(warnings[0].contains("tools.bash is empty"));
}
}
67 changes: 67 additions & 0 deletions src/runtimes/node/extension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,70 @@ Node.js is installed and available. Use `node` to run scripts, \
Ok(warnings)
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::compile::parse_markdown;

fn ctx_from(front_matter: &crate::compile::types::FrontMatter) -> CompileContext<'_> {
CompileContext::for_test(front_matter)
}

#[test]
fn test_validate_bash_disabled_warning() {
let (fm, _) =
parse_markdown("---\nname: test\ndescription: test\ntools:\n bash: []\n---\n")
.unwrap();
let ext = NodeExtension::new(NodeRuntimeConfig::Enabled(true));
let warnings = ext.validate(&ctx_from(&fm)).unwrap();
assert!(!warnings.is_empty());
assert!(warnings[0].contains("tools.bash is empty"));
}

#[test]
fn test_validate_config_and_feed_url_are_mutually_exclusive() {
let (fm, _) = parse_markdown(
"---\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",
)
.unwrap();
let node = fm.runtimes.as_ref().unwrap().node.as_ref().unwrap();
let ext = NodeExtension::new(node.clone());
let err = ext.validate(&ctx_from(&fm)).unwrap_err();
assert!(err.to_string().contains("mutually exclusive"));
}

#[test]
fn test_validate_config_only_emits_warning() {
let (fm, _) = parse_markdown(
"---\nname: test\ndescription: test\nruntimes:\n node:\n config: '.npmrc'\n---\n",
)
.unwrap();
let node = fm.runtimes.as_ref().unwrap().node.as_ref().unwrap();
let ext = NodeExtension::new(node.clone());
let warnings = ext.validate(&ctx_from(&fm)).unwrap();
assert!(warnings.iter().any(|w| w.contains("will not be available")));
}

#[test]
fn test_validate_invalid_feed_url_rejected() {
let (fm, _) = parse_markdown(
"---\nname: test\ndescription: test\nruntimes:\n node:\n feed-url: 'pkgs.dev.azure.com/no-scheme'\n---\n",
)
.unwrap();
let node = fm.runtimes.as_ref().unwrap().node.as_ref().unwrap();
let ext = NodeExtension::new(node.clone());
assert!(ext.validate(&ctx_from(&fm)).is_err());
}

#[test]
fn test_validate_version_injection_rejected() {
let (fm, _) = parse_markdown(
"---\nname: test\ndescription: test\nruntimes:\n node:\n version: '$(SECRET)'\n---\n",
)
.unwrap();
let node = fm.runtimes.as_ref().unwrap().node.as_ref().unwrap();
let ext = NodeExtension::new(node.clone());
assert!(ext.validate(&ctx_from(&fm)).is_err());
}
}
67 changes: 67 additions & 0 deletions src/runtimes/python/extension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,70 @@ management, install it first with `pip install uv`.\n"
Ok(warnings)
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::compile::parse_markdown;

fn ctx_from(front_matter: &crate::compile::types::FrontMatter) -> CompileContext<'_> {
CompileContext::for_test(front_matter)
}

#[test]
fn test_validate_bash_disabled_warning() {
let (fm, _) =
parse_markdown("---\nname: test\ndescription: test\ntools:\n bash: []\n---\n")
.unwrap();
let ext = PythonExtension::new(PythonRuntimeConfig::Enabled(true));
let warnings = ext.validate(&ctx_from(&fm)).unwrap();
assert!(!warnings.is_empty());
assert!(warnings[0].contains("tools.bash is empty"));
}

#[test]
fn test_validate_config_and_feed_url_are_mutually_exclusive() {
let (fm, _) = parse_markdown(
"---\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",
)
.unwrap();
let python = fm.runtimes.as_ref().unwrap().python.as_ref().unwrap();
let ext = PythonExtension::new(python.clone());
let err = ext.validate(&ctx_from(&fm)).unwrap_err();
assert!(err.to_string().contains("mutually exclusive"));
}

#[test]
fn test_validate_config_only_emits_warning() {
let (fm, _) = parse_markdown(
"---\nname: test\ndescription: test\nruntimes:\n python:\n config: 'pip.conf'\n---\n",
)
.unwrap();
let python = fm.runtimes.as_ref().unwrap().python.as_ref().unwrap();
let ext = PythonExtension::new(python.clone());
let warnings = ext.validate(&ctx_from(&fm)).unwrap();
assert!(warnings.iter().any(|w| w.contains("will not be available")));
}

#[test]
fn test_validate_invalid_feed_url_rejected() {
let (fm, _) = parse_markdown(
"---\nname: test\ndescription: test\nruntimes:\n python:\n feed-url: 'pkgs.dev.azure.com/no-scheme'\n---\n",
)
.unwrap();
let python = fm.runtimes.as_ref().unwrap().python.as_ref().unwrap();
let ext = PythonExtension::new(python.clone());
assert!(ext.validate(&ctx_from(&fm)).is_err());
}

#[test]
fn test_validate_version_injection_rejected() {
let (fm, _) = parse_markdown(
"---\nname: test\ndescription: test\nruntimes:\n python:\n version: '$(SECRET)'\n---\n",
)
.unwrap();
let python = fm.runtimes.as_ref().unwrap().python.as_ref().unwrap();
let ext = PythonExtension::new(python.clone());
assert!(ext.validate(&ctx_from(&fm)).is_err());
}
}