1pub mod fixtures;
6
7use std::collections::BTreeMap;
8
9use crate::ir::{
10 AuthInfo, CrudInfo, IacAttribute, IacDataSource, IacProvider, IacResource, IacType,
11 IdentityInfo,
12};
13
14#[must_use]
16pub fn test_provider(name: &str) -> IacProvider {
17 IacProvider {
18 name: name.to_string(),
19 description: format!("{name} test provider"),
20 version: "1.0.0".to_string(),
21 auth: AuthInfo {
22 token_field: "token".to_string(),
23 env_var: format!("{}_TOKEN", name.to_uppercase()),
24 gateway_url_field: "api_url".to_string(),
25 gateway_env_var: format!("{}_API_URL", name.to_uppercase()),
26 },
27 skip_fields: vec!["token".to_string()],
28 platform_config: BTreeMap::new(),
29 }
30}
31
32#[must_use]
39pub fn test_resource(name: &str) -> IacResource {
40 IacResource {
41 name: name.to_string(),
42 description: format!("{name} test resource"),
43 category: "test".to_string(),
44 crud: CrudInfo {
45 create_endpoint: format!("/create-{name}"),
46 create_schema: format!("Create{name}"),
47 update_endpoint: None,
48 update_schema: None,
49 read_endpoint: format!("/get-{name}"),
50 read_schema: format!("Get{name}"),
51 read_response_schema: None,
52 delete_endpoint: format!("/delete-{name}"),
53 delete_schema: format!("Delete{name}"),
54 },
55 attributes: vec![
56 TestAttributeBuilder::new("name", IacType::String)
57 .required()
58 .immutable()
59 .read_path("item_name")
60 .description("The resource name")
61 .build(),
62 TestAttributeBuilder::new("value", IacType::String)
63 .required()
64 .sensitive()
65 .description("The resource value")
66 .build(),
67 TestAttributeBuilder::new("tags", IacType::List(Box::new(IacType::String)))
68 .read_path("item_tags")
69 .description("Resource tags")
70 .build(),
71 ],
72 identity: IdentityInfo {
73 id_field: "name".to_string(),
74 import_field: "name".to_string(),
75 force_replace_fields: vec!["name".to_string()],
76 },
77 read_mapping: BTreeMap::new(),
78 }
79}
80
81#[must_use]
83pub fn test_resource_with_type(name: &str, attr_name: &str, iac_type: IacType) -> IacResource {
84 IacResource {
85 name: name.to_string(),
86 description: format!("{name} test resource"),
87 category: "test".to_string(),
88 crud: CrudInfo {
89 create_endpoint: format!("/create-{name}"),
90 create_schema: format!("Create{name}"),
91 update_endpoint: None,
92 update_schema: None,
93 read_endpoint: format!("/get-{name}"),
94 read_schema: format!("Get{name}"),
95 read_response_schema: None,
96 delete_endpoint: format!("/delete-{name}"),
97 delete_schema: format!("Delete{name}"),
98 },
99 attributes: vec![
100 TestAttributeBuilder::new(attr_name, iac_type)
101 .required()
102 .build(),
103 ],
104 identity: IdentityInfo {
105 id_field: attr_name.to_string(),
106 import_field: attr_name.to_string(),
107 force_replace_fields: vec![],
108 },
109 read_mapping: BTreeMap::new(),
110 }
111}
112
113#[must_use]
119pub fn test_data_source(name: &str) -> IacDataSource {
120 IacDataSource {
121 name: name.to_string(),
122 description: format!("{name} test data source"),
123 read_endpoint: format!("/get-{name}"),
124 read_schema: format!("Get{name}"),
125 read_response_schema: None,
126 attributes: vec![
127 TestAttributeBuilder::new("name", IacType::String)
128 .required()
129 .description("The data source name")
130 .build(),
131 TestAttributeBuilder::new("value", IacType::String)
132 .computed()
133 .description("The data source value")
134 .build(),
135 ],
136 read_mapping: BTreeMap::new(),
137 }
138}
139
140#[allow(clippy::struct_excessive_bools)]
142pub struct TestAttributeBuilder {
143 api_name: String,
144 canonical_name: String,
145 description: String,
146 iac_type: IacType,
147 required: bool,
148 optional: bool,
149 computed: bool,
150 sensitive: bool,
151 json_encoded: bool,
152 immutable: bool,
153 default_value: Option<serde_json::Value>,
154 enum_values: Option<Vec<String>>,
155 read_path: Option<String>,
156 update_only: bool,
157}
158
159impl TestAttributeBuilder {
160 #[must_use]
162 pub fn new(name: &str, iac_type: IacType) -> Self {
163 Self {
164 api_name: name.to_string(),
165 canonical_name: name.replace('-', "_"),
166 description: String::new(),
167 iac_type,
168 required: false,
169 optional: false,
170 computed: false,
171 sensitive: false,
172 json_encoded: false,
173 immutable: false,
174 default_value: None,
175 enum_values: None,
176 read_path: None,
177 update_only: false,
178 }
179 }
180
181 #[must_use]
183 pub fn required(mut self) -> Self {
184 self.required = true;
185 self
186 }
187
188 #[must_use]
190 pub fn optional(mut self) -> Self {
191 self.optional = true;
192 self
193 }
194
195 #[must_use]
197 pub fn computed(mut self) -> Self {
198 self.computed = true;
199 self
200 }
201
202 #[must_use]
204 pub fn sensitive(mut self) -> Self {
205 self.sensitive = true;
206 self
207 }
208
209 #[must_use]
211 pub fn json_encoded(mut self) -> Self {
212 self.json_encoded = true;
213 self
214 }
215
216 #[must_use]
218 pub fn immutable(mut self) -> Self {
219 self.immutable = true;
220 self
221 }
222
223 #[must_use]
225 pub fn read_path(mut self, path: &str) -> Self {
226 self.read_path = Some(path.to_string());
227 self
228 }
229
230 #[must_use]
232 pub fn description(mut self, desc: &str) -> Self {
233 self.description = desc.to_string();
234 self
235 }
236
237 #[must_use]
239 pub fn default_value(mut self, val: serde_json::Value) -> Self {
240 self.default_value = Some(val);
241 self
242 }
243
244 #[must_use]
246 pub fn enum_values(mut self, values: Vec<String>) -> Self {
247 self.enum_values = Some(values);
248 self
249 }
250
251 #[must_use]
253 pub fn update_only(mut self) -> Self {
254 self.update_only = true;
255 self
256 }
257
258 #[must_use]
260 pub fn build(self) -> IacAttribute {
261 IacAttribute {
262 api_name: self.api_name,
263 canonical_name: self.canonical_name,
264 description: self.description,
265 iac_type: self.iac_type,
266 required: self.required,
267 optional: self.optional,
268 computed: self.computed,
269 sensitive: self.sensitive,
270 json_encoded: self.json_encoded,
271 immutable: self.immutable,
272 default_value: self.default_value,
273 enum_values: self.enum_values,
274 read_path: self.read_path,
275 update_only: self.update_only,
276 }
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283 use crate::ir::HasAttributes;
284
285 #[test]
286 fn test_provider_creates_valid_provider() {
287 let p = test_provider("acme");
288 assert_eq!(p.name, "acme");
289 assert_eq!(p.description, "acme test provider");
290 assert_eq!(p.version, "1.0.0");
291 assert_eq!(p.auth.token_field, "token");
292 assert_eq!(p.auth.env_var, "ACME_TOKEN");
293 assert_eq!(p.auth.gateway_url_field, "api_url");
294 assert_eq!(p.auth.gateway_env_var, "ACME_API_URL");
295 assert_eq!(p.skip_fields, vec!["token"]);
296 assert!(p.platform_config.is_empty());
297 }
298
299 #[test]
300 fn test_resource_creates_three_attributes() {
301 let r = test_resource("secret");
302 assert_eq!(r.name, "secret");
303 assert_eq!(r.attributes.len(), 3);
304
305 let name_attr = &r.attributes[0];
306 assert_eq!(name_attr.canonical_name, "name");
307 assert!(name_attr.required);
308 assert!(name_attr.immutable);
309 assert_eq!(name_attr.read_path, Some("item_name".to_string()));
310
311 let value_attr = &r.attributes[1];
312 assert_eq!(value_attr.canonical_name, "value");
313 assert!(value_attr.required);
314 assert!(value_attr.sensitive);
315
316 let tags_attr = &r.attributes[2];
317 assert_eq!(tags_attr.canonical_name, "tags");
318 assert!(!tags_attr.required);
319 assert_eq!(tags_attr.read_path, Some("item_tags".to_string()));
320 assert_eq!(tags_attr.iac_type, IacType::List(Box::new(IacType::String)));
321 }
322
323 #[test]
324 fn test_resource_with_type_single_attribute() {
325 let r = test_resource_with_type("flag", "enabled", IacType::Boolean);
326 assert_eq!(r.attributes.len(), 1);
327 assert_eq!(r.attributes[0].canonical_name, "enabled");
328 assert_eq!(r.attributes[0].iac_type, IacType::Boolean);
329 assert!(r.attributes[0].required);
330 }
331
332 #[test]
333 fn test_data_source_creates_two_attributes() {
334 let ds = test_data_source("config");
335 assert_eq!(ds.name, "config");
336 assert_eq!(ds.attributes.len(), 2);
337
338 let name_attr = &ds.attributes[0];
339 assert_eq!(name_attr.canonical_name, "name");
340 assert!(name_attr.required);
341
342 let value_attr = &ds.attributes[1];
343 assert_eq!(value_attr.canonical_name, "value");
344 assert!(value_attr.computed);
345 }
346
347 #[test]
348 fn test_attribute_builder_defaults() {
349 let attr = TestAttributeBuilder::new("field", IacType::String).build();
350 assert_eq!(attr.api_name, "field");
351 assert_eq!(attr.canonical_name, "field");
352 assert_eq!(attr.iac_type, IacType::String);
353 assert!(!attr.required);
354 assert!(!attr.optional);
355 assert!(!attr.computed);
356 assert!(!attr.sensitive);
357 assert!(!attr.json_encoded);
358 assert!(!attr.immutable);
359 assert!(!attr.update_only);
360 assert!(attr.description.is_empty());
361 assert!(attr.default_value.is_none());
362 assert!(attr.enum_values.is_none());
363 assert!(attr.read_path.is_none());
364 }
365
366 #[test]
367 fn test_attribute_builder_all_flags() {
368 let attr = TestAttributeBuilder::new("secret-key", IacType::String)
369 .required()
370 .optional()
371 .computed()
372 .sensitive()
373 .json_encoded()
374 .immutable()
375 .update_only()
376 .read_path("secret_key_resp")
377 .description("A secret key")
378 .default_value(serde_json::json!("default"))
379 .enum_values(vec!["a".into(), "b".into()])
380 .build();
381 assert_eq!(attr.api_name, "secret-key");
382 assert_eq!(attr.canonical_name, "secret_key");
383 assert!(attr.required);
384 assert!(attr.optional);
385 assert!(attr.computed);
386 assert!(attr.sensitive);
387 assert!(attr.json_encoded);
388 assert!(attr.immutable);
389 assert!(attr.update_only);
390 assert_eq!(attr.read_path, Some("secret_key_resp".to_string()));
391 assert_eq!(attr.description, "A secret key");
392 assert_eq!(attr.default_value, Some(serde_json::json!("default")));
393 assert_eq!(
394 attr.enum_values,
395 Some(vec!["a".to_string(), "b".to_string()])
396 );
397 }
398
399 #[test]
400 fn test_resource_with_type_integer() {
401 let r = test_resource_with_type("counter", "count", IacType::Integer);
402 assert_eq!(r.attributes.len(), 1);
403 assert_eq!(r.attributes[0].iac_type, IacType::Integer);
404 assert!(r.attributes[0].required);
405 }
406
407 #[test]
408 fn test_resource_with_type_float() {
409 let r = test_resource_with_type("metric", "value", IacType::Float);
410 assert_eq!(r.attributes[0].iac_type, IacType::Float);
411 }
412
413 #[test]
414 fn test_resource_with_type_list() {
415 let r = test_resource_with_type("tagged", "tags", IacType::List(Box::new(IacType::String)));
416 assert_eq!(
417 r.attributes[0].iac_type,
418 IacType::List(Box::new(IacType::String))
419 );
420 }
421
422 #[test]
423 fn test_resource_with_type_map() {
424 let r = test_resource_with_type(
425 "config",
426 "settings",
427 IacType::Map(Box::new(IacType::String)),
428 );
429 assert_eq!(
430 r.attributes[0].iac_type,
431 IacType::Map(Box::new(IacType::String))
432 );
433 }
434
435 #[test]
436 fn test_resource_with_type_set() {
437 let r =
438 test_resource_with_type("unique", "items", IacType::Set(Box::new(IacType::Integer)));
439 assert_eq!(
440 r.attributes[0].iac_type,
441 IacType::Set(Box::new(IacType::Integer))
442 );
443 }
444
445 #[test]
446 fn test_resource_with_type_object() {
447 let r = test_resource_with_type(
448 "complex",
449 "config",
450 IacType::Object {
451 name: "Config".to_string(),
452 fields: vec![],
453 },
454 );
455 assert_eq!(
456 r.attributes[0].iac_type,
457 IacType::Object {
458 name: "Config".to_string(),
459 fields: vec![]
460 }
461 );
462 }
463
464 #[test]
465 fn test_resource_with_type_enum() {
466 let r = test_resource_with_type(
467 "status",
468 "state",
469 IacType::Enum {
470 values: vec!["active".to_string(), "inactive".to_string()],
471 underlying: Box::new(IacType::String),
472 },
473 );
474 assert_eq!(
475 r.attributes[0].iac_type,
476 IacType::Enum {
477 values: vec!["active".to_string(), "inactive".to_string()],
478 underlying: Box::new(IacType::String)
479 }
480 );
481 }
482
483 #[test]
484 fn test_resource_with_type_any() {
485 let r = test_resource_with_type("dynamic", "data", IacType::Any);
486 assert_eq!(r.attributes[0].iac_type, IacType::Any);
487 }
488
489 #[test]
490 fn test_attribute_builder_hyphenated_name() {
491 let attr = TestAttributeBuilder::new("my-field-name", IacType::String).build();
492 assert_eq!(attr.api_name, "my-field-name");
493 assert_eq!(attr.canonical_name, "my_field_name");
494 }
495
496 #[test]
497 fn test_attribute_builder_already_snake_case() {
498 let attr = TestAttributeBuilder::new("already_snake", IacType::String).build();
499 assert_eq!(attr.api_name, "already_snake");
500 assert_eq!(attr.canonical_name, "already_snake");
501 }
502
503 #[test]
504 fn test_resource_crud_endpoints() {
505 let r = test_resource("widget");
506 assert_eq!(r.crud.create_endpoint, "/create-widget");
507 assert_eq!(r.crud.read_endpoint, "/get-widget");
508 assert_eq!(r.crud.delete_endpoint, "/delete-widget");
509 assert!(r.crud.update_endpoint.is_none());
510 assert!(r.crud.update_schema.is_none());
511 }
512
513 #[test]
514 fn test_data_source_read_info() {
515 let ds = test_data_source("info");
516 assert_eq!(ds.read_endpoint, "/get-info");
517 assert_eq!(ds.read_schema, "Getinfo");
518 assert!(ds.read_response_schema.is_none());
519 }
520
521 #[test]
522 fn test_resource_with_type_identity() {
523 let r = test_resource_with_type("item", "my_id", IacType::String);
524 assert_eq!(r.identity.id_field, "my_id");
525 assert_eq!(r.identity.import_field, "my_id");
526 assert!(r.identity.force_replace_fields.is_empty());
527 }
528
529 #[test]
530 fn test_attribute_builder_multiple_hyphens() {
531 let attr = TestAttributeBuilder::new("a-b-c-d", IacType::String).build();
532 assert_eq!(attr.api_name, "a-b-c-d");
533 assert_eq!(attr.canonical_name, "a_b_c_d");
534 }
535
536 #[test]
537 fn test_attribute_builder_json_encoded_flag() {
538 let attr = TestAttributeBuilder::new("policy", IacType::String)
539 .json_encoded()
540 .build();
541 assert!(attr.json_encoded);
542 }
543
544 #[test]
545 fn test_attribute_builder_default_value_types() {
546 let bool_attr = TestAttributeBuilder::new("flag", IacType::Boolean)
547 .default_value(serde_json::json!(true))
548 .build();
549 assert_eq!(bool_attr.default_value, Some(serde_json::json!(true)));
550
551 let num_attr = TestAttributeBuilder::new("count", IacType::Integer)
552 .default_value(serde_json::json!(42))
553 .build();
554 assert_eq!(num_attr.default_value, Some(serde_json::json!(42)));
555
556 let null_attr = TestAttributeBuilder::new("maybe", IacType::String)
557 .default_value(serde_json::json!(null))
558 .build();
559 assert_eq!(null_attr.default_value, Some(serde_json::json!(null)));
560 }
561
562 #[test]
563 fn test_attribute_builder_enum_values_empty() {
564 let attr = TestAttributeBuilder::new("mode", IacType::String)
565 .enum_values(vec![])
566 .build();
567 assert_eq!(attr.enum_values, Some(vec![]));
568 }
569
570 #[test]
571 fn test_resource_identity() {
572 let r = test_resource("secret");
573 assert_eq!(r.identity.id_field, "name");
574 assert_eq!(r.identity.import_field, "name");
575 assert_eq!(r.identity.force_replace_fields, vec!["name"]);
576 }
577
578 #[test]
579 fn test_resource_description() {
580 let r = test_resource("widget");
581 assert_eq!(r.description, "widget test resource");
582 assert_eq!(r.category, "test");
583 }
584
585 #[test]
586 fn test_data_source_description() {
587 let ds = test_data_source("lookup");
588 assert_eq!(ds.description, "lookup test data source");
589 }
590
591 #[test]
592 fn test_resource_with_type_numeric() {
593 let r = test_resource_with_type("measure", "amount", IacType::Numeric);
594 assert_eq!(r.attributes[0].iac_type, IacType::Numeric);
595 }
596
597 #[test]
598 fn test_provider_auth_has_helpers() {
599 let p = test_provider("demo");
600 assert!(p.auth.has_token());
601 assert!(p.auth.has_gateway());
602 }
603
604 #[test]
605 fn test_resource_filter_helpers_via_fixture() {
606 let r = test_resource("secret");
607 let required = r.required_attribute_names();
608 assert_eq!(required, vec!["name", "value"]);
609
610 let sensitive = r.sensitive_attribute_names();
611 assert_eq!(sensitive, vec!["value"]);
612
613 let immutable = r.immutable_attribute_names();
614 assert_eq!(immutable, vec!["name"]);
615 }
616
617 #[test]
618 fn test_data_source_filter_helpers_via_fixture() {
619 let ds = test_data_source("config");
620 let required = ds.required_attribute_names();
621 assert_eq!(required, vec!["name"]);
622
623 let computed = ds.computed_attribute_names();
624 assert_eq!(computed, vec!["value"]);
625
626 let sensitive = ds.sensitive_attribute_names();
627 assert!(sensitive.is_empty());
628 }
629}