cli_testing_specialist/generator/
templates.rs

1use crate::error::{Error, Result};
2use std::collections::HashMap;
3
4/// Embedded templates using include_str! macro
5const BATS_TEST_TEMPLATE: &str = include_str!("../../templates/bats-test.template");
6const CONCURRENCY_TEST: &str = include_str!("../../templates/concurrency-test.fragment");
7const DESTRUCTIVE_OPS: &str = include_str!("../../templates/destructive-ops.fragment");
8const DIRECTORY_TRAVERSAL_LIMITS: &str =
9    include_str!("../../templates/directory-traversal-limits.fragment");
10const INPUT_VALIDATION: &str = include_str!("../../templates/input-validation.fragment");
11const PERFORMANCE_TEST: &str = include_str!("../../templates/performance-test.fragment");
12const SUBCOMMAND_HELP: &str = include_str!("../../templates/subcommand-help.fragment");
13
14/// Template engine for loading and processing BATS test templates
15pub struct TemplateEngine {
16    /// Cached templates (template_name -> template_content)
17    templates: HashMap<String, String>,
18}
19
20impl TemplateEngine {
21    /// Create a new template engine with embedded templates
22    pub fn new() -> Result<Self> {
23        Ok(Self {
24            templates: HashMap::new(),
25        })
26    }
27
28    /// Load all embedded templates
29    pub fn load_templates(&mut self) -> Result<()> {
30        log::info!("Loading embedded templates");
31
32        // Load all embedded templates
33        self.templates
34            .insert("bats-test".to_string(), BATS_TEST_TEMPLATE.to_string());
35        self.templates
36            .insert("concurrency-test".to_string(), CONCURRENCY_TEST.to_string());
37        self.templates
38            .insert("destructive-ops".to_string(), DESTRUCTIVE_OPS.to_string());
39        self.templates.insert(
40            "directory-traversal-limits".to_string(),
41            DIRECTORY_TRAVERSAL_LIMITS.to_string(),
42        );
43        self.templates
44            .insert("input-validation".to_string(), INPUT_VALIDATION.to_string());
45        self.templates
46            .insert("performance-test".to_string(), PERFORMANCE_TEST.to_string());
47        self.templates
48            .insert("subcommand-help".to_string(), SUBCOMMAND_HELP.to_string());
49
50        log::info!("Loaded {} templates", self.templates.len());
51
52        Ok(())
53    }
54
55    /// Get a template by name
56    pub fn get_template(&self, name: &str) -> Result<&str> {
57        self.templates
58            .get(name)
59            .map(|s| s.as_str())
60            .ok_or_else(|| Error::Config(format!("Template not found: {}", name)))
61    }
62
63    /// Substitute variables in a template
64    /// Variables are in the format ${VARIABLE_NAME}
65    pub fn substitute(&self, template: &str, variables: &HashMap<String, String>) -> String {
66        let mut result = template.to_string();
67
68        for (key, value) in variables {
69            let placeholder = format!("${{{}}}", key);
70            result = result.replace(&placeholder, value);
71        }
72
73        result
74    }
75
76    /// Get template by name and substitute variables
77    pub fn render(
78        &self,
79        template_name: &str,
80        variables: &HashMap<String, String>,
81    ) -> Result<String> {
82        let template = self.get_template(template_name)?;
83        Ok(self.substitute(template, variables))
84    }
85
86    /// List all available template names
87    pub fn available_templates(&self) -> Vec<String> {
88        self.templates.keys().cloned().collect()
89    }
90
91    /// Validate that required variables are present in the template
92    pub fn validate_template(&self, template_name: &str, required_vars: &[&str]) -> Result<()> {
93        let template = self.get_template(template_name)?;
94
95        for var in required_vars {
96            let placeholder = format!("${{{}}}", var);
97            if !template.contains(&placeholder) {
98                log::warn!(
99                    "Template '{}' does not contain expected variable: {}",
100                    template_name,
101                    var
102                );
103            }
104        }
105
106        Ok(())
107    }
108}
109
110impl Default for TemplateEngine {
111    fn default() -> Self {
112        Self::new().expect("Failed to create default TemplateEngine")
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn test_template_engine_creation() {
122        let engine = TemplateEngine::new();
123        assert!(engine.is_ok());
124    }
125
126    #[test]
127    fn test_load_templates() {
128        let mut engine = TemplateEngine::new().unwrap();
129        let result = engine.load_templates();
130
131        assert!(result.is_ok());
132        assert_eq!(engine.templates.len(), 7); // 7 embedded templates
133        assert!(engine.templates.contains_key("bats-test"));
134        assert!(engine.templates.contains_key("concurrency-test"));
135    }
136
137    #[test]
138    fn test_get_template() {
139        let mut engine = TemplateEngine::new().unwrap();
140        engine.load_templates().unwrap();
141
142        let template = engine.get_template("bats-test");
143        assert!(template.is_ok());
144        assert!(!template.unwrap().is_empty());
145    }
146
147    #[test]
148    fn test_get_nonexistent_template() {
149        let mut engine = TemplateEngine::new().unwrap();
150        engine.load_templates().unwrap();
151
152        let result = engine.get_template("nonexistent");
153        assert!(result.is_err());
154    }
155
156    #[test]
157    fn test_substitute() {
158        let engine = TemplateEngine::new().unwrap();
159
160        let template = "Hello ${NAME}, you are ${AGE} years old.";
161        let mut vars = HashMap::new();
162        vars.insert("NAME".to_string(), "Alice".to_string());
163        vars.insert("AGE".to_string(), "30".to_string());
164
165        let result = engine.substitute(template, &vars);
166        assert_eq!(result, "Hello Alice, you are 30 years old.");
167    }
168
169    #[test]
170    fn test_substitute_missing_variable() {
171        let engine = TemplateEngine::new().unwrap();
172
173        let template = "Hello ${NAME}, you are ${AGE} years old.";
174        let mut vars = HashMap::new();
175        vars.insert("NAME".to_string(), "Alice".to_string());
176
177        let result = engine.substitute(template, &vars);
178        // Missing variables are left as-is
179        assert_eq!(result, "Hello Alice, you are ${AGE} years old.");
180    }
181
182    #[test]
183    fn test_available_templates() {
184        let mut engine = TemplateEngine::new().unwrap();
185        engine.load_templates().unwrap();
186
187        let templates = engine.available_templates();
188        assert_eq!(templates.len(), 7);
189        assert!(templates.contains(&"bats-test".to_string()));
190        assert!(templates.contains(&"performance-test".to_string()));
191    }
192}