oag_fastapi_server/emitters/
tests.rs1use minijinja::{Environment, context};
2use oag_core::GeneratedFile;
3use oag_core::ir::{HttpMethod, IrOperation, IrParameterLocation, IrReturnType, IrSpec, IrType};
4
5pub fn emit_tests(ir: &IrSpec) -> Vec<GeneratedFile> {
7 vec![
8 GeneratedFile {
9 path: "conftest.py".to_string(),
10 content: include_str!("../../templates/conftest.py.j2").to_string(),
11 },
12 GeneratedFile {
13 path: "test_routes.py".to_string(),
14 content: emit_test_routes(ir),
15 },
16 ]
17}
18
19fn emit_test_routes(ir: &IrSpec) -> String {
20 let mut env = Environment::new();
21 env.add_template(
22 "test_routes.py.j2",
23 include_str!("../../templates/test_routes.py.j2"),
24 )
25 .expect("template should be valid");
26 let tmpl = env.get_template("test_routes.py.j2").unwrap();
27
28 let model_imports: Vec<String> = ir
30 .operations
31 .iter()
32 .filter_map(|op| {
33 op.request_body.as_ref().and_then(|b| match &b.body_type {
34 IrType::Ref(name) => Some(name.clone()),
35 _ => None,
36 })
37 })
38 .collect::<std::collections::BTreeSet<_>>()
39 .into_iter()
40 .collect();
41
42 let operations: Vec<minijinja::Value> = ir
43 .operations
44 .iter()
45 .flat_map(build_test_operation_contexts)
46 .collect();
47
48 tmpl.render(context! {
49 operations => operations,
50 model_imports => model_imports,
51 })
52 .expect("render should succeed")
53}
54
55fn build_test_operation_contexts(op: &IrOperation) -> Vec<minijinja::Value> {
56 let mut results = Vec::new();
57
58 let http_method = match op.method {
59 HttpMethod::Get => "get",
60 HttpMethod::Post => "post",
61 HttpMethod::Put => "put",
62 HttpMethod::Delete => "delete",
63 HttpMethod::Patch => "patch",
64 _ => "get",
65 };
66
67 let test_path = build_test_path(&op.path, op);
69 let has_body = op.request_body.is_some();
70 let mock_body = op
71 .request_body
72 .as_ref()
73 .map(|b| mock_value_python(&b.body_type))
74 .unwrap_or_else(|| "{}".to_string());
75
76 match &op.return_type {
77 IrReturnType::Standard(_) => {
78 results.push(context! {
79 kind => "standard",
80 name => op.name.snake_case.clone(),
81 http_method => http_method,
82 path => op.path.clone(),
83 test_path => test_path,
84 has_body => has_body,
85 mock_body => mock_body,
86 });
87 }
88 IrReturnType::Void => {
89 results.push(context! {
90 kind => "void",
91 name => op.name.snake_case.clone(),
92 http_method => http_method,
93 path => op.path.clone(),
94 test_path => test_path,
95 has_body => has_body,
96 mock_body => mock_body,
97 });
98 }
99 IrReturnType::Sse(sse) => {
100 results.push(context! {
101 kind => "sse",
102 name => op.name.snake_case.clone(),
103 http_method => http_method,
104 path => op.path.clone(),
105 test_path => test_path,
106 has_body => has_body,
107 mock_body => mock_body,
108 });
109
110 if sse.json_response.is_some() {
112 results.push(context! {
113 kind => "standard",
114 name => op.name.snake_case.clone(),
115 http_method => http_method,
116 path => op.path.clone(),
117 test_path => test_path,
118 has_body => has_body,
119 mock_body => mock_body,
120 });
121 }
122 }
123 }
124
125 results
126}
127
128fn build_test_path(path: &str, op: &IrOperation) -> String {
130 let mut result = path.to_string();
131 for param in &op.parameters {
132 if param.location == IrParameterLocation::Path {
133 let placeholder = format!("{{{}}}", param.original_name);
134 let test_value = mock_path_value(¶m.param_type);
135 result = result.replace(&placeholder, &test_value);
136 }
137 }
138 result
139}
140
141fn mock_path_value(ir_type: &IrType) -> String {
143 match ir_type {
144 IrType::Integer => "1".to_string(),
145 IrType::Number => "1".to_string(),
146 IrType::String | IrType::DateTime => "test".to_string(),
147 _ => "test".to_string(),
148 }
149}
150
151fn mock_value_python(ir_type: &IrType) -> String {
153 match ir_type {
154 IrType::String | IrType::DateTime => "\"test\"".to_string(),
155 IrType::StringLiteral(s) => format!("\"{s}\""),
156 IrType::Number | IrType::Integer => "1".to_string(),
157 IrType::Boolean => "True".to_string(),
158 IrType::Null | IrType::Void => "None".to_string(),
159 IrType::Array(_) => "[]".to_string(),
160 IrType::Ref(name) => format!("{}.model_construct()", name),
161 IrType::Object(_) | IrType::Map(_) | IrType::Any => "{}".to_string(),
162 IrType::Binary => "b\"test\"".to_string(),
163 IrType::Union(variants) | IrType::Intersection(variants) => {
164 if let Some(first) = variants.first() {
165 mock_value_python(first)
166 } else {
167 "{}".to_string()
168 }
169 }
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn test_mock_path_value() {
179 assert_eq!(mock_path_value(&IrType::Integer), "1");
180 assert_eq!(mock_path_value(&IrType::String), "test");
181 }
182
183 #[test]
184 fn test_mock_value_python() {
185 assert_eq!(mock_value_python(&IrType::String), "\"test\"");
186 assert_eq!(mock_value_python(&IrType::Integer), "1");
187 assert_eq!(mock_value_python(&IrType::Boolean), "True");
188 assert_eq!(
189 mock_value_python(&IrType::Array(Box::new(IrType::String))),
190 "[]"
191 );
192 assert_eq!(
193 mock_value_python(&IrType::Ref("Pet".to_string())),
194 "Pet.model_construct()"
195 );
196 }
197}