Skip to content

Commit d767022

Browse files
test: fill gaps in types.rs, common.rs trigger functions, and add pipeline-trigger integration fixture (#21)
* Initial plan * test: fill test gaps in types.rs, common.rs, and add pipeline-trigger-agent integration fixture Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
1 parent 8478453 commit d767022

4 files changed

Lines changed: 439 additions & 0 deletions

File tree

src/compile/common.rs

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,4 +656,183 @@ mod tests {
656656
assert_eq!(sanitize_filename("agent@v1.0"), "agent-v1-0");
657657
assert_eq!(sanitize_filename("test_case"), "test-case");
658658
}
659+
660+
// ─── generate_pr_trigger ─────────────────────────────────────────────────
661+
662+
#[test]
663+
fn test_generate_pr_trigger_no_triggers_no_schedule() {
664+
let result = generate_pr_trigger(&None, false);
665+
assert!(result.is_empty(), "Should be empty when no triggers configured");
666+
}
667+
668+
#[test]
669+
fn test_generate_pr_trigger_schedule_only() {
670+
let result = generate_pr_trigger(&None, true);
671+
assert!(result.contains("pr: none"));
672+
assert!(result.contains("only run on schedule"));
673+
}
674+
675+
#[test]
676+
fn test_generate_pr_trigger_pipeline_only() {
677+
let triggers = Some(crate::compile::types::TriggerConfig {
678+
pipeline: Some(crate::compile::types::PipelineTrigger {
679+
name: "Build".into(),
680+
project: None,
681+
branches: vec![],
682+
}),
683+
});
684+
let result = generate_pr_trigger(&triggers, false);
685+
assert!(result.contains("pr: none"));
686+
assert!(result.contains("upstream pipeline"));
687+
}
688+
689+
#[test]
690+
fn test_generate_pr_trigger_both_pipeline_and_schedule() {
691+
let triggers = Some(crate::compile::types::TriggerConfig {
692+
pipeline: Some(crate::compile::types::PipelineTrigger {
693+
name: "Build".into(),
694+
project: None,
695+
branches: vec![],
696+
}),
697+
});
698+
let result = generate_pr_trigger(&triggers, true);
699+
assert!(result.contains("pr: none"));
700+
// Contains text indicating both reasons
701+
assert!(result.contains("schedule") || result.contains("upstream pipeline"));
702+
}
703+
704+
// ─── generate_ci_trigger ─────────────────────────────────────────────────
705+
706+
#[test]
707+
fn test_generate_ci_trigger_no_triggers_no_schedule() {
708+
let result = generate_ci_trigger(&None, false);
709+
assert!(result.is_empty(), "Should be empty when no triggers configured");
710+
}
711+
712+
#[test]
713+
fn test_generate_ci_trigger_schedule_only() {
714+
let result = generate_ci_trigger(&None, true);
715+
assert_eq!(result, "trigger: none");
716+
}
717+
718+
#[test]
719+
fn test_generate_ci_trigger_pipeline_only() {
720+
let triggers = Some(crate::compile::types::TriggerConfig {
721+
pipeline: Some(crate::compile::types::PipelineTrigger {
722+
name: "Build".into(),
723+
project: None,
724+
branches: vec![],
725+
}),
726+
});
727+
let result = generate_ci_trigger(&triggers, false);
728+
assert_eq!(result, "trigger: none");
729+
}
730+
731+
#[test]
732+
fn test_generate_ci_trigger_both_pipeline_and_schedule() {
733+
let triggers = Some(crate::compile::types::TriggerConfig {
734+
pipeline: Some(crate::compile::types::PipelineTrigger {
735+
name: "Build".into(),
736+
project: None,
737+
branches: vec![],
738+
}),
739+
});
740+
let result = generate_ci_trigger(&triggers, true);
741+
assert_eq!(result, "trigger: none");
742+
}
743+
744+
// ─── generate_pipeline_resources ─────────────────────────────────────────
745+
746+
#[test]
747+
fn test_generate_pipeline_resources_no_triggers() {
748+
let result = generate_pipeline_resources(&None).unwrap();
749+
assert!(result.is_empty());
750+
}
751+
752+
#[test]
753+
fn test_generate_pipeline_resources_empty_trigger_config() {
754+
let triggers = Some(crate::compile::types::TriggerConfig { pipeline: None });
755+
let result = generate_pipeline_resources(&triggers).unwrap();
756+
assert!(result.is_empty());
757+
}
758+
759+
#[test]
760+
fn test_generate_pipeline_resources_with_branches() {
761+
let triggers = Some(crate::compile::types::TriggerConfig {
762+
pipeline: Some(crate::compile::types::PipelineTrigger {
763+
name: "Build Pipeline".into(),
764+
project: Some("OtherProject".into()),
765+
branches: vec!["main".into(), "release/*".into()],
766+
}),
767+
});
768+
let result = generate_pipeline_resources(&triggers).unwrap();
769+
assert!(result.contains("source: 'Build Pipeline'"));
770+
assert!(result.contains("OtherProject"));
771+
assert!(result.contains("main"));
772+
assert!(result.contains("release/*"));
773+
// Should use branch include list, not `trigger: true`
774+
assert!(result.contains("branches:"));
775+
assert!(!result.contains("trigger: true"));
776+
}
777+
778+
#[test]
779+
fn test_generate_pipeline_resources_without_branches_triggers_on_any() {
780+
let triggers = Some(crate::compile::types::TriggerConfig {
781+
pipeline: Some(crate::compile::types::PipelineTrigger {
782+
name: "My Pipeline".into(),
783+
project: None,
784+
branches: vec![],
785+
}),
786+
});
787+
let result = generate_pipeline_resources(&triggers).unwrap();
788+
assert!(result.contains("source: 'My Pipeline'"));
789+
assert!(result.contains("trigger: true"));
790+
// No project when not specified
791+
assert!(!result.contains("project:"));
792+
}
793+
794+
#[test]
795+
fn test_generate_pipeline_resources_resource_id_is_snake_case() {
796+
let triggers = Some(crate::compile::types::TriggerConfig {
797+
pipeline: Some(crate::compile::types::PipelineTrigger {
798+
name: "My Build Pipeline".into(),
799+
project: None,
800+
branches: vec![],
801+
}),
802+
});
803+
let result = generate_pipeline_resources(&triggers).unwrap();
804+
// The pipeline resource ID should be snake_case derived from the name
805+
assert!(result.contains("pipeline: my_build_pipeline"));
806+
}
807+
808+
// ─── generate_cancel_previous_builds ─────────────────────────────────────
809+
810+
#[test]
811+
fn test_generate_cancel_previous_builds_no_triggers() {
812+
let result = generate_cancel_previous_builds(&None);
813+
assert!(result.is_empty());
814+
}
815+
816+
#[test]
817+
fn test_generate_cancel_previous_builds_no_pipeline_trigger() {
818+
let triggers = Some(crate::compile::types::TriggerConfig { pipeline: None });
819+
let result = generate_cancel_previous_builds(&triggers);
820+
assert!(result.is_empty());
821+
}
822+
823+
#[test]
824+
fn test_generate_cancel_previous_builds_with_pipeline_trigger() {
825+
let triggers = Some(crate::compile::types::TriggerConfig {
826+
pipeline: Some(crate::compile::types::PipelineTrigger {
827+
name: "Build".into(),
828+
project: None,
829+
branches: vec![],
830+
}),
831+
});
832+
let result = generate_cancel_previous_builds(&triggers);
833+
assert!(!result.is_empty());
834+
assert!(result.contains("Cancel previous queued builds"));
835+
assert!(result.contains("SYSTEM_ACCESSTOKEN"));
836+
assert!(result.contains("cancelling"));
837+
}
659838
}

src/compile/types.rs

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,3 +352,149 @@ pub struct PipelineTrigger {
352352
#[serde(default)]
353353
pub branches: Vec<String>,
354354
}
355+
356+
#[cfg(test)]
357+
mod tests {
358+
use super::*;
359+
360+
// ─── PoolConfig deserialization ──────────────────────────────────────────
361+
362+
#[test]
363+
fn test_pool_config_string_form() {
364+
let yaml = "pool: MyPool";
365+
let fm: serde_yaml::Value = serde_yaml::from_str(yaml).unwrap();
366+
let pool: PoolConfig = serde_yaml::from_value(fm["pool"].clone()).unwrap();
367+
assert_eq!(pool.name(), "MyPool");
368+
assert_eq!(pool.os(), "linux"); // default
369+
}
370+
371+
#[test]
372+
fn test_pool_config_object_form_with_os() {
373+
let yaml = "pool:\n name: WinPool\n os: windows";
374+
let fm: serde_yaml::Value = serde_yaml::from_str(yaml).unwrap();
375+
let pool: PoolConfig = serde_yaml::from_value(fm["pool"].clone()).unwrap();
376+
assert_eq!(pool.name(), "WinPool");
377+
assert_eq!(pool.os(), "windows");
378+
}
379+
380+
#[test]
381+
fn test_pool_config_object_form_default_os() {
382+
let yaml = "pool:\n name: LinuxPool";
383+
let fm: serde_yaml::Value = serde_yaml::from_str(yaml).unwrap();
384+
let pool: PoolConfig = serde_yaml::from_value(fm["pool"].clone()).unwrap();
385+
assert_eq!(pool.name(), "LinuxPool");
386+
assert_eq!(pool.os(), "linux");
387+
}
388+
389+
#[test]
390+
fn test_pool_config_default() {
391+
let pool = PoolConfig::default();
392+
assert_eq!(pool.name(), "AZS-1ES-L-MMS-ubuntu-22.04");
393+
assert_eq!(pool.os(), "linux");
394+
}
395+
396+
// ─── ScheduleConfig deserialization ─────────────────────────────────────
397+
398+
#[test]
399+
fn test_schedule_config_simple_has_empty_branches() {
400+
let sc = ScheduleConfig::Simple("daily around 14:00".to_string());
401+
assert_eq!(sc.expression(), "daily around 14:00");
402+
assert!(sc.branches().is_empty());
403+
}
404+
405+
#[test]
406+
fn test_schedule_config_with_options_returns_branches() {
407+
let yaml = "run: weekly on monday\nbranches:\n - main\n - release/*";
408+
let opts: ScheduleOptions = serde_yaml::from_str(yaml).unwrap();
409+
let sc = ScheduleConfig::WithOptions(opts);
410+
assert_eq!(sc.expression(), "weekly on monday");
411+
assert_eq!(sc.branches(), &["main", "release/*"]);
412+
}
413+
414+
#[test]
415+
fn test_schedule_config_with_options_empty_branches() {
416+
let yaml = "run: hourly";
417+
let opts: ScheduleOptions = serde_yaml::from_str(yaml).unwrap();
418+
let sc = ScheduleConfig::WithOptions(opts);
419+
assert_eq!(sc.expression(), "hourly");
420+
assert!(sc.branches().is_empty());
421+
}
422+
423+
#[test]
424+
fn test_schedule_config_deserialized_as_simple_string() {
425+
let yaml = "schedule: daily around 14:00";
426+
let fm: serde_yaml::Value = serde_yaml::from_str(yaml).unwrap();
427+
let sc: ScheduleConfig = serde_yaml::from_value(fm["schedule"].clone()).unwrap();
428+
assert_eq!(sc.expression(), "daily around 14:00");
429+
assert!(sc.branches().is_empty());
430+
}
431+
432+
#[test]
433+
fn test_schedule_config_deserialized_as_object() {
434+
let yaml = "schedule:\n run: weekly on friday\n branches:\n - main\n - develop";
435+
let fm: serde_yaml::Value = serde_yaml::from_str(yaml).unwrap();
436+
let sc: ScheduleConfig = serde_yaml::from_value(fm["schedule"].clone()).unwrap();
437+
assert_eq!(sc.expression(), "weekly on friday");
438+
assert_eq!(sc.branches(), &["main", "develop"]);
439+
}
440+
441+
// ─── EngineConfig deserialization ────────────────────────────────────────
442+
443+
#[test]
444+
fn test_engine_config_simple_string() {
445+
let ec = EngineConfig::Simple("gpt-5.1".to_string());
446+
assert_eq!(ec.model(), "gpt-5.1");
447+
assert_eq!(ec.max_turns(), None);
448+
assert_eq!(ec.timeout_minutes(), None);
449+
}
450+
451+
#[test]
452+
fn test_engine_config_full_object() {
453+
let yaml = "model: claude-sonnet-4.5\nmax-turns: 50\ntimeout-minutes: 30";
454+
let opts: EngineOptions = serde_yaml::from_str(yaml).unwrap();
455+
let ec = EngineConfig::Full(opts);
456+
assert_eq!(ec.model(), "claude-sonnet-4.5");
457+
assert_eq!(ec.max_turns(), Some(50));
458+
assert_eq!(ec.timeout_minutes(), Some(30));
459+
}
460+
461+
#[test]
462+
fn test_engine_config_full_object_partial_fields() {
463+
let yaml = "max-turns: 10";
464+
let opts: EngineOptions = serde_yaml::from_str(yaml).unwrap();
465+
let ec = EngineConfig::Full(opts);
466+
// model defaults to claude-opus-4.5 when not specified
467+
assert_eq!(ec.model(), "claude-opus-4.5");
468+
assert_eq!(ec.max_turns(), Some(10));
469+
assert_eq!(ec.timeout_minutes(), None);
470+
}
471+
472+
#[test]
473+
fn test_engine_config_default() {
474+
let ec = EngineConfig::default();
475+
assert_eq!(ec.model(), "claude-opus-4.5");
476+
assert_eq!(ec.max_turns(), None);
477+
assert_eq!(ec.timeout_minutes(), None);
478+
}
479+
480+
#[test]
481+
fn test_engine_config_deserialized_as_string() {
482+
let yaml = "engine: my-custom-model";
483+
let fm: serde_yaml::Value = serde_yaml::from_str(yaml).unwrap();
484+
let ec: EngineConfig = serde_yaml::from_value(fm["engine"].clone()).unwrap();
485+
assert_eq!(ec.model(), "my-custom-model");
486+
assert_eq!(ec.max_turns(), None);
487+
assert_eq!(ec.timeout_minutes(), None);
488+
}
489+
490+
#[test]
491+
fn test_engine_config_deserialized_as_object() {
492+
let yaml =
493+
"engine:\n model: claude-opus-4.5\n max-turns: 50\n timeout-minutes: 30";
494+
let fm: serde_yaml::Value = serde_yaml::from_str(yaml).unwrap();
495+
let ec: EngineConfig = serde_yaml::from_value(fm["engine"].clone()).unwrap();
496+
assert_eq!(ec.model(), "claude-opus-4.5");
497+
assert_eq!(ec.max_turns(), Some(50));
498+
assert_eq!(ec.timeout_minutes(), Some(30));
499+
}
500+
}

0 commit comments

Comments
 (0)