1use serde::{Deserialize, Serialize};
2
3use crate::error::IacForgeError;
4use crate::ir::{IacAction, IacDataSource, IacProvider, IacResource};
5
6#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
8#[non_exhaustive]
9pub enum ArtifactKind {
10 #[default]
11 Resource,
12 DataSource,
13 Provider,
14 Test,
15 Schema,
16 Signature,
19 Module,
20 Metadata,
21 Controller,
27 ProviderConfig,
35 HelmChart,
40}
41
42impl std::fmt::Display for ArtifactKind {
43 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44 match self {
45 Self::Resource => write!(f, "resource"),
46 Self::DataSource => write!(f, "data_source"),
47 Self::Provider => write!(f, "provider"),
48 Self::Test => write!(f, "test"),
49 Self::Schema => write!(f, "schema"),
50 Self::Signature => write!(f, "signature"),
51 Self::Module => write!(f, "module"),
52 Self::Metadata => write!(f, "metadata"),
53 Self::Controller => write!(f, "controller"),
54 Self::ProviderConfig => write!(f, "provider_config"),
55 Self::HelmChart => write!(f, "helm_chart"),
56 }
57 }
58}
59
60impl std::str::FromStr for ArtifactKind {
61 type Err = IacForgeError;
62
63 fn from_str(s: &str) -> Result<Self, Self::Err> {
64 match s {
65 "resource" => Ok(Self::Resource),
66 "data_source" => Ok(Self::DataSource),
67 "provider" => Ok(Self::Provider),
68 "test" => Ok(Self::Test),
69 "schema" => Ok(Self::Schema),
70 "signature" => Ok(Self::Signature),
71 "module" => Ok(Self::Module),
72 "metadata" => Ok(Self::Metadata),
73 "controller" => Ok(Self::Controller),
74 "provider_config" => Ok(Self::ProviderConfig),
75 "helm_chart" => Ok(Self::HelmChart),
76 _ => Err(IacForgeError::ValidationError(format!(
77 "unknown artifact kind: {s}"
78 ))),
79 }
80 }
81}
82
83#[must_use]
94#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
95pub struct GeneratedArtifact {
96 pub path: String,
98 pub content: String,
100 pub kind: ArtifactKind,
102 #[serde(default)]
105 pub source_hash: String,
106 #[serde(default)]
110 pub morphism_chain: Vec<String>,
111}
112
113impl GeneratedArtifact {
114 pub fn new(path: impl Into<String>, content: impl Into<String>, kind: ArtifactKind) -> Self {
117 Self {
118 path: path.into(),
119 content: content.into(),
120 kind,
121 source_hash: String::new(),
122 morphism_chain: Vec::new(),
123 }
124 }
125
126 pub fn with_provenance(
130 mut self,
131 source_hash: impl Into<String>,
132 morphism_chain: Vec<String>,
133 ) -> Self {
134 self.source_hash = source_hash.into();
135 self.morphism_chain = morphism_chain;
136 self
137 }
138
139 #[must_use]
141 pub fn has_provenance(&self) -> bool {
142 !self.source_hash.is_empty()
143 }
144}
145
146impl std::fmt::Display for GeneratedArtifact {
147 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148 write!(f, "[{}] {}", self.kind, self.path)
149 }
150}
151
152pub trait NamingConvention {
158 #[must_use]
160 fn resource_type_name(&self, resource_name: &str, provider_name: &str) -> String;
161
162 #[must_use]
167 fn data_source_type_name(&self, ds_name: &str, provider_name: &str) -> String {
168 self.resource_type_name(ds_name, provider_name)
169 }
170
171 #[must_use]
173 fn file_name(&self, resource_name: &str, kind: &ArtifactKind) -> String;
174
175 #[must_use]
177 fn field_name(&self, api_name: &str) -> String;
178}
179
180pub trait Backend {
185 #[must_use]
187 fn platform(&self) -> &str;
188
189 fn generate_resource(
195 &self,
196 resource: &IacResource,
197 provider: &IacProvider,
198 ) -> Result<Vec<GeneratedArtifact>, IacForgeError>;
199
200 fn generate_data_source(
206 &self,
207 ds: &IacDataSource,
208 provider: &IacProvider,
209 ) -> Result<Vec<GeneratedArtifact>, IacForgeError>;
210
211 fn generate_provider(
217 &self,
218 provider: &IacProvider,
219 resources: &[IacResource],
220 data_sources: &[IacDataSource],
221 ) -> Result<Vec<GeneratedArtifact>, IacForgeError>;
222
223 fn generate_test(
229 &self,
230 resource: &IacResource,
231 provider: &IacProvider,
232 ) -> Result<Vec<GeneratedArtifact>, IacForgeError>;
233
234 fn generate_action(
245 &self,
246 _action: &IacAction,
247 _provider: &IacProvider,
248 ) -> Result<Vec<GeneratedArtifact>, IacForgeError> {
249 Ok(vec![])
250 }
251
252 fn naming(&self) -> &dyn NamingConvention;
254
255 fn validate_resource(&self, _resource: &IacResource, _provider: &IacProvider) -> Vec<String> {
260 vec![]
261 }
262
263 fn generate_all(
273 &self,
274 provider: &IacProvider,
275 resources: &[IacResource],
276 data_sources: &[IacDataSource],
277 ) -> Result<Vec<GeneratedArtifact>, IacForgeError> {
278 let mut artifacts = Vec::new();
279 for r in resources {
280 artifacts.extend(self.generate_resource(r, provider)?);
281 }
282 for ds in data_sources {
283 artifacts.extend(self.generate_data_source(ds, provider)?);
284 }
285 artifacts.extend(self.generate_provider(provider, resources, data_sources)?);
286 for r in resources {
287 artifacts.extend(self.generate_test(r, provider)?);
288 }
289 Ok(artifacts)
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296 use crate::ir::{AuthInfo, CrudInfo, IdentityInfo};
297 use std::collections::BTreeMap;
298
299 #[test]
300 fn artifact_kind_display() {
301 assert_eq!(ArtifactKind::Resource.to_string(), "resource");
302 assert_eq!(ArtifactKind::Provider.to_string(), "provider");
303 assert_eq!(ArtifactKind::Test.to_string(), "test");
304 assert_eq!(ArtifactKind::Schema.to_string(), "schema");
305 assert_eq!(ArtifactKind::Controller.to_string(), "controller");
306 assert_eq!(ArtifactKind::ProviderConfig.to_string(), "provider_config");
307 assert_eq!(ArtifactKind::HelmChart.to_string(), "helm_chart");
308 }
309
310 #[test]
311 fn artifact_kind_round_trip_for_new_variants() {
312 for s in ["controller", "provider_config", "helm_chart"] {
313 let kind: ArtifactKind = s.parse().expect("parse");
314 assert_eq!(kind.to_string(), s);
315 }
316 }
317
318 #[test]
319 fn artifact_kind_unknown_string_is_error() {
320 let err: Result<ArtifactKind, _> = "not_a_kind".parse();
321 assert!(err.is_err());
322 }
323
324 struct TestBackend;
326
327 struct TestNaming;
328
329 impl NamingConvention for TestNaming {
330 fn resource_type_name(&self, resource_name: &str, provider_name: &str) -> String {
331 format!("{provider_name}_{resource_name}")
332 }
333 fn file_name(&self, resource_name: &str, _kind: &ArtifactKind) -> String {
334 format!("{resource_name}.go")
335 }
336 fn field_name(&self, api_name: &str) -> String {
337 api_name.to_string()
338 }
339 }
340
341 impl Backend for TestBackend {
342 fn platform(&self) -> &str {
343 "test"
344 }
345 fn generate_resource(
346 &self,
347 resource: &IacResource,
348 _provider: &IacProvider,
349 ) -> Result<Vec<GeneratedArtifact>, IacForgeError> {
350 Ok(vec![GeneratedArtifact {
351 path: format!("resource_{}.go", resource.name),
352 content: String::new(),
353 kind: ArtifactKind::Resource,
354 source_hash: String::new(),
355 morphism_chain: Vec::new(),
356 }])
357 }
358 fn generate_data_source(
359 &self,
360 ds: &IacDataSource,
361 _provider: &IacProvider,
362 ) -> Result<Vec<GeneratedArtifact>, IacForgeError> {
363 Ok(vec![GeneratedArtifact {
364 path: format!("data_source_{}.go", ds.name),
365 content: String::new(),
366 kind: ArtifactKind::DataSource,
367 source_hash: String::new(),
368 morphism_chain: Vec::new(),
369 }])
370 }
371 fn generate_provider(
372 &self,
373 _provider: &IacProvider,
374 _resources: &[IacResource],
375 _data_sources: &[IacDataSource],
376 ) -> Result<Vec<GeneratedArtifact>, IacForgeError> {
377 Ok(vec![GeneratedArtifact {
378 path: "provider.go".to_string(),
379 content: String::new(),
380 kind: ArtifactKind::Provider,
381 source_hash: String::new(),
382 morphism_chain: Vec::new(),
383 }])
384 }
385 fn generate_test(
386 &self,
387 resource: &IacResource,
388 _provider: &IacProvider,
389 ) -> Result<Vec<GeneratedArtifact>, IacForgeError> {
390 Ok(vec![GeneratedArtifact {
391 path: format!("resource_{}_test.go", resource.name),
392 content: String::new(),
393 kind: ArtifactKind::Test,
394 source_hash: String::new(),
395 morphism_chain: Vec::new(),
396 }])
397 }
398 fn naming(&self) -> &dyn NamingConvention {
399 &TestNaming
400 }
401 }
402
403 fn make_test_provider() -> IacProvider {
404 IacProvider {
405 name: "test".to_string(),
406 description: "Test provider".to_string(),
407 version: "1.0.0".to_string(),
408 auth: AuthInfo::default(),
409 skip_fields: vec![],
410 platform_config: BTreeMap::new(),
411 }
412 }
413
414 fn make_test_resource(name: &str) -> IacResource {
415 IacResource {
416 name: name.to_string(),
417 description: String::new(),
418 category: "test".to_string(),
419 crud: CrudInfo {
420 create_endpoint: "/create".to_string(),
421 create_schema: "Create".to_string(),
422 update_endpoint: None,
423 update_schema: None,
424 read_endpoint: "/read".to_string(),
425 read_schema: "Read".to_string(),
426 read_response_schema: None,
427 delete_endpoint: "/delete".to_string(),
428 delete_schema: "Delete".to_string(),
429 },
430 attributes: vec![],
431 identity: IdentityInfo {
432 id_field: "id".to_string(),
433 import_field: "id".to_string(),
434 force_replace_fields: vec![],
435 },
436 read_mapping: BTreeMap::new(),
437 }
438 }
439
440 fn make_test_data_source(name: &str) -> IacDataSource {
441 IacDataSource {
442 name: name.to_string(),
443 description: String::new(),
444 read_endpoint: "/read".to_string(),
445 read_schema: "Read".to_string(),
446 read_response_schema: None,
447 attributes: vec![],
448 read_mapping: BTreeMap::new(),
449 }
450 }
451
452 #[test]
453 fn validate_resource_default_returns_empty() {
454 let backend = TestBackend;
455 let provider = make_test_provider();
456 let resource = make_test_resource("foo");
457 let errors = backend.validate_resource(&resource, &provider);
458 assert!(errors.is_empty());
459 }
460
461 #[test]
462 fn generate_all_default_delegates() {
463 let backend = TestBackend;
464 let provider = make_test_provider();
465 let resources = vec![make_test_resource("r1"), make_test_resource("r2")];
466 let data_sources = vec![make_test_data_source("ds1")];
467
468 let artifacts = backend
469 .generate_all(&provider, &resources, &data_sources)
470 .expect("generate_all");
471
472 assert_eq!(artifacts.len(), 6);
474 assert_eq!(
475 artifacts
476 .iter()
477 .filter(|a| a.kind == ArtifactKind::Resource)
478 .count(),
479 2
480 );
481 assert_eq!(
482 artifacts
483 .iter()
484 .filter(|a| a.kind == ArtifactKind::DataSource)
485 .count(),
486 1
487 );
488 assert_eq!(
489 artifacts
490 .iter()
491 .filter(|a| a.kind == ArtifactKind::Provider)
492 .count(),
493 1
494 );
495 assert_eq!(
496 artifacts
497 .iter()
498 .filter(|a| a.kind == ArtifactKind::Test)
499 .count(),
500 2
501 );
502 }
503
504 #[test]
505 fn data_source_type_name_default() {
506 let naming = TestNaming;
507 assert_eq!(
508 naming.data_source_type_name("auth_method", "akeyless"),
509 naming.resource_type_name("auth_method", "akeyless")
510 );
511 }
512
513 #[test]
514 fn generated_artifact_display() {
515 let artifact = GeneratedArtifact {
516 path: "resource_secret.go".to_string(),
517 content: "package main".to_string(),
518 kind: ArtifactKind::Resource,
519 source_hash: String::new(),
520 morphism_chain: Vec::new(),
521 };
522 assert_eq!(artifact.to_string(), "[resource] resource_secret.go");
523 }
524
525 #[test]
526 fn generated_artifact_serialize_roundtrip() {
527 let artifact = GeneratedArtifact {
528 path: "provider.go".to_string(),
529 content: "code here".to_string(),
530 kind: ArtifactKind::Provider,
531 source_hash: String::new(),
532 morphism_chain: Vec::new(),
533 };
534 let json = serde_json::to_string(&artifact).expect("serialize");
535 let deserialized: GeneratedArtifact = serde_json::from_str(&json).expect("deserialize");
536 assert_eq!(artifact, deserialized);
537 }
538
539 #[test]
540 fn artifact_kind_serialize_roundtrip() {
541 let kinds = vec![
542 ArtifactKind::Resource,
543 ArtifactKind::DataSource,
544 ArtifactKind::Provider,
545 ArtifactKind::Test,
546 ArtifactKind::Schema,
547 ArtifactKind::Signature,
548 ArtifactKind::Module,
549 ArtifactKind::Metadata,
550 ];
551 for kind in kinds {
552 let json = serde_json::to_string(&kind).expect("serialize");
553 let deserialized: ArtifactKind = serde_json::from_str(&json).expect("deserialize");
554 assert_eq!(kind, deserialized);
555 }
556 }
557
558 #[test]
559 fn artifact_kind_display_all_variants() {
560 assert_eq!(ArtifactKind::DataSource.to_string(), "data_source");
561 assert_eq!(ArtifactKind::Module.to_string(), "module");
562 assert_eq!(ArtifactKind::Metadata.to_string(), "metadata");
563 }
564
565 struct FailingBackend;
567
568 struct FailingNaming;
569
570 impl NamingConvention for FailingNaming {
571 fn resource_type_name(&self, resource_name: &str, provider_name: &str) -> String {
572 format!("{provider_name}_{resource_name}")
573 }
574 fn file_name(&self, resource_name: &str, _kind: &ArtifactKind) -> String {
575 format!("{resource_name}.go")
576 }
577 fn field_name(&self, api_name: &str) -> String {
578 api_name.to_string()
579 }
580 }
581
582 impl Backend for FailingBackend {
583 fn platform(&self) -> &str {
584 "failing"
585 }
586 fn generate_resource(
587 &self,
588 _resource: &IacResource,
589 _provider: &IacProvider,
590 ) -> Result<Vec<GeneratedArtifact>, IacForgeError> {
591 Err(IacForgeError::BackendError(
592 "resource generation failed".to_string(),
593 ))
594 }
595 fn generate_data_source(
596 &self,
597 _ds: &IacDataSource,
598 _provider: &IacProvider,
599 ) -> Result<Vec<GeneratedArtifact>, IacForgeError> {
600 Ok(vec![])
601 }
602 fn generate_provider(
603 &self,
604 _provider: &IacProvider,
605 _resources: &[IacResource],
606 _data_sources: &[IacDataSource],
607 ) -> Result<Vec<GeneratedArtifact>, IacForgeError> {
608 Ok(vec![])
609 }
610 fn generate_test(
611 &self,
612 _resource: &IacResource,
613 _provider: &IacProvider,
614 ) -> Result<Vec<GeneratedArtifact>, IacForgeError> {
615 Ok(vec![])
616 }
617 fn naming(&self) -> &dyn NamingConvention {
618 &FailingNaming
619 }
620 }
621
622 #[test]
623 fn generate_all_propagates_resource_error() {
624 let backend = FailingBackend;
625 let provider = make_test_provider();
626 let resources = vec![make_test_resource("r1")];
627 let data_sources = vec![];
628
629 let result = backend.generate_all(&provider, &resources, &data_sources);
630 assert!(
631 result.is_err(),
632 "should propagate resource generation error"
633 );
634 let err_msg = result.unwrap_err().to_string();
635 assert!(
636 err_msg.contains("resource generation failed"),
637 "error message should contain failure reason"
638 );
639 }
640
641 struct FailingDsBackend;
643
644 impl Backend for FailingDsBackend {
645 fn platform(&self) -> &str {
646 "failing_ds"
647 }
648 fn generate_resource(
649 &self,
650 _resource: &IacResource,
651 _provider: &IacProvider,
652 ) -> Result<Vec<GeneratedArtifact>, IacForgeError> {
653 Ok(vec![])
654 }
655 fn generate_data_source(
656 &self,
657 _ds: &IacDataSource,
658 _provider: &IacProvider,
659 ) -> Result<Vec<GeneratedArtifact>, IacForgeError> {
660 Err(IacForgeError::BackendError(
661 "data source generation failed".to_string(),
662 ))
663 }
664 fn generate_provider(
665 &self,
666 _provider: &IacProvider,
667 _resources: &[IacResource],
668 _data_sources: &[IacDataSource],
669 ) -> Result<Vec<GeneratedArtifact>, IacForgeError> {
670 Ok(vec![])
671 }
672 fn generate_test(
673 &self,
674 _resource: &IacResource,
675 _provider: &IacProvider,
676 ) -> Result<Vec<GeneratedArtifact>, IacForgeError> {
677 Ok(vec![])
678 }
679 fn naming(&self) -> &dyn NamingConvention {
680 &FailingNaming
681 }
682 }
683
684 #[test]
685 fn generate_all_propagates_data_source_error() {
686 let backend = FailingDsBackend;
687 let provider = make_test_provider();
688 let resources = vec![];
689 let data_sources = vec![make_test_data_source("ds1")];
690
691 let result = backend.generate_all(&provider, &resources, &data_sources);
692 assert!(
693 result.is_err(),
694 "should propagate data source generation error"
695 );
696 }
697
698 struct ValidatingBackend;
700
701 impl Backend for ValidatingBackend {
702 fn platform(&self) -> &str {
703 "validating"
704 }
705 fn generate_resource(
706 &self,
707 _resource: &IacResource,
708 _provider: &IacProvider,
709 ) -> Result<Vec<GeneratedArtifact>, IacForgeError> {
710 Ok(vec![])
711 }
712 fn generate_data_source(
713 &self,
714 _ds: &IacDataSource,
715 _provider: &IacProvider,
716 ) -> Result<Vec<GeneratedArtifact>, IacForgeError> {
717 Ok(vec![])
718 }
719 fn generate_provider(
720 &self,
721 _provider: &IacProvider,
722 _resources: &[IacResource],
723 _data_sources: &[IacDataSource],
724 ) -> Result<Vec<GeneratedArtifact>, IacForgeError> {
725 Ok(vec![])
726 }
727 fn generate_test(
728 &self,
729 _resource: &IacResource,
730 _provider: &IacProvider,
731 ) -> Result<Vec<GeneratedArtifact>, IacForgeError> {
732 Ok(vec![])
733 }
734 fn naming(&self) -> &dyn NamingConvention {
735 &TestNaming
736 }
737 fn validate_resource(
738 &self,
739 resource: &IacResource,
740 _provider: &IacProvider,
741 ) -> Vec<String> {
742 let mut errors = Vec::new();
743 if resource.attributes.is_empty() {
744 errors.push("resource has no attributes".to_string());
745 }
746 errors
747 }
748 }
749
750 #[test]
751 fn validate_resource_override_returns_errors() {
752 let backend = ValidatingBackend;
753 let provider = make_test_provider();
754 let resource = make_test_resource("empty");
755 let errors = backend.validate_resource(&resource, &provider);
757 assert_eq!(errors.len(), 1);
758 assert_eq!(errors[0], "resource has no attributes");
759 }
760
761 #[test]
762 fn validate_resource_override_returns_empty_for_valid() {
763 let backend = ValidatingBackend;
764 let provider = make_test_provider();
765 let mut resource = make_test_resource("valid");
766 resource.attributes.push(crate::ir::IacAttribute {
767 api_name: "name".to_string(),
768 canonical_name: "name".to_string(),
769 description: String::new(),
770 iac_type: crate::ir::IacType::String,
771 required: true,
772 optional: false,
773 computed: false,
774 sensitive: false,
775 json_encoded: false,
776 immutable: false,
777 default_value: None,
778 enum_values: None,
779 read_path: None,
780 update_only: false,
781 });
782 let errors = backend.validate_resource(&resource, &provider);
783 assert!(errors.is_empty());
784 }
785
786 #[test]
787 fn generate_all_empty_inputs() {
788 let backend = TestBackend;
789 let provider = make_test_provider();
790 let artifacts = backend
791 .generate_all(&provider, &[], &[])
792 .expect("generate_all");
793 assert_eq!(artifacts.len(), 1);
795 assert_eq!(artifacts[0].kind, ArtifactKind::Provider);
796 }
797
798 struct FailingProviderBackend;
800
801 impl Backend for FailingProviderBackend {
802 fn platform(&self) -> &str {
803 "failing_provider"
804 }
805 fn generate_resource(
806 &self,
807 _resource: &IacResource,
808 _provider: &IacProvider,
809 ) -> Result<Vec<GeneratedArtifact>, IacForgeError> {
810 Ok(vec![])
811 }
812 fn generate_data_source(
813 &self,
814 _ds: &IacDataSource,
815 _provider: &IacProvider,
816 ) -> Result<Vec<GeneratedArtifact>, IacForgeError> {
817 Ok(vec![])
818 }
819 fn generate_provider(
820 &self,
821 _provider: &IacProvider,
822 _resources: &[IacResource],
823 _data_sources: &[IacDataSource],
824 ) -> Result<Vec<GeneratedArtifact>, IacForgeError> {
825 Err(IacForgeError::BackendError(
826 "provider generation failed".to_string(),
827 ))
828 }
829 fn generate_test(
830 &self,
831 _resource: &IacResource,
832 _provider: &IacProvider,
833 ) -> Result<Vec<GeneratedArtifact>, IacForgeError> {
834 Ok(vec![])
835 }
836 fn naming(&self) -> &dyn NamingConvention {
837 &FailingNaming
838 }
839 }
840
841 #[test]
842 fn generate_all_propagates_provider_error() {
843 let backend = FailingProviderBackend;
844 let provider = make_test_provider();
845 let result = backend.generate_all(&provider, &[], &[]);
846 assert!(
847 result.is_err(),
848 "should propagate provider generation error"
849 );
850 }
851
852 struct FailingTestBackend;
854
855 impl Backend for FailingTestBackend {
856 fn platform(&self) -> &str {
857 "failing_test"
858 }
859 fn generate_resource(
860 &self,
861 _resource: &IacResource,
862 _provider: &IacProvider,
863 ) -> Result<Vec<GeneratedArtifact>, IacForgeError> {
864 Ok(vec![])
865 }
866 fn generate_data_source(
867 &self,
868 _ds: &IacDataSource,
869 _provider: &IacProvider,
870 ) -> Result<Vec<GeneratedArtifact>, IacForgeError> {
871 Ok(vec![])
872 }
873 fn generate_provider(
874 &self,
875 _provider: &IacProvider,
876 _resources: &[IacResource],
877 _data_sources: &[IacDataSource],
878 ) -> Result<Vec<GeneratedArtifact>, IacForgeError> {
879 Ok(vec![])
880 }
881 fn generate_test(
882 &self,
883 _resource: &IacResource,
884 _provider: &IacProvider,
885 ) -> Result<Vec<GeneratedArtifact>, IacForgeError> {
886 Err(IacForgeError::BackendError(
887 "test generation failed".to_string(),
888 ))
889 }
890 fn naming(&self) -> &dyn NamingConvention {
891 &FailingNaming
892 }
893 }
894
895 #[test]
896 fn generate_all_propagates_test_error() {
897 let backend = FailingTestBackend;
898 let provider = make_test_provider();
899 let resources = vec![make_test_resource("r1")];
900 let result = backend.generate_all(&provider, &resources, &[]);
901 assert!(result.is_err(), "should propagate test generation error");
902 }
903
904 #[test]
905 fn generate_all_ordering_resources_before_datasources_before_provider() {
906 let backend = TestBackend;
907 let provider = make_test_provider();
908 let resources = vec![make_test_resource("r1")];
909 let data_sources = vec![make_test_data_source("ds1")];
910 let artifacts = backend
911 .generate_all(&provider, &resources, &data_sources)
912 .expect("generate_all");
913
914 let resource_idx = artifacts
915 .iter()
916 .position(|a| a.kind == ArtifactKind::Resource)
917 .expect("resource");
918 let ds_idx = artifacts
919 .iter()
920 .position(|a| a.kind == ArtifactKind::DataSource)
921 .expect("data_source");
922 let provider_idx = artifacts
923 .iter()
924 .position(|a| a.kind == ArtifactKind::Provider)
925 .expect("provider");
926 let test_idx = artifacts
927 .iter()
928 .position(|a| a.kind == ArtifactKind::Test)
929 .expect("test");
930
931 assert!(
932 resource_idx < ds_idx,
933 "resources should come before data sources"
934 );
935 assert!(
936 ds_idx < provider_idx,
937 "data sources should come before provider"
938 );
939 assert!(provider_idx < test_idx, "provider should come before tests");
940 }
941
942 #[test]
943 fn generate_all_multiple_resources_produces_correct_count() {
944 let backend = TestBackend;
945 let provider = make_test_provider();
946 let resources = vec![
947 make_test_resource("r1"),
948 make_test_resource("r2"),
949 make_test_resource("r3"),
950 ];
951 let data_sources = vec![make_test_data_source("ds1"), make_test_data_source("ds2")];
952 let artifacts = backend
953 .generate_all(&provider, &resources, &data_sources)
954 .expect("generate_all");
955 assert_eq!(artifacts.len(), 9);
957 }
958
959 #[test]
960 fn naming_convention_resource_type_name() {
961 let naming = TestNaming;
962 assert_eq!(
963 naming.resource_type_name("secret", "akeyless"),
964 "akeyless_secret"
965 );
966 }
967
968 #[test]
969 fn naming_convention_file_name() {
970 let naming = TestNaming;
971 assert_eq!(
972 naming.file_name("secret", &ArtifactKind::Resource),
973 "secret.go"
974 );
975 assert_eq!(naming.file_name("secret", &ArtifactKind::Test), "secret.go");
976 }
977
978 #[test]
979 fn naming_convention_field_name() {
980 let naming = TestNaming;
981 assert_eq!(naming.field_name("my_field"), "my_field");
982 }
983
984 #[test]
985 fn generated_artifact_display_data_source() {
986 let artifact = GeneratedArtifact {
987 path: "data_source_config.go".to_string(),
988 content: String::new(),
989 kind: ArtifactKind::DataSource,
990 source_hash: String::new(),
991 morphism_chain: Vec::new(),
992 };
993 assert_eq!(artifact.to_string(), "[data_source] data_source_config.go");
994 }
995
996 #[test]
997 fn generated_artifact_display_test() {
998 let artifact = GeneratedArtifact {
999 path: "resource_secret_test.go".to_string(),
1000 content: "test code".to_string(),
1001 kind: ArtifactKind::Test,
1002 source_hash: String::new(),
1003 morphism_chain: Vec::new(),
1004 };
1005 assert_eq!(artifact.to_string(), "[test] resource_secret_test.go");
1006 }
1007
1008 #[test]
1009 fn generated_artifact_equality() {
1010 let a = GeneratedArtifact {
1011 path: "a.go".to_string(),
1012 content: "content".to_string(),
1013 kind: ArtifactKind::Resource,
1014 source_hash: String::new(),
1015 morphism_chain: Vec::new(),
1016 };
1017 let b = GeneratedArtifact {
1018 path: "a.go".to_string(),
1019 content: "content".to_string(),
1020 kind: ArtifactKind::Resource,
1021 source_hash: String::new(),
1022 morphism_chain: Vec::new(),
1023 };
1024 let c = GeneratedArtifact {
1025 path: "a.go".to_string(),
1026 content: "different".to_string(),
1027 kind: ArtifactKind::Resource,
1028 source_hash: String::new(),
1029 morphism_chain: Vec::new(),
1030 };
1031 assert_eq!(a, b);
1032 assert_ne!(a, c);
1033 }
1034
1035 #[test]
1036 fn generated_artifact_display_metadata() {
1037 let artifact = GeneratedArtifact {
1038 path: "metadata.json".to_string(),
1039 content: "{}".to_string(),
1040 kind: ArtifactKind::Metadata,
1041 source_hash: String::new(),
1042 morphism_chain: Vec::new(),
1043 };
1044 assert_eq!(artifact.to_string(), "[metadata] metadata.json");
1045 }
1046
1047 #[test]
1048 fn generated_artifact_display_schema() {
1049 let artifact = GeneratedArtifact {
1050 path: "schema.json".to_string(),
1051 content: "{}".to_string(),
1052 kind: ArtifactKind::Schema,
1053 source_hash: String::new(),
1054 morphism_chain: Vec::new(),
1055 };
1056 assert_eq!(artifact.to_string(), "[schema] schema.json");
1057 }
1058
1059 #[test]
1060 fn generated_artifact_display_module() {
1061 let artifact = GeneratedArtifact {
1062 path: "index.ts".to_string(),
1063 content: String::new(),
1064 kind: ArtifactKind::Module,
1065 source_hash: String::new(),
1066 morphism_chain: Vec::new(),
1067 };
1068 assert_eq!(artifact.to_string(), "[module] index.ts");
1069 }
1070
1071 #[test]
1072 fn backend_platform_name() {
1073 let backend = TestBackend;
1074 assert_eq!(backend.platform(), "test");
1075 }
1076
1077 #[test]
1078 fn generated_artifact_new() {
1079 let artifact = GeneratedArtifact::new("test.go", "package main", ArtifactKind::Resource);
1080 assert_eq!(artifact.path, "test.go");
1081 assert_eq!(artifact.content, "package main");
1082 assert_eq!(artifact.kind, ArtifactKind::Resource);
1083 }
1084
1085 #[test]
1086 fn generated_artifact_new_from_string() {
1087 let path = String::from("test.go");
1088 let content = String::from("code");
1089 let artifact = GeneratedArtifact::new(path, content, ArtifactKind::Test);
1090 assert_eq!(artifact.path, "test.go");
1091 }
1092
1093 #[test]
1094 fn artifact_kind_from_str_roundtrip() {
1095 let kinds = vec![
1096 ArtifactKind::Resource,
1097 ArtifactKind::DataSource,
1098 ArtifactKind::Provider,
1099 ArtifactKind::Test,
1100 ArtifactKind::Schema,
1101 ArtifactKind::Signature,
1102 ArtifactKind::Module,
1103 ArtifactKind::Metadata,
1104 ];
1105 for kind in kinds {
1106 let s = kind.to_string();
1107 let parsed: ArtifactKind = s.parse().expect("parse");
1108 assert_eq!(kind, parsed);
1109 }
1110 }
1111
1112 #[test]
1113 fn artifact_kind_from_str_invalid() {
1114 let result: Result<ArtifactKind, _> = "unknown".parse();
1115 assert!(result.is_err());
1116 }
1117}