haste_codegen/testscript_gen/
mod.rs1use std::path::Path;
2
3use crate::utilities::load;
4use haste_fhir_model::r4::generated::{
6 resources::{
7 Resource, TestScript, TestScriptFixture, TestScriptSetupActionAssert,
8 TestScriptSetupActionOperation, TestScriptTeardown, TestScriptTeardownAction,
9 TestScriptTest, TestScriptTestAction,
10 },
11 terminology::{AssertDirectionCodes, DefinedTypes, PublicationStatus},
12 types::{Coding, Meta, Reference},
13};
14use haste_reflect::MetaValue;
15use walkdir::WalkDir;
16
17fn file_path_to_resources(file_path: &Path) -> Result<Vec<Box<Resource>>, String> {
18 let resource = load::load_from_file(file_path)?;
19
20 Ok(match resource {
21 Resource::Bundle(bundle) => bundle
22 .entry
23 .unwrap_or(vec![])
24 .into_iter()
25 .filter_map(|entry| entry.resource)
26 .collect::<Vec<_>>(),
27 _ => vec![Box::new(resource)],
28 })
29}
30
31fn get_meta_mutable<'a>(resource: &'a mut Resource) -> Result<&'a mut Meta, String> {
32 let meta: &mut dyn std::any::Any = resource
33 .get_field_mut("meta")
34 .ok_or("Missing Meta Field".to_string())?;
35 let meta: &mut Option<Box<Meta>> = meta
36 .downcast_mut::<Option<Box<Meta>>>()
37 .ok_or("Failed to downcast meta".to_string())?;
38
39 if meta.is_none() {
40 *meta = Some(Box::new(Meta::default()))
41 }
42
43 Ok(meta.as_mut().unwrap())
44}
45
46fn set_resource_tag(tag: &str, resource: &mut Resource) -> Result<(), String> {
47 let meta = get_meta_mutable(resource)?;
48
49 meta.tag = Some(vec![Box::new(Coding {
50 code: Some(Box::new(tag.to_string().into())),
51 ..Default::default()
52 })]);
53
54 Ok(())
55}
56
57fn set_resource_id(id: &str, resource: &mut Resource) -> Result<(), String> {
58 let id_field: &mut dyn std::any::Any = resource
59 .get_field_mut("id")
60 .ok_or("Missing id field".to_string())?;
61
62 let id_field: &mut Option<String> = id_field
63 .downcast_mut::<Option<String>>()
64 .ok_or("Failed to downcast id field".to_string())?;
65
66 *id_field = Some(id.to_string());
67
68 Ok(())
69}
70
71fn fixture_name(i: usize, resource_type: &str) -> String {
72 format!("fixture-{}-{}", resource_type, i)
73}
74
75fn generate_testcases_for_resource(
76 tag: &str,
77 index: usize,
78 resource: &Resource,
79) -> Vec<TestScriptTest> {
80 let resource_type = resource.resource_type();
81 let defined_type = Some(Box::new(
82 DefinedTypes::try_from(resource_type.as_ref().to_string())
83 .expect("Unsupported resource type"),
84 ));
85
86 vec![TestScriptTest {
87 name: Some(Box::new(
88 format!("Test for resource with tag: {}", tag).into(),
89 )),
90 action: vec![
91 TestScriptTestAction {
92 operation: Some(TestScriptSetupActionOperation {
93 type_: Some(Box::new(Coding {
94 system: Some(Box::new(
95 "http://terminology.hl7.org/CodeSystem/testscript-operation-codes"
96 .to_string()
97 .into(),
98 )),
99 code: Some(Box::new("create".to_string().into())),
100 ..Default::default()
101 })),
102 resource: defined_type.clone(),
103 sourceId: Some(Box::new(
104 fixture_name(index, resource_type.as_ref())
105 .to_string()
106 .into(),
107 )),
108 responseId: Some(Box::new(fixture_name(index, resource_type.as_ref()).into())),
109 encodeRequestUrl: Box::new(true.into()),
110 ..Default::default()
111 }),
112 ..Default::default()
113 },
114 TestScriptTestAction {
115 assert: Some(TestScriptSetupActionAssert {
116 label: Some(Box::new("Read created resource".to_string().into())),
117 description: Some(Box::new(
118 format!(
119 "Confirm resource of type {} created.",
120 resource_type.as_ref()
121 )
122 .into(),
123 )),
124 direction: Some(Box::new(AssertDirectionCodes::Response(None))),
125 resource: defined_type.clone(),
126 warningOnly: Box::new(false.into()),
127 ..Default::default()
128 }),
129 ..Default::default()
130 },
131 ],
132 ..Default::default()
133 }]
134}
135
136fn generate_fixtures_for_resource(
137 testscript: &mut TestScript,
138 resources: Vec<Box<Resource>>,
139) -> Result<(), String> {
140 let mut contained = vec![];
141 let mut fixtures = vec![];
142
143 for (index, resource) in resources.into_iter().enumerate() {
144 let resource_type = resource.resource_type();
145 let fixture_id = fixture_name(index, resource_type.as_ref());
146
147 fixtures.push(TestScriptFixture {
148 id: Some(fixture_id.clone()),
149 autocreate: Box::new(false.into()),
150 autodelete: Box::new(false.into()),
151 resource: Some(Box::new(Reference {
152 reference: Some(Box::new(format!("#{}", fixture_id).into())),
153 ..Default::default()
154 })),
155 ..Default::default()
156 });
157 contained.push(resource);
158 }
159
160 testscript.contained = Some(contained);
161 testscript.fixture = Some(fixtures);
162
163 Ok(())
164}
165
166fn create_tag(file_path: &Path) -> String {
167 file_path
168 .to_str()
169 .unwrap()
170 .replace("/", "-")
171 .replace("\\", "-")
172 .replace(".", "-")
173}
174
175fn generate_testscript_from_file(file_path: &Path) -> Result<TestScript, String> {
176 let mut testscript = TestScript::default();
177 let mut resources = file_path_to_resources(file_path)?;
178
179 let tag = create_tag(file_path);
180
181 testscript.url = Box::new(tag.to_string().into());
182 testscript.status = Box::new(PublicationStatus::Active(None));
183 testscript.id = Some(tag.to_string());
184 testscript.name = Box::new(tag.to_string().into());
185
186 for (i, resource) in resources.iter_mut().enumerate() {
187 set_resource_tag(&tag, resource).expect("Failed to set resource tag");
188 set_resource_id(
189 &fixture_name(i, &resource.resource_type().as_ref()),
190 resource,
191 )
192 .expect("Failed to set resource id");
193 }
194
195 generate_fixtures_for_resource(&mut testscript, resources.clone())?;
196
197 testscript.test = Some(
198 resources
199 .iter()
200 .enumerate()
201 .map(|(i, r)| generate_testcases_for_resource(&tag, i, r))
202 .flatten()
203 .collect::<Vec<_>>(),
204 );
205
206 testscript.teardown = Some(TestScriptTeardown {
207 action: vec![TestScriptTeardownAction {
208 operation: TestScriptSetupActionOperation {
209 type_: Some(Box::new(Coding {
210 system: Some(Box::new(
211 "http://terminology.hl7.org/CodeSystem/testscript-operation-codes"
212 .to_string()
213 .into(),
214 )),
215 code: Some(Box::new("deleteCondMultiple".to_string().into())),
216 ..Default::default()
217 })),
218 encodeRequestUrl: Box::new(true.into()),
219 resource: None,
220 params: Some(Box::new(format!("_tag={}", tag).into())),
221 description: Some(Box::new(
222 "Delete resources created in test.".to_string().into(),
223 )),
224
225 ..Default::default()
226 },
227 ..Default::default()
228 }],
229 ..Default::default()
230 });
231
232 Ok(testscript)
233}
234
235pub fn generate_testscripts(file_paths: &Vec<String>) -> Result<Vec<TestScript>, String> {
236 let mut testscripts = vec![];
237 for dir_path in file_paths {
238 let walker = WalkDir::new(dir_path).into_iter();
239 for entry in walker
240 .filter_map(|e| e.ok())
241 .filter(|e| e.metadata().unwrap().is_file())
242 {
243 let testscript = generate_testscript_from_file(&entry.path().to_path_buf())?;
244 testscripts.push(testscript);
245 }
246 }
247
248 Ok(testscripts)
249}