Skip to content

πŸ§ͺ Test gap analysis β€” 4 gaps found in types.rs, execute.rs, common.rs triggersΒ #20

@github-actions

Description

@github-actions

Test Gap Analysis

Test suite snapshot: 209 unit tests, 22 integration tests (8 compiler + 5 mcp_firewall + 9 proxy), 3 test fixtures β€” 231 total

Priority Gaps

Module Function/Path Why It Matters Suggested Test
compile/types.rs PoolConfig::name, PoolConfig::os, ScheduleConfig::expression/branches, EngineConfig::model/max_turns/timeout_minutes Core front matter parsing types (7 pub fns, 0 tests). Serde deserialization of string vs. object formats entirely untested. Deserialize from YAML string and object formats; verify fallback defaults
execute.rs execute_safe_output β€” unknown tool path, missing name field, malformed JSON Stage 2 execution dispatcher: the bail!("Unknown tool type: {}") branch and missing-name error are never hit by any test Unit test with mock NDJSON records for each error path
compile/common.rs generate_pr_trigger, generate_ci_trigger, generate_pipeline_resources, generate_cancel_previous_builds All four combinatorial trigger-generation functions (schedule Γ— pipeline trigger) have no direct unit tests Test all 4 combinations: no trigger, schedule-only, pipeline-only, both
tests/fixtures/ No fixture with triggers.pipeline: configured Integration tests never exercise pipeline resource generation, trigger suppression, or cancel-previous-builds YAML output Add pipeline-trigger-agent.md fixture and assert generated YAML contains resources.pipelines block

Suggested Test Cases

1. compile/types.rs β€” PoolConfig deserialization

#[test]
fn test_pool_config_string_form() {
    let yaml = "pool: MyPool";
    let fm: serde_yaml::Value = serde_yaml::from_str(yaml).unwrap();
    let pool: PoolConfig = serde_yaml::from_value(fm["pool"].clone()).unwrap();
    assert_eq!(pool.name(), "MyPool");
    assert_eq!(pool.os(), "linux"); // default
}

#[test]
fn test_pool_config_object_form_with_os() {
    let yaml = "pool:\n  name: WinPool\n  os: windows";
    let fm: serde_yaml::Value = serde_yaml::from_str(yaml).unwrap();
    let pool: PoolConfig = serde_yaml::from_value(fm["pool"].clone()).unwrap();
    assert_eq!(pool.name(), "WinPool");
    assert_eq!(pool.os(), "windows");
}

2. compile/types.rs β€” ScheduleConfig deserialization

#[test]
fn test_schedule_config_simple_has_empty_branches() {
    let sc = ScheduleConfig::Simple("daily around 14:00".to_string());
    assert_eq!(sc.expression(), "daily around 14:00");
    assert!(sc.branches().is_empty());
}

#[test]
fn test_schedule_config_with_options_returns_branches() {
    let yaml = "run: weekly on monday\nbranches:\n  - main\n  - release/*";
    let opts: ScheduleOptions = serde_yaml::from_str(yaml).unwrap();
    let sc = ScheduleConfig::WithOptions(opts);
    assert_eq!(sc.branches(), &["main", "release/*"]);
}

3. compile/types.rs β€” EngineConfig deserialization

#[test]
fn test_engine_config_simple_string() {
    let ec = EngineConfig::Simple("gpt-5.1".to_string());
    assert_eq!(ec.model(), "gpt-5.1");
    assert_eq!(ec.max_turns(), None);
    assert_eq!(ec.timeout_minutes(), None);
}

#[test]
fn test_engine_config_full_object() {
    let yaml = "model: claude-sonnet-4.5\nmax-turns: 50\ntimeout-minutes: 30";
    let opts: EngineOptions = serde_yaml::from_str(yaml).unwrap();
    let ec = EngineConfig::Full(opts);
    assert_eq!(ec.model(), "claude-sonnet-4.5");
    assert_eq!(ec.max_turns(), Some(50));
    assert_eq!(ec.timeout_minutes(), Some(30));
}

4. compile/common.rs β€” trigger generation combinatorics

#[test]
fn test_generate_pr_trigger_no_triggers_no_schedule() {
    let result = generate_pr_trigger(&None, false);
    assert!(result.is_empty(), "Should be empty when no triggers configured");
}

#[test]
fn test_generate_pr_trigger_schedule_only() {
    let result = generate_pr_trigger(&None, true);
    assert!(result.contains("pr: none"));
    assert!(result.contains("only run on schedule"));
}

#[test]
fn test_generate_ci_trigger_pipeline_trigger_suppresses_ci() {
    let triggers = Some(TriggerConfig {
        pipeline: Some(PipelineTrigger { name: "Build".into(), project: None, branches: vec![] }),
    });
    let result = generate_ci_trigger(&triggers, false);
    assert!(result.contains("trigger: none"));
}

#[test]
fn test_generate_pipeline_resources_with_branches() {
    let triggers = Some(TriggerConfig {
        pipeline: Some(PipelineTrigger {
            name: "Build Pipeline".into(),
            project: Some("OtherProject".into()),
            branches: vec!["main".into(), "release/*".into()],
        }),
    });
    let result = generate_pipeline_resources(&triggers).unwrap();
    assert!(result.contains("source: Build Pipeline"));
    assert!(result.contains("OtherProject"));
    assert!(result.contains("main"));
}

5. execute.rs β€” error paths for Stage 2 dispatcher

#[tokio::test]
async fn test_execute_safe_output_missing_name_field() {
    let record = serde_json::json!({"type": "noop", "context": "test"});
    // Missing 'name' field should return an error
    let result = execute_safe_output(&record, &config, &work_dir).await;
    assert!(result.is_err());
    assert!(result.unwrap_err().to_string().contains("missing 'name' field"));
}

#[tokio::test]
async fn test_execute_safe_output_unknown_tool_type() {
    let record = serde_json::json!({"name": "unknown-tool", "type": "unknown-tool"});
    let result = execute_safe_output(&record, &config, &work_dir).await;
    assert!(result.is_err());
    assert!(result.unwrap_err().to_string().contains("Unknown tool type"));
}

6. Integration fixture β€” pipeline trigger agent

---
name: "Pipeline Trigger Agent"
description: "Agent triggered by an upstream pipeline"
triggers:
  pipeline:
    name: "Build Pipeline"
    project: "OtherProject"
    branches:
      - main
---
## Task
Process artifacts from upstream build.

Expected assertions on compiled YAML:

  • Contains resources.pipelines[0].source: Build Pipeline
  • Contains trigger: none and pr: none
  • Contains cancel-previous-builds bash step
  • Does NOT contain schedules: block

Coverage Summary

Module Public Fns Tests Coverage Estimate
compile/types.rs 7 0 ~0%
execute.rs 2 0 ~0%
compile/common.rs 22 17 ~65% (trigger fns untested)
compile/standalone.rs 1 7 ~80% (via integration)
sanitize.rs 1 29 ~95%
fuzzy_schedule.rs 8 20 ~90%

This issue was created by the automated test gap finder. Previous run: 2026-03-06. Modules audited this cycle: all 23 source modules. Total tests: 231 (up from 226).

Generated by Test Gap Finder Β· β—·

Metadata

Metadata

Labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions