Skip to main content

ferro_cli/templates/
ci_workflow.rs

1//! Renderer for `.github/workflows/ci.yml` (D-13..D-17, D-21).
2//!
3//! Loads the canonical GitHub Actions template via `include_str!` and
4//! returns it verbatim. The context struct exists to make future
5//! placeholder substitution (e.g. package name) trivial without an API
6//! break.
7
8pub const CI_WORKFLOW_TEMPLATE: &str = include_str!("files/ci/github-actions-ci.yml.tpl");
9
10/// Context for rendering the CI workflow file.
11///
12/// Currently a passthrough — the template has no placeholders. Kept as
13/// a struct so future fields (e.g. matrix toolchains, extra steps) can
14/// be added without changing the function signature.
15pub struct CiWorkflowContext<'a> {
16    pub package_name: &'a str,
17}
18
19/// Render `.github/workflows/ci.yml` from a context.
20///
21/// Pure function — no IO, deterministic output (D-17 idempotency).
22pub fn render_ci_workflow(_ctx: &CiWorkflowContext<'_>) -> String {
23    CI_WORKFLOW_TEMPLATE.to_string()
24}
25
26#[cfg(test)]
27mod tests {
28    use super::*;
29
30    fn render() -> String {
31        render_ci_workflow(&CiWorkflowContext {
32            package_name: "sample",
33        })
34    }
35
36    #[test]
37    fn contains_all_five_lint_gate_steps() {
38        let out = render();
39        assert!(out.contains("cargo fmt --all -- --check"));
40        assert!(out.contains("cargo clippy --all-targets -- -D warnings"));
41        assert!(out.contains("cargo test --all-features"));
42        assert!(out.contains("api:check"));
43        assert!(out.contains("validate:contracts"));
44    }
45
46    #[test]
47    fn triggers_on_pull_request_and_push_to_main() {
48        let out = render();
49        assert!(out.contains("pull_request:"));
50        assert!(out.contains("push:"));
51        assert!(out.contains("branches: [main]"));
52    }
53
54    #[test]
55    fn uses_swatinem_rust_cache_v2() {
56        let out = render();
57        assert!(out.contains("Swatinem/rust-cache@v2"));
58    }
59
60    #[test]
61    fn uses_dtolnay_rust_toolchain_stable() {
62        let out = render();
63        assert!(out.contains("dtolnay/rust-toolchain@stable"));
64    }
65
66    #[test]
67    fn structural_yaml_anchors_present() {
68        // No yaml parser is in the workspace; verify top-level keys and
69        // indentation anchors instead. (`serde_yaml` intentionally not added.)
70        let out = render();
71        assert!(out.contains("\nname: CI"));
72        assert!(out.contains("\non:"));
73        assert!(out.contains("\njobs:"));
74        assert!(out.contains("\n  lint-and-test:"));
75        assert!(out.contains("\n    runs-on: ubuntu-latest"));
76        assert!(out.contains("\n    steps:"));
77    }
78
79    #[test]
80    fn rendering_is_deterministic() {
81        let a = render();
82        let b = render();
83        assert_eq!(a, b);
84    }
85}