Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
5afa524
feat(ci): CI-01 added generate ci subcommand entrypoint
mitanuriel Mar 14, 2026
bee699b
refactor: rename submodule to ci_generation
mitanuriel Mar 14, 2026
2aba939
feat(ci): CI-02 CiContext struct and context collector
mitanuriel Mar 20, 2026
965a4de
feat(ci): CI-03 runtime version resolver with 8 unit tests
mitanuriel Mar 20, 2026
cc63af2
feat(ci): CI-04 dependency cache strategy module with 10 unit tests
mitanuriel Mar 20, 2026
7dce9ab
Merge remote-tracking branch 'origin/develop' into feature/ci-pipelin…
mitanuriel Mar 30, 2026
90a051f
chore: add .DS_Store to .gitignore
mitanuriel Mar 30, 2026
a2a8e4a
fix(ci): add Serialize derives to CiContext, RuntimeSetup, CacheConfi…
mitanuriel Mar 30, 2026
7f56576
refactor(ci): extract shared make_ctx test helper
mitanuriel Mar 30, 2026
4e0b72d
chore(ci): mark CI handler wiring gap for integration
mitanuriel Mar 30, 2026
1f539fe
feat(ci): CI-14 CiPipeline skeleton schema
mitanuriel Mar 30, 2026
e7a1675
feat(ci): CI-18 trigger configuration module
mitanuriel Mar 30, 2026
7210c2a
feat(ci): CI-15 placeholder token resolution engine
mitanuriel Mar 30, 2026
1d75373
feat(ci): CI-05 test step generator
mitanuriel Mar 30, 2026
469a66d
feat(ci): CI-06 lint step generator
mitanuriel Mar 30, 2026
a319b5d
feat(ci): CI-07 build step generator
mitanuriel Mar 30, 2026
12f92de
fix(ci): CI-18 scheduled placeholder, CI-06 Gradle Checkstyle variant
mitanuriel Mar 30, 2026
7535960
chore: ignore all .DS_Store files
mitanuriel Mar 30, 2026
d5034f3
feat(ci): CI-08 Docker build and tag step generator
mitanuriel Mar 30, 2026
883855c
feat(ci): CI-09 container image security scan step generator
mitanuriel Mar 30, 2026
2377872
feat(ci): CI-10 secret and credential leak scan step generator
mitanuriel Mar 30, 2026
a9612d9
fix(ci): expand schema fields for CI-08/09/10 spec gaps (buildx, SARI…
mitanuriel Mar 30, 2026
f150891
feat(ci): CI-11 GitHub Actions CI template builder
mitanuriel Mar 30, 2026
67bbd6f
feat(ci): CI-12 Azure Pipelines CI template builder
mitanuriel Mar 30, 2026
4a58bfc
feat(ci): CI-13 GCP Cloud Build CI template builder
mitanuriel Mar 30, 2026
0b69bb7
feat(ci): CI-22 .syncable.ci.toml config struct and merge
mitanuriel Mar 30, 2026
40aed8f
feat(ci): CI-19 secrets inventory generator
mitanuriel Mar 30, 2026
76b777a
feat(ci): CI-16 monorepo CI strategy generator
mitanuriel Mar 30, 2026
0b9fa69
feat(ci): CI-17 multi-version test matrix generator
mitanuriel Mar 30, 2026
08263d6
feat(ci): CI-20 CI file writer & conflict detection
mitanuriel Mar 30, 2026
fe18804
feat(ci): CI-20 close gaps — interactive prompt, merge, summary table
mitanuriel Mar 30, 2026
2609c2e
feat(ci): CI-21/23/24 dry-run renderer, coverage upload step, Slack n…
mitanuriel Mar 30, 2026
c844a44
fix(ci): CI-24 wire --notify flag and config key through handler (gap…
mitanuriel Mar 30, 2026
a07dd4c
test(ci): CI-25/26 unit and integration tests, language fixtures, ins…
mitanuriel Mar 31, 2026
4cd0756
feat(ci): CI-01/27/28 — wire full pipeline, telemetry, update docs
mitanuriel Mar 31, 2026
81de4b8
fix(ci): root-manifest language priority, platform SHA exprs, Azure/G…
mitanuriel Mar 31, 2026
7f35cf4
feat(ci): DEV-43 — secrets_doc scalar walker, platform-aware where_to…
mitanuriel Apr 8, 2026
71fdbda
feat(cd): CD generation foundation (CD-02, CD-17, CD-22)
mitanuriel Apr 14, 2026
fb0697f
feat(cd): Session 2 — Auth & Registry modules (CD-03, CD-04, CD-05, C…
mitanuriel Apr 14, 2026
c3b1ff1
feat(cd): Session 3 — Deploy steps, migration & health check (CD-07..…
mitanuriel Apr 14, 2026
e2c51ab
feat(cd): Templates, Writer, Pipeline Builder & CLI Entrypoint
mitanuriel Apr 14, 2026
2694691
feat(cd): Advanced features (CD-12..CD-16, CD-21, CD-29)
mitanuriel Apr 14, 2026
54706a2
feat(cd): combined CI+CD command, config, secrets doc & tests (CD-23/…
mitanuriel Apr 14, 2026
048fad1
docs(cd): add generate cd and generate ci-cd sections to command-over…
mitanuriel Apr 15, 2026
cd5e652
fix(CD-24): apply migration_command from .syncable.cd.toml to pipeline
mitanuriel Apr 15, 2026
89dd103
fix(cd): correct YAML step indentation in Azure/GCP/Hetzner templates
mitanuriel Apr 21, 2026
7a057f3
Merge remote-tracking branch 'origin/main' into feature/ci-pipeline-g…
mitanuriel Apr 24, 2026
5905719
Merge pull request #330 from syncable-dev/feature/ci-pipeline-generation
Alex793x May 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
test(ci): CI-25/26 unit and integration tests, language fixtures, ins…
…ta snapshot
  • Loading branch information
mitanuriel committed Mar 31, 2026
commit a07dd4c550304e6e170c1cce4262fc48d5992f3c
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ assert_cmd = "2"
predicates = "3"
tempfile = "3"
proptest = "1"
insta = { version = "1", features = ["yaml"] }

# Fast debug builds - prioritize compile speed over runtime
[profile.dev]
Expand Down
1 change: 0 additions & 1 deletion src/generator/ci_generation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,4 @@ pub mod token_resolver;
pub mod triggers;
pub mod writer;

#[cfg(test)]
pub mod test_helpers;
2 changes: 1 addition & 1 deletion src/generator/ci_generation/test_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::generator::ci_generation::context::{CiContext, PackageManager};
/// Fields that matter for the test under hand should be overridden by the
/// caller after construction. Using struct-update syntax is idiomatic:
///
/// ```rust
/// ```ignore
/// let ctx = make_base_ctx(dir.path(), "TypeScript");
/// let ctx = CiContext { package_manager: PackageManager::Npm, ..ctx };
/// ```
Expand Down
258 changes: 258 additions & 0 deletions tests/ci_generator_integration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
//! CI-26 — End-to-end integration tests for the CI generation subsystem.
//!
//! Tests template rendering at the full-pipeline level — valid YAML output,
//! required structural fields, and absence of hardcoded secrets.
//!
//! Also exercises `collect_ci_context` against the language fixture projects
//! in `tests/fixtures/ci/` to verify that context collection succeeds and
//! produces the expected primary language for each ecosystem.
//!
//! # Note on CI-01 wiring
//!
//! The CLI handler `handle_generate_ci` currently returns a static skeleton
//! rather than invoking the full pipeline. These tests exercise the template
//! layer directly. A companion test asserting the full CLI binary output will
//! be added once CI-01 (final wiring) replaces the stub.

use std::path::PathBuf;

use syncable_cli::cli::{CiFormat, CiPlatform};
use syncable_cli::generator::ci_generation::{
context::collect_ci_context,
schema::{
CiPipeline, InstallStep, RuntimeStep, SecretScanStep, TestStep, TriggerConfig,
},
templates,
};

// ── Shared helpers ────────────────────────────────────────────────────────────

fn minimal_pipeline(platform: CiPlatform, format: CiFormat) -> CiPipeline {
CiPipeline {
project_name: "integration-test-app".to_string(),
platform,
format,
triggers: TriggerConfig {
push_branches: vec!["main".to_string()],
pr_branches: vec!["main".to_string()],
tag_pattern: None,
scheduled: None,
},
runtime: RuntimeStep {
action: "actions/setup-node@v4".to_string(),
version: "20".to_string(),
},
cache: None,
install: InstallStep { command: "npm ci".to_string() },
lint: None,
test: TestStep {
command: "npm test".to_string(),
coverage_flag: None,
coverage_report_path: None,
},
build: None,
docker_build: None,
image_scan: None,
secret_scan: syncable_cli::generator::ci_generation::schema::SecretScanStep {
github_token_expr: "${{ secrets.GITHUB_TOKEN }}".to_string(),
gitleaks_license_secret: None,
},
upload_artifact: None,
unresolved_tokens: vec![],
}
}

/// Returns the absolute path to a CI language fixture directory.
fn fixture(lang: &str) -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("fixtures")
.join("ci")
.join(lang)
}

/// Asserts that `yaml` contains no string patterns that look like real
/// credential values (GitHub tokens, AWS keys, etc.).
fn assert_no_hardcoded_secrets(yaml: &str) {
// Real GitHub personal-access tokens start with "ghp_" followed by 36+ alphanum chars.
assert!(
!yaml.split_whitespace().any(|w| w.starts_with("ghp_") && w.len() > 10),
"output contains a GitHub token pattern: {yaml}"
);
// Real AWS access key IDs start with "AKIA" followed by exactly 16 uppercase chars.
assert!(
!yaml.split_whitespace().any(|w| {
w.starts_with("AKIA")
&& w.len() == 20
&& w[4..].chars().all(|c| c.is_ascii_uppercase() || c.is_ascii_digit())
}),
"output contains an AWS access key pattern: {yaml}"
);
}

// ── GitHub Actions end-to-end ─────────────────────────────────────────────────

#[test]
fn github_actions_output_is_valid_yaml() {
let yaml = templates::github_actions::render(&minimal_pipeline(
CiPlatform::Hetzner,
CiFormat::GithubActions,
));
serde_yaml::from_str::<serde_yaml::Value>(&yaml)
.expect("GitHub Actions output must be valid YAML");
}

#[test]
fn github_actions_output_contains_checkout_step() {
let yaml = templates::github_actions::render(&minimal_pipeline(
CiPlatform::Hetzner,
CiFormat::GithubActions,
));
assert!(
yaml.contains("actions/checkout"),
"GitHub Actions pipeline must include a checkout step"
);
}

#[test]
fn github_actions_output_contains_runtime_setup_step() {
let yaml = templates::github_actions::render(&minimal_pipeline(
CiPlatform::Hetzner,
CiFormat::GithubActions,
));
// Runtime setup action was injected into the pipeline.
assert!(
yaml.contains("setup-node"),
"pipeline must contain a runtime setup step"
);
}

#[test]
fn github_actions_output_contains_test_step() {
let yaml = templates::github_actions::render(&minimal_pipeline(
CiPlatform::Hetzner,
CiFormat::GithubActions,
));
assert!(yaml.contains("npm test"), "pipeline must contain the test command");
}

#[test]
fn github_actions_output_has_no_hardcoded_secrets() {
let yaml = templates::github_actions::render(&minimal_pipeline(
CiPlatform::Hetzner,
CiFormat::GithubActions,
));
assert_no_hardcoded_secrets(&yaml);
}

// ── Azure Pipelines end-to-end ────────────────────────────────────────────────

#[test]
fn azure_pipelines_output_is_valid_yaml() {
let yaml = templates::azure_pipelines::render(&minimal_pipeline(
CiPlatform::Azure,
CiFormat::AzurePipelines,
));
serde_yaml::from_str::<serde_yaml::Value>(&yaml)
.expect("Azure Pipelines output must be valid YAML");
}

#[test]
fn azure_pipelines_output_contains_required_fields() {
let yaml = templates::azure_pipelines::render(&minimal_pipeline(
CiPlatform::Azure,
CiFormat::AzurePipelines,
));
// Azure auto-checkouts; runtime setup and test step are required.
assert!(yaml.contains("npm test"), "Azure pipeline must contain the test command");
assert!(
yaml.contains("ubuntu") || yaml.contains("ubuntu-latest"),
"Azure pipeline must specify an agent VM image"
);
}

#[test]
fn azure_pipelines_output_has_no_hardcoded_secrets() {
let yaml = templates::azure_pipelines::render(&minimal_pipeline(
CiPlatform::Azure,
CiFormat::AzurePipelines,
));
assert_no_hardcoded_secrets(&yaml);
}

// ── Cloud Build end-to-end ────────────────────────────────────────────────────

#[test]
fn cloud_build_output_is_valid_yaml() {
let yaml = templates::cloud_build::render(&minimal_pipeline(
CiPlatform::Gcp,
CiFormat::CloudBuild,
));
serde_yaml::from_str::<serde_yaml::Value>(&yaml)
.expect("Cloud Build output must be valid YAML");
}

#[test]
fn cloud_build_output_contains_test_step() {
let yaml = templates::cloud_build::render(&minimal_pipeline(
CiPlatform::Gcp,
CiFormat::CloudBuild,
));
assert!(yaml.contains("npm test"), "Cloud Build pipeline must contain the test command");
}

#[test]
fn cloud_build_output_has_no_hardcoded_secrets() {
let yaml = templates::cloud_build::render(&minimal_pipeline(
CiPlatform::Gcp,
CiFormat::CloudBuild,
));
assert_no_hardcoded_secrets(&yaml);
}

// ── CiContext collection from language fixtures ───────────────────────────────

#[test]
fn collect_ci_context_succeeds_for_node_fixture() {
let ctx = collect_ci_context(&fixture("node"), CiPlatform::Hetzner, CiFormat::GithubActions)
.expect("should collect context from Node.js fixture");
assert_ne!(
ctx.primary_language.to_lowercase(),
"unknown",
"should detect a real language for Node.js fixture"
);
}

#[test]
fn collect_ci_context_succeeds_for_python_fixture() {
let ctx =
collect_ci_context(&fixture("python"), CiPlatform::Gcp, CiFormat::GithubActions)
.expect("should collect context from Python fixture");
assert_ne!(ctx.primary_language.to_lowercase(), "unknown");
}

#[test]
fn collect_ci_context_succeeds_for_rust_fixture() {
let ctx =
collect_ci_context(&fixture("rust"), CiPlatform::Hetzner, CiFormat::GithubActions)
.expect("should collect context from Rust fixture");
assert!(
ctx.primary_language.to_lowercase().contains("rust"),
"expected Rust primary language, got: {}",
ctx.primary_language
);
}

#[test]
fn collect_ci_context_succeeds_for_go_fixture() {
let ctx = collect_ci_context(&fixture("go"), CiPlatform::Gcp, CiFormat::GithubActions)
.expect("should collect context from Go fixture");
assert_ne!(ctx.primary_language.to_lowercase(), "unknown");
}

#[test]
fn collect_ci_context_succeeds_for_java_fixture() {
let ctx = collect_ci_context(&fixture("java"), CiPlatform::Azure, CiFormat::AzurePipelines)
.expect("should collect context from Java fixture");
assert_ne!(ctx.primary_language.to_lowercase(), "unknown");
}
Loading