oag_node_client/emitters/
tests.rs1use minijinja::{Environment, context};
2use oag_core::ir::{IrOperation, IrParameterLocation, IrReturnType, IrSpec, IrType};
3
4use crate::type_mapper::ir_type_to_ts;
5
6pub fn emit_client_tests(ir: &IrSpec) -> String {
8 let mut env = Environment::new();
9 env.add_template(
10 "client.test.ts.j2",
11 include_str!("../../templates/client.test.ts.j2"),
12 )
13 .expect("template should be valid");
14 let tmpl = env.get_template("client.test.ts.j2").unwrap();
15
16 let type_imports: Vec<String> = collect_type_imports(ir);
18
19 let operations: Vec<minijinja::Value> = ir
20 .operations
21 .iter()
22 .flat_map(build_test_operation_contexts)
23 .collect();
24
25 tmpl.render(context! {
26 operations => operations,
27 type_imports => type_imports,
28 })
29 .expect("render should succeed")
30}
31
32fn collect_type_imports(ir: &IrSpec) -> Vec<String> {
34 let mut names = std::collections::BTreeSet::new();
35
36 for op in &ir.operations {
37 if let Some(ref body) = op.request_body {
39 collect_ref_names(&body.body_type, &mut names);
40 }
41 match &op.return_type {
43 IrReturnType::Standard(resp) => {
44 collect_ref_names(&resp.response_type, &mut names);
45 }
46 IrReturnType::Sse(sse) => {
47 collect_ref_names(&sse.event_type, &mut names);
48 if let Some(ref json_resp) = sse.json_response {
49 collect_ref_names(&json_resp.response_type, &mut names);
50 }
51 }
52 IrReturnType::Void => {}
53 }
54 }
55
56 names.into_iter().collect()
57}
58
59fn collect_ref_names(ir_type: &IrType, names: &mut std::collections::BTreeSet<String>) {
60 match ir_type {
61 IrType::Ref(name) => {
62 names.insert(name.clone());
63 }
64 IrType::Array(inner) => collect_ref_names(inner, names),
65 IrType::Union(variants) => {
66 for v in variants {
67 collect_ref_names(v, names);
68 }
69 }
70 _ => {}
71 }
72}
73
74fn build_test_operation_contexts(op: &IrOperation) -> Vec<minijinja::Value> {
75 let mut results = Vec::new();
76
77 match &op.return_type {
78 IrReturnType::Standard(resp) => {
79 let return_type = ir_type_to_ts(&resp.response_type);
80 results.push(build_test_context(
81 op,
82 "standard",
83 &op.name.camel_case,
84 &return_type,
85 ));
86 }
87 IrReturnType::Void => {
88 results.push(build_test_context(op, "void", &op.name.camel_case, "void"));
89 }
90 IrReturnType::Sse(sse) => {
91 let sse_name = if sse.also_has_json {
92 format!("{}Stream", op.name.camel_case)
93 } else {
94 op.name.camel_case.clone()
95 };
96 let return_type = if let Some(ref name) = sse.event_type_name {
97 name.clone()
98 } else {
99 ir_type_to_ts(&sse.event_type)
100 };
101 results.push(build_test_context(op, "sse", &sse_name, &return_type));
102
103 if let Some(ref json_resp) = sse.json_response {
104 let rt = ir_type_to_ts(&json_resp.response_type);
105 results.push(build_test_context(op, "standard", &op.name.camel_case, &rt));
106 }
107 }
108 }
109
110 results
111}
112
113fn build_test_context(
114 op: &IrOperation,
115 kind: &str,
116 method_name: &str,
117 return_type: &str,
118) -> minijinja::Value {
119 let has_body = op.request_body.is_some();
120 let test_call_args = build_test_call_args(op);
121 let expected_url_pattern = build_expected_url_pattern(op);
122 let mock_response = mock_value_ts(&if return_type == "void" {
123 IrType::Void
124 } else {
125 guess_mock_type(return_type)
127 });
128
129 context! {
130 kind => kind,
131 method_name => method_name,
132 http_method => op.method.as_str(),
133 return_type => return_type,
134 has_body => has_body,
135 test_call_args => test_call_args,
136 expected_url_pattern => expected_url_pattern,
137 mock_response => mock_response,
138 }
139}
140
141fn build_test_call_args(op: &IrOperation) -> String {
143 let mut args = Vec::new();
144
145 for param in &op.parameters {
146 if param.location == IrParameterLocation::Path {
147 args.push(mock_value_ts(¶m.param_type));
148 }
149 }
150
151 for param in &op.parameters {
152 if param.location == IrParameterLocation::Query && param.required {
153 args.push(mock_value_ts(¶m.param_type));
154 }
155 }
156
157 if let Some(ref body) = op.request_body {
158 args.push(mock_value_ts(&body.body_type));
159 }
160
161 args.join(", ")
162}
163
164fn build_expected_url_pattern(op: &IrOperation) -> String {
166 let mut path = op.path.clone();
167 for param in &op.parameters {
168 if param.location == IrParameterLocation::Path {
169 let placeholder = format!("{{{}}}", param.original_name);
170 path = path.replace(&placeholder, &mock_path_value_ts(¶m.param_type));
171 }
172 }
173 path
174}
175
176fn mock_value_ts(ir_type: &IrType) -> String {
178 match ir_type {
179 IrType::String | IrType::DateTime => "\"test\"".to_string(),
180 IrType::Number | IrType::Integer => "1".to_string(),
181 IrType::Boolean => "true".to_string(),
182 IrType::Null | IrType::Void => "undefined".to_string(),
183 IrType::Array(_) => "[]".to_string(),
184 IrType::Object(_) | IrType::Map(_) | IrType::Any => "{}".to_string(),
185 IrType::Ref(name) => format!("{{}} as {}", name),
186 IrType::Binary => "new Blob()".to_string(),
187 IrType::Union(variants) => {
188 if let Some(first) = variants.first() {
189 mock_value_ts(first)
190 } else {
191 "{}".to_string()
192 }
193 }
194 }
195}
196
197fn mock_path_value_ts(ir_type: &IrType) -> String {
199 match ir_type {
200 IrType::Integer | IrType::Number => "1".to_string(),
201 _ => "test".to_string(),
202 }
203}
204
205fn guess_mock_type(return_type: &str) -> IrType {
207 match return_type {
208 "string" => IrType::String,
209 "number" => IrType::Number,
210 "boolean" => IrType::Boolean,
211 "void" => IrType::Void,
212 t if t.ends_with("[]") => IrType::Array(Box::new(IrType::Any)),
213 _ => IrType::Ref(return_type.to_string()),
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220
221 #[test]
222 fn test_mock_value_ts() {
223 assert_eq!(mock_value_ts(&IrType::String), "\"test\"");
224 assert_eq!(mock_value_ts(&IrType::Integer), "1");
225 assert_eq!(mock_value_ts(&IrType::Boolean), "true");
226 assert_eq!(mock_value_ts(&IrType::Void), "undefined");
227 assert_eq!(mock_value_ts(&IrType::Ref("Pet".to_string())), "{} as Pet");
228 }
229
230 #[test]
231 fn test_mock_path_value() {
232 assert_eq!(mock_path_value_ts(&IrType::Integer), "1");
233 assert_eq!(mock_path_value_ts(&IrType::String), "test");
234 }
235}