Skip to main content

alef_e2e/
scaffold.rs

1//! Fixture scaffolding for `alef e2e init` and `alef e2e scaffold`.
2
3use crate::config::E2eConfig;
4use alef_core::config::ResolvedCrateConfig;
5use anyhow::{Context, Result};
6use std::path::Path;
7
8static FIXTURE_SCHEMA: &str = include_str!("../schema/fixture.schema.json");
9
10/// Create the fixtures directory structure and write the schema file.
11/// Called by `alef e2e init`.
12pub fn init_fixtures(e2e_config: &E2eConfig, _config: &ResolvedCrateConfig) -> Result<Vec<String>> {
13    let fixtures_dir = Path::new(&e2e_config.fixtures);
14    let mut created = Vec::new();
15
16    // 1. Create fixtures directory
17    if !fixtures_dir.exists() {
18        std::fs::create_dir_all(fixtures_dir)
19            .with_context(|| format!("failed to create fixtures dir: {}", fixtures_dir.display()))?;
20        created.push(fixtures_dir.display().to_string());
21    }
22
23    // 2. Write schema.json
24    let schema_path = fixtures_dir.join("schema.json");
25    std::fs::write(&schema_path, FIXTURE_SCHEMA)
26        .with_context(|| format!("failed to write {}", schema_path.display()))?;
27    created.push(schema_path.display().to_string());
28
29    // 3. Create smoke directory
30    let smoke_dir = fixtures_dir.join("smoke");
31    if !smoke_dir.exists() {
32        std::fs::create_dir_all(&smoke_dir)
33            .with_context(|| format!("failed to create smoke dir: {}", smoke_dir.display()))?;
34        created.push(smoke_dir.display().to_string());
35    }
36
37    // 4. Write smoke/basic.json example fixture
38    let basic_path = smoke_dir.join("basic.json");
39    let basic_fixture = build_example_fixture(e2e_config);
40    std::fs::write(&basic_path, basic_fixture).with_context(|| format!("failed to write {}", basic_path.display()))?;
41    created.push(basic_path.display().to_string());
42
43    Ok(created)
44}
45
46/// Create a new fixture file from a template.
47/// Called by `alef e2e scaffold --id <id> --category <cat> --description <desc>`.
48pub fn scaffold_fixture(
49    e2e_config: &E2eConfig,
50    _config: &ResolvedCrateConfig,
51    id: &str,
52    category: &str,
53    description: &str,
54) -> Result<String> {
55    let fixtures_dir = Path::new(&e2e_config.fixtures);
56    let category_dir = fixtures_dir.join(category);
57
58    // 1. Create category directory
59    if !category_dir.exists() {
60        std::fs::create_dir_all(&category_dir)
61            .with_context(|| format!("failed to create category dir: {}", category_dir.display()))?;
62    }
63
64    // 2. Write fixture file
65    let fixture_path = category_dir.join(format!("{id}.json"));
66    let fixture = build_scaffold_fixture(e2e_config, id, description);
67    std::fs::write(&fixture_path, fixture).with_context(|| format!("failed to write {}", fixture_path.display()))?;
68
69    Ok(fixture_path.display().to_string())
70}
71
72/// Build the example fixture JSON for `init`.
73fn build_example_fixture(e2e_config: &E2eConfig) -> String {
74    let mut input_fields = Vec::new();
75    for arg in &e2e_config.call.args {
76        let value = example_value_for_type(&arg.arg_type);
77        input_fields.push(format!("    \"{}\": {value}", arg.field));
78    }
79
80    let input_block = if input_fields.is_empty() {
81        "{}".to_string()
82    } else {
83        format!("{{\n{}\n  }}", input_fields.join(",\n"))
84    };
85
86    // Use the first arg's field for the not_empty assertion if available
87    let first_field = e2e_config
88        .call
89        .args
90        .first()
91        .map(|a| a.field.as_str())
92        .unwrap_or("result");
93
94    format!(
95        r#"{{
96  "id": "basic_smoke",
97  "description": "Basic smoke test verifying the function returns without error",
98  "input": {input_block},
99  "assertions": [
100    {{ "type": "not_error" }},
101    {{ "type": "not_empty", "field": "{first_field}" }}
102  ]
103}}
104"#
105    )
106}
107
108/// Build a scaffold fixture JSON for `scaffold`.
109fn build_scaffold_fixture(e2e_config: &E2eConfig, id: &str, description: &str) -> String {
110    let mut input_fields = Vec::new();
111    for arg in &e2e_config.call.args {
112        let value = empty_value_for_type(&arg.arg_type);
113        input_fields.push(format!("    \"{}\": {value}", arg.field));
114    }
115
116    let input_block = if input_fields.is_empty() {
117        "{}".to_string()
118    } else {
119        format!("{{\n{}\n  }}", input_fields.join(",\n"))
120    };
121
122    format!(
123        r#"{{
124  "id": "{id}",
125  "description": "{description}",
126  "input": {input_block},
127  "assertions": [
128    {{ "type": "not_error" }}
129  ]
130}}
131"#
132    )
133}
134
135/// Return an example value for a given arg type (for init).
136fn example_value_for_type(arg_type: &str) -> &'static str {
137    match arg_type {
138        "string" => "\"example\"",
139        "int" | "integer" => "0",
140        "float" | "number" => "0.0",
141        "bool" | "boolean" => "true",
142        "json_object" => "{}",
143        "bytes" => "\"\"",
144        _ => "\"\"",
145    }
146}
147
148/// Return an empty/default value for a given arg type (for scaffold).
149fn empty_value_for_type(arg_type: &str) -> &'static str {
150    match arg_type {
151        "string" => "\"\"",
152        "int" | "integer" => "0",
153        "float" | "number" => "0.0",
154        "bool" | "boolean" => "false",
155        "json_object" => "{}",
156        "bytes" => "\"\"",
157        _ => "\"\"",
158    }
159}