style: apply cargo fmt workspace-wide
Pure formatting pass from `cargo fmt --all`. No logic changes. Separating this out so the 1.9 release feature commits that follow show only their intentional edits.
This commit is contained in:
@@ -96,15 +96,13 @@ impl ModuleLoader for WfeModuleLoader {
|
||||
|
||||
// Relative or bare path — resolve against referrer.
|
||||
// This handles ./foo, ../foo, and /foo (absolute path on same origin, e.g. esm.sh redirects)
|
||||
if specifier.starts_with("./")
|
||||
|| specifier.starts_with("../")
|
||||
|| specifier.starts_with('/')
|
||||
if specifier.starts_with("./") || specifier.starts_with("../") || specifier.starts_with('/')
|
||||
{
|
||||
let base = ModuleSpecifier::parse(referrer)
|
||||
.map_err(|e| JsErrorBox::generic(format!("Invalid referrer '{referrer}': {e}")))?;
|
||||
let resolved = base
|
||||
.join(specifier)
|
||||
.map_err(|e| JsErrorBox::generic(format!("Failed to resolve '{specifier}': {e}")))?;
|
||||
let resolved = base.join(specifier).map_err(|e| {
|
||||
JsErrorBox::generic(format!("Failed to resolve '{specifier}': {e}"))
|
||||
})?;
|
||||
|
||||
// Check permissions based on scheme.
|
||||
match resolved.scheme() {
|
||||
@@ -172,11 +170,9 @@ impl ModuleLoader for WfeModuleLoader {
|
||||
.map_err(|e| JsErrorBox::new("PermissionError", e.to_string()))?;
|
||||
}
|
||||
|
||||
let response = reqwest::get(&url)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
JsErrorBox::generic(format!("Failed to fetch module '{url}': {e}"))
|
||||
})?;
|
||||
let response = reqwest::get(&url).await.map_err(|e| {
|
||||
JsErrorBox::generic(format!("Failed to fetch module '{url}': {e}"))
|
||||
})?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(JsErrorBox::generic(format!(
|
||||
@@ -224,9 +220,10 @@ impl ModuleLoader for WfeModuleLoader {
|
||||
&specifier,
|
||||
None,
|
||||
))),
|
||||
Err(e) => ModuleLoadResponse::Sync(Err(JsErrorBox::generic(
|
||||
format!("Failed to read module '{}': {e}", path.display()),
|
||||
))),
|
||||
Err(e) => ModuleLoadResponse::Sync(Err(JsErrorBox::generic(format!(
|
||||
"Failed to read module '{}': {e}",
|
||||
path.display()
|
||||
)))),
|
||||
}
|
||||
}
|
||||
Err(e) => ModuleLoadResponse::Sync(Err(e)),
|
||||
@@ -274,7 +271,11 @@ mod tests {
|
||||
..Default::default()
|
||||
});
|
||||
let result = loader
|
||||
.resolve("npm:lodash@4", "ext:wfe/bootstrap.js", ResolutionKind::Import)
|
||||
.resolve(
|
||||
"npm:lodash@4",
|
||||
"ext:wfe/bootstrap.js",
|
||||
ResolutionKind::Import,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(result.as_str(), "https://esm.sh/lodash@4");
|
||||
}
|
||||
@@ -304,7 +305,12 @@ mod tests {
|
||||
ResolutionKind::Import,
|
||||
);
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().to_string().contains("Permission denied"));
|
||||
assert!(
|
||||
result
|
||||
.unwrap_err()
|
||||
.to_string()
|
||||
.contains("Permission denied")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -320,10 +326,12 @@ mod tests {
|
||||
ResolutionKind::DynamicImport,
|
||||
);
|
||||
assert!(result.is_err());
|
||||
assert!(result
|
||||
.unwrap_err()
|
||||
.to_string()
|
||||
.contains("Dynamic import is not allowed"));
|
||||
assert!(
|
||||
result
|
||||
.unwrap_err()
|
||||
.to_string()
|
||||
.contains("Dynamic import is not allowed")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -361,11 +369,7 @@ mod tests {
|
||||
..Default::default()
|
||||
});
|
||||
let result = loader
|
||||
.resolve(
|
||||
"./helper.js",
|
||||
"file:///tmp/main.js",
|
||||
ResolutionKind::Import,
|
||||
)
|
||||
.resolve("./helper.js", "file:///tmp/main.js", ResolutionKind::Import)
|
||||
.unwrap();
|
||||
assert_eq!(result.as_str(), "file:///tmp/helper.js");
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
use deno_core::op2;
|
||||
use deno_core::OpState;
|
||||
use deno_core::op2;
|
||||
use deno_error::JsErrorBox;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use deno_core::op2;
|
||||
use deno_core::OpState;
|
||||
use deno_core::op2;
|
||||
|
||||
/// Workflow data available to the script via `inputs()`.
|
||||
pub struct WorkflowInputs {
|
||||
@@ -28,11 +28,7 @@ pub fn op_inputs(state: &mut OpState) -> serde_json::Value {
|
||||
|
||||
/// Stores a key/value pair in the step outputs.
|
||||
#[op2]
|
||||
pub fn op_output(
|
||||
state: &mut OpState,
|
||||
#[string] key: String,
|
||||
#[serde] value: serde_json::Value,
|
||||
) {
|
||||
pub fn op_output(state: &mut OpState, #[string] key: String, #[serde] value: serde_json::Value) {
|
||||
let outputs = state.borrow_mut::<StepOutputs>();
|
||||
outputs.map.insert(key, value);
|
||||
}
|
||||
@@ -56,7 +52,8 @@ pub async fn op_read_file(
|
||||
{
|
||||
let s = state.borrow();
|
||||
let checker = s.borrow::<super::super::permissions::PermissionChecker>();
|
||||
checker.check_read(&path)
|
||||
checker
|
||||
.check_read(&path)
|
||||
.map_err(|e| deno_error::JsErrorBox::new("PermissionError", e.to_string()))?;
|
||||
}
|
||||
tokio::fs::read_to_string(&path)
|
||||
@@ -66,7 +63,13 @@ pub async fn op_read_file(
|
||||
|
||||
deno_core::extension!(
|
||||
wfe_ops,
|
||||
ops = [op_inputs, op_output, op_log, op_read_file, super::http::op_fetch],
|
||||
ops = [
|
||||
op_inputs,
|
||||
op_output,
|
||||
op_log,
|
||||
op_read_file,
|
||||
super::http::op_fetch
|
||||
],
|
||||
esm_entry_point = "ext:wfe/bootstrap.js",
|
||||
esm = ["ext:wfe/bootstrap.js" = "src/executors/deno/js/bootstrap.js"],
|
||||
);
|
||||
|
||||
@@ -120,9 +120,9 @@ impl PermissionChecker {
|
||||
|
||||
/// Detect `..` path traversal components.
|
||||
fn has_traversal(path: &str) -> bool {
|
||||
Path::new(path).components().any(|c| {
|
||||
matches!(c, std::path::Component::ParentDir)
|
||||
})
|
||||
Path::new(path)
|
||||
.components()
|
||||
.any(|c| matches!(c, std::path::Component::ParentDir))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,12 +130,7 @@ impl PermissionChecker {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn perms(
|
||||
net: &[&str],
|
||||
read: &[&str],
|
||||
write: &[&str],
|
||||
env: &[&str],
|
||||
) -> PermissionChecker {
|
||||
fn perms(net: &[&str], read: &[&str], write: &[&str], env: &[&str]) -> PermissionChecker {
|
||||
PermissionChecker::from_config(&DenoPermissions {
|
||||
net: net.iter().map(|s| s.to_string()).collect(),
|
||||
read: read.iter().map(|s| s.to_string()).collect(),
|
||||
@@ -182,9 +177,7 @@ mod tests {
|
||||
#[test]
|
||||
fn read_path_traversal_blocked() {
|
||||
let checker = perms(&[], &["/tmp"], &[], &[]);
|
||||
let err = checker
|
||||
.check_read("/tmp/../../../etc/passwd")
|
||||
.unwrap_err();
|
||||
let err = checker.check_read("/tmp/../../../etc/passwd").unwrap_err();
|
||||
assert_eq!(err.kind, "read");
|
||||
assert!(err.resource.contains(".."));
|
||||
}
|
||||
@@ -205,9 +198,7 @@ mod tests {
|
||||
#[test]
|
||||
fn write_path_traversal_blocked() {
|
||||
let checker = perms(&[], &[], &["/tmp/out"], &[]);
|
||||
assert!(checker
|
||||
.check_write("/tmp/out/../../etc/shadow")
|
||||
.is_err());
|
||||
assert!(checker.check_write("/tmp/out/../../etc/shadow").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -8,7 +8,7 @@ use wfe_core::WfeError;
|
||||
|
||||
use super::config::DenoConfig;
|
||||
use super::module_loader::WfeModuleLoader;
|
||||
use super::ops::workflow::{wfe_ops, StepMeta, StepOutputs, WorkflowInputs};
|
||||
use super::ops::workflow::{StepMeta, StepOutputs, WorkflowInputs, wfe_ops};
|
||||
use super::permissions::PermissionChecker;
|
||||
|
||||
/// Create a configured `JsRuntime` for executing a workflow step script.
|
||||
@@ -61,8 +61,8 @@ pub fn would_auto_add_esm_sh(config: &DenoConfig) -> bool {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use super::super::config::DenoPermissions;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn create_runtime_succeeds() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use async_trait::async_trait;
|
||||
use wfe_core::WfeError;
|
||||
use wfe_core::models::ExecutionResult;
|
||||
use wfe_core::traits::step::{StepBody, StepExecutionContext};
|
||||
use wfe_core::WfeError;
|
||||
|
||||
use super::config::DenoConfig;
|
||||
use super::ops::workflow::StepOutputs;
|
||||
@@ -95,7 +95,9 @@ impl StepBody for DenoStep {
|
||||
/// Check if the source code uses ES module syntax or top-level await.
|
||||
fn needs_module_evaluation(source: &str) -> bool {
|
||||
// Top-level await requires module evaluation. ES import/export also require it.
|
||||
source.contains("import ") || source.contains("import(") || source.contains("export ")
|
||||
source.contains("import ")
|
||||
|| source.contains("import(")
|
||||
|| source.contains("export ")
|
||||
|| source.contains("await ")
|
||||
}
|
||||
|
||||
@@ -191,9 +193,8 @@ async fn run_module_inner(
|
||||
"wfe:///inline-module.js".to_string()
|
||||
};
|
||||
|
||||
let specifier = deno_core::ModuleSpecifier::parse(&module_url).map_err(|e| {
|
||||
WfeError::StepExecution(format!("Invalid module URL '{module_url}': {e}"))
|
||||
})?;
|
||||
let specifier = deno_core::ModuleSpecifier::parse(&module_url)
|
||||
.map_err(|e| WfeError::StepExecution(format!("Invalid module URL '{module_url}': {e}")))?;
|
||||
|
||||
let module_id = runtime
|
||||
.load_main_es_module_from_code(&specifier, source.to_string())
|
||||
|
||||
@@ -2,9 +2,9 @@ use std::collections::HashMap;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use wfe_core::WfeError;
|
||||
use wfe_core::models::ExecutionResult;
|
||||
use wfe_core::traits::step::{StepBody, StepExecutionContext};
|
||||
use wfe_core::WfeError;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ShellConfig {
|
||||
@@ -31,8 +31,15 @@ impl ShellStep {
|
||||
// Inject workflow data as UPPER_CASE env vars (top-level keys only).
|
||||
// Skip keys that would override security-sensitive environment variables.
|
||||
const BLOCKED_KEYS: &[&str] = &[
|
||||
"PATH", "LD_PRELOAD", "LD_LIBRARY_PATH", "DYLD_LIBRARY_PATH",
|
||||
"HOME", "SHELL", "USER", "LOGNAME", "TERM",
|
||||
"PATH",
|
||||
"LD_PRELOAD",
|
||||
"LD_LIBRARY_PATH",
|
||||
"DYLD_LIBRARY_PATH",
|
||||
"HOME",
|
||||
"SHELL",
|
||||
"USER",
|
||||
"LOGNAME",
|
||||
"TERM",
|
||||
];
|
||||
if let Some(data_obj) = context.workflow.data.as_object() {
|
||||
for (key, value) in data_obj {
|
||||
@@ -78,19 +85,25 @@ impl ShellStep {
|
||||
let workflow_id = context.workflow.id.clone();
|
||||
let definition_id = context.workflow.workflow_definition_id.clone();
|
||||
let step_id = context.step.id;
|
||||
let step_name = context.step.name.clone().unwrap_or_else(|| "unknown".to_string());
|
||||
let step_name = context
|
||||
.step
|
||||
.name
|
||||
.clone()
|
||||
.unwrap_or_else(|| "unknown".to_string());
|
||||
|
||||
let mut cmd = self.build_command(context);
|
||||
let mut child = cmd.spawn().map_err(|e| {
|
||||
WfeError::StepExecution(format!("Failed to spawn shell command: {e}"))
|
||||
})?;
|
||||
let mut child = cmd
|
||||
.spawn()
|
||||
.map_err(|e| WfeError::StepExecution(format!("Failed to spawn shell command: {e}")))?;
|
||||
|
||||
let stdout_pipe = child.stdout.take().ok_or_else(|| {
|
||||
WfeError::StepExecution("failed to capture stdout pipe".to_string())
|
||||
})?;
|
||||
let stderr_pipe = child.stderr.take().ok_or_else(|| {
|
||||
WfeError::StepExecution("failed to capture stderr pipe".to_string())
|
||||
})?;
|
||||
let stdout_pipe = child
|
||||
.stdout
|
||||
.take()
|
||||
.ok_or_else(|| WfeError::StepExecution("failed to capture stdout pipe".to_string()))?;
|
||||
let stderr_pipe = child
|
||||
.stderr
|
||||
.take()
|
||||
.ok_or_else(|| WfeError::StepExecution("failed to capture stderr pipe".to_string()))?;
|
||||
let mut stdout_lines = BufReader::new(stdout_pipe).lines();
|
||||
let mut stderr_lines = BufReader::new(stderr_pipe).lines();
|
||||
|
||||
@@ -194,9 +207,9 @@ impl ShellStep {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cmd.output()
|
||||
.await
|
||||
.map_err(|e| WfeError::StepExecution(format!("Failed to spawn shell command: {e}")))?
|
||||
cmd.output().await.map_err(|e| {
|
||||
WfeError::StepExecution(format!("Failed to spawn shell command: {e}"))
|
||||
})?
|
||||
};
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
||||
@@ -209,7 +222,10 @@ impl ShellStep {
|
||||
|
||||
#[async_trait]
|
||||
impl StepBody for ShellStep {
|
||||
async fn run(&mut self, context: &StepExecutionContext<'_>) -> wfe_core::Result<ExecutionResult> {
|
||||
async fn run(
|
||||
&mut self,
|
||||
context: &StepExecutionContext<'_>,
|
||||
) -> wfe_core::Result<ExecutionResult> {
|
||||
let (stdout, stderr, exit_code) = if context.log_sink.is_some() {
|
||||
self.run_streaming(context).await?
|
||||
} else {
|
||||
|
||||
@@ -9,8 +9,8 @@ pub mod validation;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::path::Path;
|
||||
|
||||
use serde::de::Error as _;
|
||||
use serde::Deserialize;
|
||||
use serde::de::Error as _;
|
||||
|
||||
use crate::compiler::CompiledWorkflow;
|
||||
use crate::error::YamlWorkflowError;
|
||||
@@ -50,8 +50,11 @@ pub fn load_workflow_from_str(
|
||||
// Parse to a generic YAML value first, then resolve merge keys (<<:).
|
||||
// This adds YAML 1.1 merge key support on top of serde_yaml 0.9's YAML 1.2 parser.
|
||||
let raw_value: serde_yaml::Value = serde_yaml::from_str(&interpolated)?;
|
||||
let merged_value = yaml_merge_keys::merge_keys_serde(raw_value)
|
||||
.map_err(|e| YamlWorkflowError::Parse(serde_yaml::Error::custom(format!("merge key resolution failed: {e}"))))?;
|
||||
let merged_value = yaml_merge_keys::merge_keys_serde(raw_value).map_err(|e| {
|
||||
YamlWorkflowError::Parse(serde_yaml::Error::custom(format!(
|
||||
"merge key resolution failed: {e}"
|
||||
)))
|
||||
})?;
|
||||
|
||||
// Deserialize the merge-resolved value into our schema.
|
||||
let file: schema::YamlWorkflowFile = serde_yaml::from_value(merged_value)?;
|
||||
@@ -108,12 +111,11 @@ pub fn load_workflow_with_includes(
|
||||
|
||||
let interpolated = interpolation::interpolate(yaml, config)?;
|
||||
let raw_value: serde_yaml::Value = serde_yaml::from_str(&interpolated)?;
|
||||
let merged_value = yaml_merge_keys::merge_keys_serde(raw_value)
|
||||
.map_err(|e| {
|
||||
YamlWorkflowError::Parse(serde_yaml::Error::custom(format!(
|
||||
"merge key resolution failed: {e}"
|
||||
)))
|
||||
})?;
|
||||
let merged_value = yaml_merge_keys::merge_keys_serde(raw_value).map_err(|e| {
|
||||
YamlWorkflowError::Parse(serde_yaml::Error::custom(format!(
|
||||
"merge key resolution failed: {e}"
|
||||
)))
|
||||
})?;
|
||||
|
||||
let with_includes: YamlWorkflowFileWithIncludes = serde_yaml::from_value(merged_value)?;
|
||||
|
||||
@@ -121,13 +123,11 @@ pub fn load_workflow_with_includes(
|
||||
|
||||
// Process includes.
|
||||
for include_path_str in &with_includes.include {
|
||||
let include_path = base_path.parent().unwrap_or(base_path).join(include_path_str);
|
||||
load_includes_recursive(
|
||||
&include_path,
|
||||
config,
|
||||
&mut main_specs,
|
||||
&mut visited,
|
||||
)?;
|
||||
let include_path = base_path
|
||||
.parent()
|
||||
.unwrap_or(base_path)
|
||||
.join(include_path_str);
|
||||
load_includes_recursive(&include_path, config, &mut main_specs, &mut visited)?;
|
||||
}
|
||||
|
||||
// Main file takes precedence: included specs are only added if their ID
|
||||
@@ -149,14 +149,12 @@ fn load_includes_recursive(
|
||||
specs: &mut Vec<schema::WorkflowSpec>,
|
||||
visited: &mut HashSet<String>,
|
||||
) -> Result<(), YamlWorkflowError> {
|
||||
let canonical = path
|
||||
.canonicalize()
|
||||
.map_err(|e| {
|
||||
YamlWorkflowError::Io(std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
format!("Include file not found: {}: {e}", path.display()),
|
||||
))
|
||||
})?;
|
||||
let canonical = path.canonicalize().map_err(|e| {
|
||||
YamlWorkflowError::Io(std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
format!("Include file not found: {}: {e}", path.display()),
|
||||
))
|
||||
})?;
|
||||
|
||||
let canonical_str = canonical.to_string_lossy().to_string();
|
||||
if !visited.insert(canonical_str.clone()) {
|
||||
@@ -169,12 +167,11 @@ fn load_includes_recursive(
|
||||
let yaml = std::fs::read_to_string(&canonical)?;
|
||||
let interpolated = interpolation::interpolate(&yaml, config)?;
|
||||
let raw_value: serde_yaml::Value = serde_yaml::from_str(&interpolated)?;
|
||||
let merged_value = yaml_merge_keys::merge_keys_serde(raw_value)
|
||||
.map_err(|e| {
|
||||
YamlWorkflowError::Parse(serde_yaml::Error::custom(format!(
|
||||
"merge key resolution failed: {e}"
|
||||
)))
|
||||
})?;
|
||||
let merged_value = yaml_merge_keys::merge_keys_serde(raw_value).map_err(|e| {
|
||||
YamlWorkflowError::Parse(serde_yaml::Error::custom(format!(
|
||||
"merge key resolution failed: {e}"
|
||||
)))
|
||||
})?;
|
||||
|
||||
let with_includes: YamlWorkflowFileWithIncludes = serde_yaml::from_value(merged_value)?;
|
||||
|
||||
@@ -190,7 +187,10 @@ fn load_includes_recursive(
|
||||
|
||||
// Recurse into nested includes.
|
||||
for nested_include in &with_includes.include {
|
||||
let nested_path = canonical.parent().unwrap_or(&canonical).join(nested_include);
|
||||
let nested_path = canonical
|
||||
.parent()
|
||||
.unwrap_or(&canonical)
|
||||
.join(nested_include);
|
||||
load_includes_recursive(&nested_path, config, specs, visited)?;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,9 @@ pub fn parse_type_string(s: &str) -> Result<SchemaType, String> {
|
||||
// Check for generic types: list<...> or map<...>
|
||||
if let Some(inner_start) = s.find('<') {
|
||||
if !s.ends_with('>') {
|
||||
return Err(format!("Malformed generic type: '{s}' (missing closing '>')"));
|
||||
return Err(format!(
|
||||
"Malformed generic type: '{s}' (missing closing '>')"
|
||||
));
|
||||
}
|
||||
let container = &s[..inner_start];
|
||||
let inner_str = &s[inner_start + 1..s.len() - 1];
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::collections::{HashMap, HashSet};
|
||||
|
||||
use crate::error::YamlWorkflowError;
|
||||
use crate::schema::{WorkflowSpec, YamlCombinator, YamlComparison, YamlCondition, YamlStep};
|
||||
use crate::types::{parse_type_string, SchemaType};
|
||||
use crate::types::{SchemaType, parse_type_string};
|
||||
|
||||
/// Validate a parsed workflow spec.
|
||||
pub fn validate(spec: &WorkflowSpec) -> Result<(), YamlWorkflowError> {
|
||||
@@ -494,11 +494,7 @@ fn validate_field_path(
|
||||
return Err(YamlWorkflowError::Validation(format!(
|
||||
"Condition references unknown input field '{field_name}'. \
|
||||
Available inputs: [{}]",
|
||||
spec.inputs
|
||||
.keys()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
spec.inputs.keys().cloned().collect::<Vec<_>>().join(", ")
|
||||
)));
|
||||
}
|
||||
}
|
||||
@@ -509,11 +505,7 @@ fn validate_field_path(
|
||||
return Err(YamlWorkflowError::Validation(format!(
|
||||
"Condition references unknown output field '{field_name}'. \
|
||||
Available outputs: [{}]",
|
||||
spec.outputs
|
||||
.keys()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
spec.outputs.keys().cloned().collect::<Vec<_>>().join(", ")
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +221,10 @@ workflow:
|
||||
let test_config: wfe_yaml::executors::shell::ShellConfig =
|
||||
serde_json::from_value(test_step.step_config.clone().unwrap()).unwrap();
|
||||
assert_eq!(test_config.run, "cargo build");
|
||||
assert_eq!(test_config.shell, "bash", "shell should be inherited from YAML anchor alias");
|
||||
assert_eq!(
|
||||
test_config.shell, "bash",
|
||||
"shell should be inherited from YAML anchor alias"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -473,8 +476,14 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
assert!(err.contains("explode"), "Error should mention the invalid type, got: {err}");
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("explode"),
|
||||
"Error should mention the invalid type, got: {err}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -517,7 +526,10 @@ workflow:
|
||||
.iter()
|
||||
.find(|s| s.name.as_deref() == Some(*child_name))
|
||||
.unwrap();
|
||||
assert!(child.step_config.is_some(), "Child {child_name} should have step_config");
|
||||
assert!(
|
||||
child.step_config.is_some(),
|
||||
"Child {child_name} should have step_config"
|
||||
);
|
||||
}
|
||||
|
||||
// Factories should include entries for all 3 children.
|
||||
@@ -806,7 +818,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("config"),
|
||||
"Error should mention missing config, got: {err}"
|
||||
@@ -1019,11 +1034,11 @@ workflow:
|
||||
/// SubWorkflowStep (from wfe-core), not a placeholder.
|
||||
#[tokio::test]
|
||||
async fn workflow_step_factory_produces_real_sub_workflow_step() {
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Mutex;
|
||||
use wfe_core::models::{ExecutionPointer, WorkflowInstance, WorkflowStep as WfStep};
|
||||
use wfe_core::traits::step::{HostContext, StepExecutionContext};
|
||||
use std::pin::Pin;
|
||||
use std::future::Future;
|
||||
use std::sync::Mutex;
|
||||
|
||||
let yaml = r#"
|
||||
workflows:
|
||||
@@ -1047,30 +1062,45 @@ workflows:
|
||||
let workflows = load_workflow_from_str(yaml, &config).unwrap();
|
||||
|
||||
// Find the parent workflow's factory for the "run-child" step
|
||||
let parent = workflows.iter().find(|w| w.definition.id == "parent").unwrap();
|
||||
let factory_key = parent.step_factories.iter()
|
||||
let parent = workflows
|
||||
.iter()
|
||||
.find(|w| w.definition.id == "parent")
|
||||
.unwrap();
|
||||
let factory_key = parent
|
||||
.step_factories
|
||||
.iter()
|
||||
.find(|(k, _)| k.contains("run-child"))
|
||||
.map(|(k, _)| k.clone())
|
||||
.expect("run-child factory should exist");
|
||||
|
||||
// Create a step from the factory
|
||||
let factory = &parent.step_factories.iter()
|
||||
let factory = &parent
|
||||
.step_factories
|
||||
.iter()
|
||||
.find(|(k, _)| *k == factory_key)
|
||||
.unwrap().1;
|
||||
.unwrap()
|
||||
.1;
|
||||
let mut step = factory();
|
||||
|
||||
// Mock host context that records the start_workflow call
|
||||
struct MockHost { called: Mutex<bool> }
|
||||
struct MockHost {
|
||||
called: Mutex<bool>,
|
||||
}
|
||||
impl HostContext for MockHost {
|
||||
fn start_workflow(&self, _def: &str, _ver: u32, _data: serde_json::Value)
|
||||
-> Pin<Box<dyn Future<Output = wfe_core::Result<String>> + Send + '_>>
|
||||
{
|
||||
fn start_workflow(
|
||||
&self,
|
||||
_def: &str,
|
||||
_ver: u32,
|
||||
_data: serde_json::Value,
|
||||
) -> Pin<Box<dyn Future<Output = wfe_core::Result<String>> + Send + '_>> {
|
||||
*self.called.lock().unwrap() = true;
|
||||
Box::pin(async { Ok("child-instance-id".to_string()) })
|
||||
}
|
||||
}
|
||||
|
||||
let host = MockHost { called: Mutex::new(false) };
|
||||
let host = MockHost {
|
||||
called: Mutex::new(false),
|
||||
};
|
||||
let pointer = ExecutionPointer::new(0);
|
||||
let wf_step = WfStep::new(0, &factory_key);
|
||||
let workflow = WorkflowInstance::new("parent", 1, serde_json::json!({}));
|
||||
@@ -1182,15 +1212,13 @@ workflow:
|
||||
}
|
||||
// Second child: not
|
||||
match &children[1] {
|
||||
wfe_core::models::StepCondition::Not(inner) => {
|
||||
match inner.as_ref() {
|
||||
wfe_core::models::StepCondition::Comparison(cmp) => {
|
||||
assert_eq!(cmp.field, ".inputs.skip");
|
||||
assert_eq!(cmp.operator, wfe_core::models::ComparisonOp::Equals);
|
||||
}
|
||||
other => panic!("Expected Comparison inside Not, got: {other:?}"),
|
||||
wfe_core::models::StepCondition::Not(inner) => match inner.as_ref() {
|
||||
wfe_core::models::StepCondition::Comparison(cmp) => {
|
||||
assert_eq!(cmp.field, ".inputs.skip");
|
||||
assert_eq!(cmp.operator, wfe_core::models::ComparisonOp::Equals);
|
||||
}
|
||||
}
|
||||
other => panic!("Expected Comparison inside Not, got: {other:?}"),
|
||||
},
|
||||
other => panic!("Expected Not, got: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ fn make_context<'a>(
|
||||
workflow,
|
||||
cancellation_token: tokio_util::sync::CancellationToken::new(),
|
||||
host_context: None,
|
||||
log_sink: None,
|
||||
log_sink: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,10 +224,7 @@ workflow:
|
||||
let result = wfe_yaml::load_single_workflow_from_str(yaml, &config);
|
||||
assert!(result.is_err());
|
||||
let msg = result.err().unwrap().to_string();
|
||||
assert!(
|
||||
msg.contains("config") || msg.contains("Deno"),
|
||||
"got: {msg}"
|
||||
);
|
||||
assert!(msg.contains("config") || msg.contains("Deno"), "got: {msg}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -247,10 +244,7 @@ workflow:
|
||||
let result = wfe_yaml::load_single_workflow_from_str(yaml, &config);
|
||||
assert!(result.is_err());
|
||||
let msg = result.err().unwrap().to_string();
|
||||
assert!(
|
||||
msg.contains("script") || msg.contains("file"),
|
||||
"got: {msg}"
|
||||
);
|
||||
assert!(msg.contains("script") || msg.contains("file"), "got: {msg}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -269,7 +263,10 @@ workflow:
|
||||
let compiled = wfe_yaml::load_single_workflow_from_str(yaml, &config).unwrap();
|
||||
assert!(!compiled.step_factories.is_empty());
|
||||
let (key, _factory) = &compiled.step_factories[0];
|
||||
assert!(key.contains("deno"), "factory key should contain 'deno', got: {key}");
|
||||
assert!(
|
||||
key.contains("deno"),
|
||||
"factory key should contain 'deno', got: {key}"
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -345,10 +342,7 @@ async fn deno_fetch_denied_host() {
|
||||
"expected permission error, got: {data:?}"
|
||||
);
|
||||
let err_msg = data["error"].as_str().unwrap();
|
||||
assert!(
|
||||
err_msg.contains("Permission denied"),
|
||||
"got: {err_msg}"
|
||||
);
|
||||
assert!(err_msg.contains("Permission denied"), "got: {err_msg}");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -384,8 +378,13 @@ async fn deno_fetch_returns_json() {
|
||||
async fn deno_fetch_post_with_body() {
|
||||
let server = wiremock::MockServer::start().await;
|
||||
wiremock::Mock::given(wiremock::matchers::method("POST"))
|
||||
.and(wiremock::matchers::header("content-type", "application/json"))
|
||||
.and(wiremock::matchers::body_json(serde_json::json!({"key": "val"})))
|
||||
.and(wiremock::matchers::header(
|
||||
"content-type",
|
||||
"application/json",
|
||||
))
|
||||
.and(wiremock::matchers::body_json(
|
||||
serde_json::json!({"key": "val"}),
|
||||
))
|
||||
.respond_with(wiremock::ResponseTemplate::new(201).set_body_string("created"))
|
||||
.mount(&server)
|
||||
.await;
|
||||
@@ -501,7 +500,11 @@ async fn deno_import_local_file() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let helper_path = dir.path().join("helper.js");
|
||||
let mut f = std::fs::File::create(&helper_path).unwrap();
|
||||
writeln!(f, "export function greet(name) {{ return `hello ${{name}}`; }}").unwrap();
|
||||
writeln!(
|
||||
f,
|
||||
"export function greet(name) {{ return `hello ${{name}}`; }}"
|
||||
)
|
||||
.unwrap();
|
||||
drop(f);
|
||||
|
||||
let main_path = dir.path().join("main.js");
|
||||
@@ -536,10 +539,7 @@ output("greeting", greet("world"));"#,
|
||||
async fn deno_dynamic_import_denied() {
|
||||
let server = wiremock::MockServer::start().await;
|
||||
wiremock::Mock::given(wiremock::matchers::any())
|
||||
.respond_with(
|
||||
wiremock::ResponseTemplate::new(200)
|
||||
.set_body_string("export const x = 1;"),
|
||||
)
|
||||
.respond_with(wiremock::ResponseTemplate::new(200).set_body_string("export const x = 1;"))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
|
||||
@@ -105,7 +105,10 @@ workflow:
|
||||
"#;
|
||||
let instance = run_yaml_workflow(yaml).await;
|
||||
assert_eq!(instance.status, WorkflowStatus::Complete);
|
||||
assert_eq!(instance.data["message"], serde_json::json!("hello from deno"));
|
||||
assert_eq!(
|
||||
instance.data["message"],
|
||||
serde_json::json!("hello from deno")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -114,8 +117,7 @@ async fn yaml_deno_with_fetch_wiremock() {
|
||||
wiremock::Mock::given(wiremock::matchers::method("GET"))
|
||||
.and(wiremock::matchers::path("/api/data"))
|
||||
.respond_with(
|
||||
wiremock::ResponseTemplate::new(200)
|
||||
.set_body_json(serde_json::json!({"value": 42})),
|
||||
wiremock::ResponseTemplate::new(200).set_body_json(serde_json::json!({"value": 42})),
|
||||
)
|
||||
.mount(&server)
|
||||
.await;
|
||||
@@ -325,8 +327,7 @@ workflow:
|
||||
const data = inputs();
|
||||
output("doubled", data.value * 2);
|
||||
"#;
|
||||
let instance =
|
||||
run_yaml_workflow_with_data(yaml, serde_json::json!({"value": 21})).await;
|
||||
let instance = run_yaml_workflow_with_data(yaml, serde_json::json!({"value": 21})).await;
|
||||
assert_eq!(instance.status, WorkflowStatus::Complete);
|
||||
assert_eq!(instance.data["doubled"], serde_json::json!(42));
|
||||
}
|
||||
@@ -547,10 +548,7 @@ workflow:
|
||||
match result {
|
||||
Err(e) => {
|
||||
let msg = e.to_string();
|
||||
assert!(
|
||||
msg.contains("config") || msg.contains("Deno"),
|
||||
"got: {msg}"
|
||||
);
|
||||
assert!(msg.contains("config") || msg.contains("Deno"), "got: {msg}");
|
||||
}
|
||||
Ok(_) => panic!("expected error for deno step without config"),
|
||||
}
|
||||
@@ -574,10 +572,7 @@ workflow:
|
||||
match result {
|
||||
Err(e) => {
|
||||
let msg = e.to_string();
|
||||
assert!(
|
||||
msg.contains("script") || msg.contains("file"),
|
||||
"got: {msg}"
|
||||
);
|
||||
assert!(msg.contains("script") || msg.contains("file"), "got: {msg}");
|
||||
}
|
||||
Ok(_) => panic!("expected error for deno step without script or file"),
|
||||
}
|
||||
@@ -633,8 +628,14 @@ workflow:
|
||||
"#;
|
||||
let config = HashMap::new();
|
||||
let compiled = load_single_workflow_from_str(yaml, &config).unwrap();
|
||||
let has_shell = compiled.step_factories.iter().any(|(k, _)| k.contains("shell"));
|
||||
let has_deno = compiled.step_factories.iter().any(|(k, _)| k.contains("deno"));
|
||||
let has_shell = compiled
|
||||
.step_factories
|
||||
.iter()
|
||||
.any(|(k, _)| k.contains("shell"));
|
||||
let has_deno = compiled
|
||||
.step_factories
|
||||
.iter()
|
||||
.any(|(k, _)| k.contains("deno"));
|
||||
assert!(has_shell, "should have shell factory");
|
||||
assert!(has_deno, "should have deno factory");
|
||||
}
|
||||
@@ -785,7 +786,10 @@ workflow:
|
||||
"#;
|
||||
let instance = run_yaml_workflow(yaml).await;
|
||||
assert_eq!(instance.status, WorkflowStatus::Complete);
|
||||
assert_eq!(instance.data["nested"]["a"]["b"]["c"], serde_json::json!(42));
|
||||
assert_eq!(
|
||||
instance.data["nested"]["a"]["b"]["c"],
|
||||
serde_json::json!(42)
|
||||
);
|
||||
assert_eq!(instance.data["array"], serde_json::json!([1, 2, 3]));
|
||||
assert!(instance.data["null_val"].is_null());
|
||||
assert_eq!(instance.data["bool_val"], serde_json::json!(false));
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::collections::HashMap;
|
||||
use std::io::Write;
|
||||
|
||||
use wfe_yaml::{load_workflow, load_single_workflow_from_str};
|
||||
use wfe_yaml::{load_single_workflow_from_str, load_workflow};
|
||||
|
||||
#[test]
|
||||
fn load_workflow_from_file() {
|
||||
@@ -35,7 +35,10 @@ fn load_workflow_from_nonexistent_file_returns_error() {
|
||||
let path = std::path::Path::new("/tmp/nonexistent_wfe_test_file_12345.yaml");
|
||||
let result = load_workflow(path, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("IO error") || err.contains("No such file"),
|
||||
"Expected IO error, got: {err}"
|
||||
@@ -47,7 +50,10 @@ fn load_workflow_from_str_with_invalid_yaml_returns_error() {
|
||||
let yaml = "this is not valid yaml: [[[";
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("YAML parse error"),
|
||||
"Expected YAML parse error, got: {err}"
|
||||
@@ -96,7 +102,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("missing"),
|
||||
"Expected unresolved variable error, got: {err}"
|
||||
|
||||
@@ -55,9 +55,19 @@ async fn run_yaml_workflow_with_config(
|
||||
) -> wfe::models::WorkflowInstance {
|
||||
let compiled = load_single_workflow_from_str(yaml, config).unwrap();
|
||||
for step in &compiled.definition.steps {
|
||||
eprintln!(" step: {:?} type={} config={:?}", step.name, step.step_type, step.step_config);
|
||||
eprintln!(
|
||||
" step: {:?} type={} config={:?}",
|
||||
step.name, step.step_type, step.step_config
|
||||
);
|
||||
}
|
||||
eprintln!(" factories: {:?}", compiled.step_factories.iter().map(|(k, _)| k.clone()).collect::<Vec<_>>());
|
||||
eprintln!(
|
||||
" factories: {:?}",
|
||||
compiled
|
||||
.step_factories
|
||||
.iter()
|
||||
.map(|(k, _)| k.clone())
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
let persistence = Arc::new(InMemoryPersistenceProvider::new());
|
||||
let lock = Arc::new(InMemoryLockProvider::new());
|
||||
@@ -197,7 +207,9 @@ fn make_config(
|
||||
#[tokio::test]
|
||||
#[ignore = "requires containerd daemon"]
|
||||
async fn minimal_echo_in_containerd_via_workflow() {
|
||||
let _ = tracing_subscriber::fmt().with_env_filter("wfe_containerd=debug,wfe_core::executor=debug").try_init();
|
||||
let _ = tracing_subscriber::fmt()
|
||||
.with_env_filter("wfe_containerd=debug,wfe_core::executor=debug")
|
||||
.try_init();
|
||||
let Some(addr) = containerd_addr() else {
|
||||
eprintln!("SKIP: containerd not available");
|
||||
return;
|
||||
@@ -237,10 +249,7 @@ async fn minimal_echo_in_containerd_via_workflow() {
|
||||
eprintln!("Status: {:?}, Data: {:?}", instance.status, instance.data);
|
||||
assert_eq!(instance.status, WorkflowStatus::Complete);
|
||||
let data = instance.data.as_object().unwrap();
|
||||
assert_eq!(
|
||||
data.get("echo.status").and_then(|v| v.as_str()),
|
||||
Some("ok"),
|
||||
);
|
||||
assert_eq!(data.get("echo.status").and_then(|v| v.as_str()), Some("ok"),);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -259,79 +268,129 @@ async fn full_rust_pipeline_in_container() {
|
||||
let rustup_home = shared_tempdir("rustup");
|
||||
let workspace = shared_tempdir("workspace");
|
||||
|
||||
let config = make_config(
|
||||
&addr,
|
||||
&cargo_home,
|
||||
&rustup_home,
|
||||
Some(&workspace),
|
||||
);
|
||||
let config = make_config(&addr, &cargo_home, &rustup_home, Some(&workspace));
|
||||
|
||||
let steps = [
|
||||
containerd_step_yaml(
|
||||
"install-rust", "host", "if-not-present", "10m", None, false,
|
||||
"install-rust",
|
||||
"host",
|
||||
"if-not-present",
|
||||
"10m",
|
||||
None,
|
||||
false,
|
||||
" apt-get update && apt-get install -y curl gcc pkg-config libssl-dev\n\
|
||||
\x20 curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain stable",
|
||||
),
|
||||
containerd_step_yaml(
|
||||
"install-tools", "host", "never", "10m", None, false,
|
||||
"install-tools",
|
||||
"host",
|
||||
"never",
|
||||
"10m",
|
||||
None,
|
||||
false,
|
||||
" rustup component add clippy rustfmt llvm-tools-preview\n\
|
||||
\x20 cargo install cargo-audit cargo-deny cargo-nextest cargo-llvm-cov",
|
||||
),
|
||||
containerd_step_yaml(
|
||||
"create-project", "host", "never", "2m", None, true,
|
||||
"create-project",
|
||||
"host",
|
||||
"never",
|
||||
"2m",
|
||||
None,
|
||||
true,
|
||||
" cargo init /workspace/test-crate --name test-crate\n\
|
||||
\x20 cd /workspace/test-crate\n\
|
||||
\x20 echo '#[cfg(test)] mod tests { #[test] fn it_works() { assert_eq!(2+2,4); } }' >> src/main.rs",
|
||||
),
|
||||
containerd_step_yaml(
|
||||
"cargo-fmt", "none", "never", "2m",
|
||||
Some("/workspace/test-crate"), true,
|
||||
"cargo-fmt",
|
||||
"none",
|
||||
"never",
|
||||
"2m",
|
||||
Some("/workspace/test-crate"),
|
||||
true,
|
||||
" cargo fmt -- --check || cargo fmt",
|
||||
),
|
||||
containerd_step_yaml(
|
||||
"cargo-check", "none", "never", "5m",
|
||||
Some("/workspace/test-crate"), true,
|
||||
"cargo-check",
|
||||
"none",
|
||||
"never",
|
||||
"5m",
|
||||
Some("/workspace/test-crate"),
|
||||
true,
|
||||
" cargo check",
|
||||
),
|
||||
containerd_step_yaml(
|
||||
"cargo-clippy", "none", "never", "5m",
|
||||
Some("/workspace/test-crate"), true,
|
||||
"cargo-clippy",
|
||||
"none",
|
||||
"never",
|
||||
"5m",
|
||||
Some("/workspace/test-crate"),
|
||||
true,
|
||||
" cargo clippy -- -D warnings",
|
||||
),
|
||||
containerd_step_yaml(
|
||||
"cargo-test", "none", "never", "5m",
|
||||
Some("/workspace/test-crate"), true,
|
||||
"cargo-test",
|
||||
"none",
|
||||
"never",
|
||||
"5m",
|
||||
Some("/workspace/test-crate"),
|
||||
true,
|
||||
" cargo test",
|
||||
),
|
||||
containerd_step_yaml(
|
||||
"cargo-build", "none", "never", "5m",
|
||||
Some("/workspace/test-crate"), true,
|
||||
"cargo-build",
|
||||
"none",
|
||||
"never",
|
||||
"5m",
|
||||
Some("/workspace/test-crate"),
|
||||
true,
|
||||
" cargo build --release",
|
||||
),
|
||||
containerd_step_yaml(
|
||||
"cargo-nextest", "none", "never", "5m",
|
||||
Some("/workspace/test-crate"), true,
|
||||
"cargo-nextest",
|
||||
"none",
|
||||
"never",
|
||||
"5m",
|
||||
Some("/workspace/test-crate"),
|
||||
true,
|
||||
" cargo nextest run",
|
||||
),
|
||||
containerd_step_yaml(
|
||||
"cargo-llvm-cov", "none", "never", "5m",
|
||||
Some("/workspace/test-crate"), true,
|
||||
"cargo-llvm-cov",
|
||||
"none",
|
||||
"never",
|
||||
"5m",
|
||||
Some("/workspace/test-crate"),
|
||||
true,
|
||||
" cargo llvm-cov --summary-only",
|
||||
),
|
||||
containerd_step_yaml(
|
||||
"cargo-audit", "host", "never", "5m",
|
||||
Some("/workspace/test-crate"), true,
|
||||
"cargo-audit",
|
||||
"host",
|
||||
"never",
|
||||
"5m",
|
||||
Some("/workspace/test-crate"),
|
||||
true,
|
||||
" cargo audit || true",
|
||||
),
|
||||
containerd_step_yaml(
|
||||
"cargo-deny", "none", "never", "5m",
|
||||
Some("/workspace/test-crate"), true,
|
||||
"cargo-deny",
|
||||
"none",
|
||||
"never",
|
||||
"5m",
|
||||
Some("/workspace/test-crate"),
|
||||
true,
|
||||
" cargo deny init\n\
|
||||
\x20 cargo deny check || true",
|
||||
),
|
||||
containerd_step_yaml(
|
||||
"cargo-doc", "none", "never", "5m",
|
||||
Some("/workspace/test-crate"), true,
|
||||
"cargo-doc",
|
||||
"none",
|
||||
"never",
|
||||
"5m",
|
||||
Some("/workspace/test-crate"),
|
||||
true,
|
||||
" cargo doc --no-deps",
|
||||
),
|
||||
];
|
||||
|
||||
@@ -17,10 +17,7 @@ workflow:
|
||||
assert_eq!(parsed.workflow.version, 1);
|
||||
assert_eq!(parsed.workflow.steps.len(), 1);
|
||||
assert_eq!(parsed.workflow.steps[0].name, "hello");
|
||||
assert_eq!(
|
||||
parsed.workflow.steps[0].step_type.as_deref(),
|
||||
Some("shell")
|
||||
);
|
||||
assert_eq!(parsed.workflow.steps[0].step_type.as_deref(), Some("shell"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -263,20 +260,14 @@ workflow:
|
||||
let parsed: YamlWorkflow = serde_yaml::from_str(yaml).unwrap();
|
||||
assert_eq!(parsed.workflow.inputs.len(), 3);
|
||||
assert_eq!(parsed.workflow.inputs.get("repo_url").unwrap(), "string");
|
||||
assert_eq!(
|
||||
parsed.workflow.inputs.get("tags").unwrap(),
|
||||
"list<string>"
|
||||
);
|
||||
assert_eq!(parsed.workflow.inputs.get("tags").unwrap(), "list<string>");
|
||||
assert_eq!(parsed.workflow.inputs.get("verbose").unwrap(), "bool?");
|
||||
assert_eq!(parsed.workflow.outputs.len(), 2);
|
||||
assert_eq!(
|
||||
parsed.workflow.outputs.get("artifact_path").unwrap(),
|
||||
"string"
|
||||
);
|
||||
assert_eq!(
|
||||
parsed.workflow.outputs.get("exit_code").unwrap(),
|
||||
"integer"
|
||||
);
|
||||
assert_eq!(parsed.workflow.outputs.get("exit_code").unwrap(), "integer");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -60,7 +60,9 @@ struct CollectingLogSink {
|
||||
|
||||
impl CollectingLogSink {
|
||||
fn new() -> Self {
|
||||
Self { chunks: tokio::sync::Mutex::new(Vec::new()) }
|
||||
Self {
|
||||
chunks: tokio::sync::Mutex::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn chunks(&self) -> Vec<wfe_core::traits::LogChunk> {
|
||||
@@ -242,11 +244,8 @@ workflow:
|
||||
run: echo "{wfe_prefix}[output result=$GREETING]"
|
||||
"#
|
||||
);
|
||||
let instance = run_yaml_workflow_with_data(
|
||||
&yaml,
|
||||
serde_json::json!({"greeting": "hi there"}),
|
||||
)
|
||||
.await;
|
||||
let instance =
|
||||
run_yaml_workflow_with_data(&yaml, serde_json::json!({"greeting": "hi there"})).await;
|
||||
assert_eq!(instance.status, WorkflowStatus::Complete);
|
||||
|
||||
if let Some(data) = instance.data.as_object() {
|
||||
@@ -320,19 +319,33 @@ workflow:
|
||||
assert_eq!(instance.status, WorkflowStatus::Complete);
|
||||
|
||||
let chunks = log_sink.chunks().await;
|
||||
assert!(chunks.len() >= 2, "expected at least 2 stdout chunks, got {}", chunks.len());
|
||||
assert!(
|
||||
chunks.len() >= 2,
|
||||
"expected at least 2 stdout chunks, got {}",
|
||||
chunks.len()
|
||||
);
|
||||
|
||||
let stdout_chunks: Vec<_> = chunks
|
||||
.iter()
|
||||
.filter(|c| c.stream == wfe_core::traits::LogStreamType::Stdout)
|
||||
.collect();
|
||||
assert!(stdout_chunks.len() >= 2, "expected at least 2 stdout chunks");
|
||||
assert!(
|
||||
stdout_chunks.len() >= 2,
|
||||
"expected at least 2 stdout chunks"
|
||||
);
|
||||
|
||||
let all_data: String = stdout_chunks.iter()
|
||||
let all_data: String = stdout_chunks
|
||||
.iter()
|
||||
.map(|c| String::from_utf8_lossy(&c.data).to_string())
|
||||
.collect();
|
||||
assert!(all_data.contains("line one"), "stdout should contain 'line one', got: {all_data}");
|
||||
assert!(all_data.contains("line two"), "stdout should contain 'line two', got: {all_data}");
|
||||
assert!(
|
||||
all_data.contains("line one"),
|
||||
"stdout should contain 'line one', got: {all_data}"
|
||||
);
|
||||
assert!(
|
||||
all_data.contains("line two"),
|
||||
"stdout should contain 'line two', got: {all_data}"
|
||||
);
|
||||
|
||||
// Verify chunk metadata.
|
||||
for chunk in &stdout_chunks {
|
||||
@@ -364,10 +377,14 @@ workflow:
|
||||
.collect();
|
||||
assert!(!stderr_chunks.is_empty(), "expected stderr chunks");
|
||||
|
||||
let stderr_data: String = stderr_chunks.iter()
|
||||
let stderr_data: String = stderr_chunks
|
||||
.iter()
|
||||
.map(|c| String::from_utf8_lossy(&c.data).to_string())
|
||||
.collect();
|
||||
assert!(stderr_data.contains("stderr output"), "stderr should contain 'stderr output', got: {stderr_data}");
|
||||
assert!(
|
||||
stderr_data.contains("stderr output"),
|
||||
"stderr should contain 'stderr output', got: {stderr_data}"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -392,8 +409,14 @@ workflow:
|
||||
|
||||
let chunks = log_sink.chunks().await;
|
||||
let step_names: Vec<_> = chunks.iter().map(|c| c.step_name.as_str()).collect();
|
||||
assert!(step_names.contains(&"step-a"), "should have chunks from step-a");
|
||||
assert!(step_names.contains(&"step-b"), "should have chunks from step-b");
|
||||
assert!(
|
||||
step_names.contains(&"step-a"),
|
||||
"should have chunks from step-a"
|
||||
);
|
||||
assert!(
|
||||
step_names.contains(&"step-b"),
|
||||
"should have chunks from step-b"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -412,7 +435,13 @@ workflow:
|
||||
let instance = run_yaml_workflow(yaml).await;
|
||||
assert_eq!(instance.status, WorkflowStatus::Complete);
|
||||
let data = instance.data.as_object().unwrap();
|
||||
assert!(data.get("echo-step.stdout").unwrap().as_str().unwrap().contains("no sink"));
|
||||
assert!(
|
||||
data.get("echo-step.stdout")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.contains("no sink")
|
||||
);
|
||||
}
|
||||
|
||||
// ── Security regression tests ────────────────────────────────────────
|
||||
@@ -431,11 +460,8 @@ workflow:
|
||||
run: echo "$PATH"
|
||||
"#;
|
||||
// Set a workflow data key "path" that would override PATH if not blocked.
|
||||
let instance = run_yaml_workflow_with_data(
|
||||
yaml,
|
||||
serde_json::json!({"path": "/attacker/bin"}),
|
||||
)
|
||||
.await;
|
||||
let instance =
|
||||
run_yaml_workflow_with_data(yaml, serde_json::json!({"path": "/attacker/bin"})).await;
|
||||
assert_eq!(instance.status, WorkflowStatus::Complete);
|
||||
|
||||
let data = instance.data.as_object().unwrap();
|
||||
@@ -463,11 +489,8 @@ workflow:
|
||||
run: echo "{wfe_prefix}[output val=$MY_CUSTOM_VAR]"
|
||||
"#
|
||||
);
|
||||
let instance = run_yaml_workflow_with_data(
|
||||
&yaml,
|
||||
serde_json::json!({"my_custom_var": "works"}),
|
||||
)
|
||||
.await;
|
||||
let instance =
|
||||
run_yaml_workflow_with_data(&yaml, serde_json::json!({"my_custom_var": "works"})).await;
|
||||
assert_eq!(instance.status, WorkflowStatus::Complete);
|
||||
|
||||
let data = instance.data.as_object().unwrap();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use wfe_yaml::types::{parse_type_string, SchemaType};
|
||||
use wfe_yaml::types::{SchemaType, parse_type_string};
|
||||
|
||||
#[test]
|
||||
fn parse_all_primitives() {
|
||||
|
||||
@@ -12,7 +12,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("at least one step"),
|
||||
"Expected 'at least one step' error, got: {err}"
|
||||
@@ -30,7 +33,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("type") && err.contains("parallel"),
|
||||
"Expected error about missing type or parallel, got: {err}"
|
||||
@@ -54,7 +60,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("cannot have both"),
|
||||
"Expected 'cannot have both' error, got: {err}"
|
||||
@@ -79,7 +88,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("Duplicate step name") && err.contains("deploy"),
|
||||
"Expected duplicate name error, got: {err}"
|
||||
@@ -100,7 +112,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("config.run") || err.contains("config.file"),
|
||||
"Expected error about missing run/file, got: {err}"
|
||||
@@ -119,7 +134,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("config"),
|
||||
"Expected error about missing config, got: {err}"
|
||||
@@ -142,7 +160,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("panic"),
|
||||
"Expected error mentioning invalid type, got: {err}"
|
||||
@@ -165,7 +186,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("crash"),
|
||||
"Expected error mentioning invalid type, got: {err}"
|
||||
@@ -185,7 +209,11 @@ workflow:
|
||||
run: echo hello
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_ok(), "Valid workflow should pass, got: {:?}", result.err());
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Valid workflow should pass, got: {:?}",
|
||||
result.err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -203,7 +231,11 @@ workflow:
|
||||
run: echo a
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_ok(), "Valid parallel workflow should pass, got: {:?}", result.err());
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Valid parallel workflow should pass, got: {:?}",
|
||||
result.err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -225,7 +257,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("Duplicate step name"),
|
||||
"Expected duplicate name error for hook, got: {err}"
|
||||
@@ -250,7 +285,11 @@ workflow:
|
||||
run: echo ok
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_ok(), "Valid on_success hook should pass, got: {:?}", result.err());
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Valid on_success hook should pass, got: {:?}",
|
||||
result.err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -271,7 +310,11 @@ workflow:
|
||||
run: cleanup.sh
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_ok(), "Valid ensure hook should pass, got: {:?}", result.err());
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Valid ensure hook should pass, got: {:?}",
|
||||
result.err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -320,7 +363,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("Duplicate step name") && err.contains("task-a"),
|
||||
"Expected duplicate name in parallel children, got: {err}"
|
||||
@@ -341,7 +387,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("config"),
|
||||
"Expected error about missing config, got: {err}"
|
||||
@@ -362,7 +411,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("config.workflow"),
|
||||
"Expected error about missing config.workflow, got: {err}"
|
||||
@@ -382,7 +434,11 @@ workflow:
|
||||
workflow: child-wf
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_ok(), "Valid workflow step should pass, got: {:?}", result.err());
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Valid workflow step should pass, got: {:?}",
|
||||
result.err()
|
||||
);
|
||||
}
|
||||
|
||||
// --- Multi-workflow validation tests ---
|
||||
@@ -407,7 +463,11 @@ workflows:
|
||||
run: cargo test
|
||||
"#;
|
||||
let result = load_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_ok(), "Valid multi-workflow should pass, got: {:?}", result.err());
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Valid multi-workflow should pass, got: {:?}",
|
||||
result.err()
|
||||
);
|
||||
assert_eq!(result.unwrap().len(), 2);
|
||||
}
|
||||
|
||||
@@ -432,7 +492,10 @@ workflows:
|
||||
"#;
|
||||
let result = load_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("Duplicate workflow ID"),
|
||||
"Expected duplicate workflow ID error, got: {err}"
|
||||
@@ -461,7 +524,10 @@ workflows:
|
||||
"#;
|
||||
let result = load_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("Cannot specify both"),
|
||||
"Expected error about both workflow and workflows, got: {err}"
|
||||
@@ -476,7 +542,10 @@ something_else:
|
||||
"#;
|
||||
let result = load_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("Must specify either"),
|
||||
"Expected error about missing workflow/workflows, got: {err}"
|
||||
@@ -490,7 +559,10 @@ workflows: []
|
||||
"#;
|
||||
let result = load_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("empty"),
|
||||
"Expected error about empty workflows, got: {err}"
|
||||
@@ -520,7 +592,10 @@ workflows:
|
||||
"#;
|
||||
let result = load_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("Circular workflow reference"),
|
||||
"Expected circular reference error, got: {err}"
|
||||
@@ -541,7 +616,10 @@ workflows:
|
||||
"#;
|
||||
let result = load_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("Circular workflow reference"),
|
||||
"Expected circular reference error, got: {err}"
|
||||
@@ -568,7 +646,11 @@ workflows:
|
||||
run: echo working
|
||||
"#;
|
||||
let result = load_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_ok(), "Valid workflow reference should pass, got: {:?}", result.err());
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Valid workflow reference should pass, got: {:?}",
|
||||
result.err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -585,7 +667,11 @@ workflow:
|
||||
workflow: some-external-wf
|
||||
"#;
|
||||
let result = load_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_ok(), "External workflow ref should not error, got: {:?}", result.err());
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"External workflow ref should not error, got: {:?}",
|
||||
result.err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -609,7 +695,10 @@ workflows:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("Expected single workflow"),
|
||||
"Expected single workflow error, got: {err}"
|
||||
@@ -631,7 +720,11 @@ workflows:
|
||||
run: echo hello
|
||||
"#;
|
||||
let result = load_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_ok(), "Single workflow in multi-mode should pass, got: {:?}", result.err());
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Single workflow in multi-mode should pass, got: {:?}",
|
||||
result.err()
|
||||
);
|
||||
assert_eq!(result.unwrap().len(), 1);
|
||||
}
|
||||
|
||||
@@ -662,7 +755,11 @@ workflows:
|
||||
run: echo gamma
|
||||
"#;
|
||||
let result = load_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_ok(), "Multiple independent workflows should pass, got: {:?}", result.err());
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Multiple independent workflows should pass, got: {:?}",
|
||||
result.err()
|
||||
);
|
||||
assert_eq!(result.unwrap().len(), 3);
|
||||
}
|
||||
|
||||
@@ -686,7 +783,11 @@ workflows:
|
||||
run: echo working
|
||||
"#;
|
||||
let result = load_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_ok(), "Cross-referenced workflows should pass, got: {:?}", result.err());
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Cross-referenced workflows should pass, got: {:?}",
|
||||
result.err()
|
||||
);
|
||||
}
|
||||
|
||||
// --- Cycle detection edge cases ---
|
||||
@@ -719,7 +820,10 @@ workflows:
|
||||
"#;
|
||||
let result = load_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("Circular workflow reference"),
|
||||
"Expected circular reference error for 3-node cycle, got: {err}"
|
||||
@@ -753,7 +857,11 @@ workflows:
|
||||
run: echo done
|
||||
"#;
|
||||
let result = load_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_ok(), "Linear chain should not be a cycle, got: {:?}", result.err());
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Linear chain should not be a cycle, got: {:?}",
|
||||
result.err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -794,7 +902,11 @@ workflows:
|
||||
run: echo done
|
||||
"#;
|
||||
let result = load_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_ok(), "Diamond dependency should not be a cycle, got: {:?}", result.err());
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Diamond dependency should not be a cycle, got: {:?}",
|
||||
result.err()
|
||||
);
|
||||
}
|
||||
|
||||
// --- Deno step validation ---
|
||||
@@ -811,7 +923,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("Deno") && err.contains("config"),
|
||||
"Expected Deno config error, got: {err}"
|
||||
@@ -833,7 +948,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("Deno") && (err.contains("script") || err.contains("file")),
|
||||
"Expected Deno script/file error, got: {err}"
|
||||
@@ -854,7 +972,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("BuildKit") && err.contains("config"),
|
||||
"Expected BuildKit config error, got: {err}"
|
||||
@@ -875,7 +996,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("dockerfile"),
|
||||
"Expected dockerfile error, got: {err}"
|
||||
@@ -896,7 +1020,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("context"),
|
||||
"Expected context error, got: {err}"
|
||||
@@ -919,7 +1046,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("push") && err.contains("tags"),
|
||||
"Expected push/tags error, got: {err}"
|
||||
@@ -970,7 +1100,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("Containerd") && err.contains("config"),
|
||||
"Expected Containerd config error, got: {err}"
|
||||
@@ -991,11 +1124,11 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
assert!(
|
||||
err.contains("image"),
|
||||
"Expected image error, got: {err}"
|
||||
);
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(err.contains("image"), "Expected image error, got: {err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1012,7 +1145,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("run") || err.contains("command"),
|
||||
"Expected run/command error, got: {err}"
|
||||
@@ -1037,7 +1173,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("cannot have both"),
|
||||
"Expected 'cannot have both' error, got: {err}"
|
||||
@@ -1060,7 +1199,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("network") && err.contains("overlay"),
|
||||
"Expected invalid network error, got: {err}"
|
||||
@@ -1111,7 +1253,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("pull") && err.contains("aggressive"),
|
||||
"Expected invalid pull policy error, got: {err}"
|
||||
@@ -1190,7 +1335,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("config"),
|
||||
"Expected config error for invalid hook, got: {err}"
|
||||
@@ -1214,7 +1362,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("config"),
|
||||
"Expected config error for invalid on_success hook, got: {err}"
|
||||
@@ -1238,7 +1389,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("config"),
|
||||
"Expected config error for invalid ensure hook, got: {err}"
|
||||
@@ -1261,7 +1415,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("config"),
|
||||
"Expected config error for deeply nested invalid step, got: {err}"
|
||||
@@ -1301,7 +1458,10 @@ workflows:
|
||||
"#;
|
||||
let result = load_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("Circular workflow reference"),
|
||||
"Expected circular reference from hooks, got: {err}"
|
||||
@@ -1339,7 +1499,10 @@ workflows:
|
||||
"#;
|
||||
let result = load_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("Circular workflow reference"),
|
||||
"Expected circular reference from ensure hooks, got: {err}"
|
||||
@@ -1371,7 +1534,10 @@ workflows:
|
||||
"#;
|
||||
let result = load_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("Circular workflow reference"),
|
||||
"Expected circular reference from parallel blocks, got: {err}"
|
||||
@@ -1394,7 +1560,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("Unknown step type") && err.contains("terraform"),
|
||||
"Expected unknown step type error, got: {err}"
|
||||
@@ -1408,7 +1577,10 @@ fn load_workflow_from_nonexistent_file_returns_io_error() {
|
||||
let path = std::path::Path::new("/tmp/nonexistent_wfe_test_file.yaml");
|
||||
let result = wfe_yaml::load_workflow(path, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("IO error") || err.contains("No such file"),
|
||||
"Expected IO error, got: {err}"
|
||||
@@ -1435,7 +1607,11 @@ workflow:
|
||||
equals: true
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_ok(), "Field path to known input should pass, got: {:?}", result.err());
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Field path to known input should pass, got: {:?}",
|
||||
result.err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1458,7 +1634,11 @@ workflow:
|
||||
equals: success
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_ok(), "Field path to known output should pass, got: {:?}", result.err());
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Field path to known output should pass, got: {:?}",
|
||||
result.err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1480,7 +1660,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("unknown input field") && err.contains("nonexistent"),
|
||||
"Expected unknown input field error, got: {err}"
|
||||
@@ -1508,7 +1691,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("unknown output field") && err.contains("missing"),
|
||||
"Expected unknown output field error, got: {err}"
|
||||
@@ -1534,7 +1720,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("gt/gte/lt/lte") && err.contains("number/integer"),
|
||||
"Expected type mismatch error, got: {err}"
|
||||
@@ -1559,7 +1748,11 @@ workflow:
|
||||
gt: 5
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_ok(), "gt on number should pass, got: {:?}", result.err());
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"gt on number should pass, got: {:?}",
|
||||
result.err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1581,7 +1774,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("contains") && err.contains("string/list"),
|
||||
"Expected type mismatch error for contains, got: {err}"
|
||||
@@ -1606,7 +1802,11 @@ workflow:
|
||||
contains: needle
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_ok(), "contains on string should pass, got: {:?}", result.err());
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"contains on string should pass, got: {:?}",
|
||||
result.err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1627,7 +1827,11 @@ workflow:
|
||||
contains: release
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_ok(), "contains on list should pass, got: {:?}", result.err());
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"contains on list should pass, got: {:?}",
|
||||
result.err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1649,7 +1853,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("is_null/is_not_null") && err.contains("optional"),
|
||||
"Expected type mismatch error for is_null, got: {err}"
|
||||
@@ -1674,7 +1881,11 @@ workflow:
|
||||
is_null: true
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_ok(), "is_null on optional should pass, got: {:?}", result.err());
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"is_null on optional should pass, got: {:?}",
|
||||
result.err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1693,7 +1904,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("never produced") && err.contains("result"),
|
||||
"Expected unused output error, got: {err}"
|
||||
@@ -1717,7 +1931,11 @@ workflow:
|
||||
- name: result
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_ok(), "Output produced by step should pass, got: {:?}", result.err());
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Output produced by step should pass, got: {:?}",
|
||||
result.err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1734,7 +1952,11 @@ workflow:
|
||||
run: echo hi
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_ok(), "No outputs schema should not cause error, got: {:?}", result.err());
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"No outputs schema should not cause error, got: {:?}",
|
||||
result.err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1780,7 +2002,10 @@ workflow:
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("inputs") && err.contains("outputs"),
|
||||
"Expected error about invalid path segment, got: {err}"
|
||||
@@ -1826,9 +2051,12 @@ workflow:
|
||||
let main_path = dir.path().join("main.yaml");
|
||||
std::fs::write(&main_path, &main_yaml).unwrap();
|
||||
|
||||
let result =
|
||||
wfe_yaml::load_workflow_with_includes(&main_yaml, &main_path, &HashMap::new());
|
||||
assert!(result.is_ok(), "Include single file should work, got: {:?}", result.err());
|
||||
let result = wfe_yaml::load_workflow_with_includes(&main_yaml, &main_path, &HashMap::new());
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Include single file should work, got: {:?}",
|
||||
result.err()
|
||||
);
|
||||
let workflows = result.unwrap();
|
||||
assert_eq!(workflows.len(), 2);
|
||||
let ids: Vec<&str> = workflows.iter().map(|w| w.definition.id.as_str()).collect();
|
||||
@@ -1887,9 +2115,12 @@ workflow:
|
||||
let main_path = dir.path().join("main.yaml");
|
||||
std::fs::write(&main_path, main_yaml).unwrap();
|
||||
|
||||
let result =
|
||||
wfe_yaml::load_workflow_with_includes(main_yaml, &main_path, &HashMap::new());
|
||||
assert!(result.is_ok(), "Include multiple files should work, got: {:?}", result.err());
|
||||
let result = wfe_yaml::load_workflow_with_includes(main_yaml, &main_path, &HashMap::new());
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Include multiple files should work, got: {:?}",
|
||||
result.err()
|
||||
);
|
||||
let workflows = result.unwrap();
|
||||
assert_eq!(workflows.len(), 3);
|
||||
}
|
||||
@@ -1931,9 +2162,12 @@ workflow:
|
||||
let main_path = dir.path().join("main.yaml");
|
||||
std::fs::write(&main_path, main_yaml).unwrap();
|
||||
|
||||
let result =
|
||||
wfe_yaml::load_workflow_with_includes(main_yaml, &main_path, &HashMap::new());
|
||||
assert!(result.is_ok(), "Override should work, got: {:?}", result.err());
|
||||
let result = wfe_yaml::load_workflow_with_includes(main_yaml, &main_path, &HashMap::new());
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Override should work, got: {:?}",
|
||||
result.err()
|
||||
);
|
||||
let workflows = result.unwrap();
|
||||
// Only 1 workflow since main takes precedence over included
|
||||
assert_eq!(workflows.len(), 1);
|
||||
@@ -1972,10 +2206,12 @@ workflow:
|
||||
let main_path = dir.path().join("main.yaml");
|
||||
std::fs::write(&main_path, main_yaml).unwrap();
|
||||
|
||||
let result =
|
||||
wfe_yaml::load_workflow_with_includes(main_yaml, &main_path, &HashMap::new());
|
||||
let result = wfe_yaml::load_workflow_with_includes(main_yaml, &main_path, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("nonexistent") || err.contains("not found") || err.contains("No such file"),
|
||||
"Expected file not found error, got: {err}"
|
||||
@@ -2018,10 +2254,12 @@ workflow:
|
||||
|
||||
let a_path = dir.path().join("a.yaml");
|
||||
|
||||
let result =
|
||||
wfe_yaml::load_workflow_with_includes(a_yaml, &a_path, &HashMap::new());
|
||||
let result = wfe_yaml::load_workflow_with_includes(a_yaml, &a_path, &HashMap::new());
|
||||
assert!(result.is_err());
|
||||
let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
|
||||
let err = match result {
|
||||
Err(e) => e.to_string(),
|
||||
Ok(_) => panic!("expected error"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("Circular include"),
|
||||
"Expected circular include error, got: {err}"
|
||||
@@ -2053,7 +2291,11 @@ workflow:
|
||||
equals: true
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_ok(), "equals should work on all types, got: {:?}", result.err());
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"equals should work on all types, got: {:?}",
|
||||
result.err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2074,7 +2316,11 @@ workflow:
|
||||
gte: 10
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_ok(), "gte on integer should pass, got: {:?}", result.err());
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"gte on integer should pass, got: {:?}",
|
||||
result.err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2096,5 +2342,9 @@ workflow:
|
||||
gt: 5
|
||||
"#;
|
||||
let result = load_single_workflow_from_str(yaml, &HashMap::new());
|
||||
assert!(result.is_ok(), "any type should allow gt, got: {:?}", result.err());
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"any type should allow gt, got: {:?}",
|
||||
result.err()
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user