1use crate::error::{Result, SzConfigError};
33use serde_json::Value;
34use std::fs;
35use std::path::Path;
36
37pub struct CommandProcessor {
39 config: String,
40 commands_executed: Vec<String>,
41 dry_run: bool,
42}
43
44impl CommandProcessor {
45 pub fn new(config_json: String) -> Self {
50 Self {
51 config: config_json,
52 commands_executed: Vec::new(),
53 dry_run: false,
54 }
55 }
56
57 pub fn dry_run(mut self, enabled: bool) -> Self {
64 self.dry_run = enabled;
65 self
66 }
67
68 pub fn process_file<P: AsRef<Path>>(&mut self, path: P) -> Result<String> {
76 let content = fs::read_to_string(path.as_ref()).map_err(|e| {
77 SzConfigError::InvalidConfig(format!("Failed to read script file: {e}"))
78 })?;
79 self.process_script(&content)
80 }
81
82 pub fn process_script(&mut self, script: &str) -> Result<String> {
90 for (line_num, line) in script.lines().enumerate() {
91 let trimmed = line.trim();
92
93 if trimmed.is_empty() || trimmed.starts_with('#') {
95 continue;
96 }
97
98 if let Err(e) = self.process_command(trimmed) {
100 return Err(SzConfigError::InvalidConfig(format!(
101 "Line {}: {} - Error: {}",
102 line_num + 1,
103 trimmed,
104 e
105 )));
106 }
107
108 if trimmed != "save" {
110 self.commands_executed
111 .push(format!("Line {}: {}", line_num + 1, trimmed));
112 }
113 }
114
115 Ok(self.config.clone())
116 }
117
118 fn process_command(&mut self, line: &str) -> Result<()> {
120 if line == "save" {
122 return Ok(());
123 }
124
125 let (cmd, params) = parse_command_line(line)?;
126
127 let new_config = execute_command(&self.config, &cmd, ¶ms)?;
129
130 if !self.dry_run {
132 self.config = new_config;
133 }
134
135 Ok(())
136 }
137
138 pub fn summary(&self) -> String {
140 format!(
141 "Executed {} commands{}",
142 self.commands_executed.len(),
143 if self.dry_run { " (DRY RUN)" } else { "" }
144 )
145 }
146
147 pub fn get_executed_commands(&self) -> &[String] {
149 &self.commands_executed
150 }
151
152 pub fn get_config(&self) -> &str {
154 &self.config
155 }
156}
157
158fn parse_command_line(line: &str) -> Result<(String, Value)> {
160 let parts: Vec<&str> = line.splitn(2, ' ').collect();
161
162 if parts.is_empty() {
163 return Err(SzConfigError::InvalidInput("Empty command".to_string()));
164 }
165
166 let cmd = parts[0].to_string();
167
168 let params = if parts.len() > 1 {
169 serde_json::from_str(parts[1])
170 .map_err(|e| SzConfigError::JsonParse(format!("Invalid JSON in '{cmd}': {e}")))?
171 } else {
172 Value::Null
173 };
174
175 Ok((cmd, params))
176}
177
178fn execute_command(config: &str, cmd: &str, params: &Value) -> Result<String> {
180 match cmd {
181 "verifyCompatibilityVersion" => {
183 let expected = get_str_param(params, "expectedVersion")?;
184 crate::versioning::verify_compatibility_version(config, expected)?;
185 Ok(config.to_string()) }
187
188 "updateCompatibilityVersion" => {
189 let to = get_str_param(params, "toVersion")?;
191 crate::versioning::update_compatibility_version(config, to)
192 }
193
194 "removeConfigSection" => {
196 let section = get_str_param(params, "section")?;
197 crate::config_sections::remove_config_section(config, section)
198 }
199
200 "removeConfigSectionField" => {
201 let section = get_str_param(params, "section")?;
202 let field = get_str_param(params, "field")?;
203 crate::config_sections::remove_config_section_field(config, section, field)
204 .map(|(cfg, _)| cfg)
205 }
206
207 "addConfigSection" => {
208 let section = get_str_param(params, "section")?;
209 crate::config_sections::add_config_section(config, section)
211 }
212
213 "addConfigSectionField" => {
214 let section = get_str_param(params, "section")?;
215 let field = get_str_param(params, "field")?;
216 let value = ¶ms["value"];
217 crate::config_sections::add_config_section_field(config, section, field, value)
218 .map(|(cfg, _)| cfg)
219 }
220
221 "addAttribute" => {
223 let attr = get_str_param(params, "attribute")?;
224 let class = get_str_param(params, "class")?;
225 let feature = get_str_param(params, "feature")?;
226 let element = get_str_param(params, "element")?;
227 let required = get_opt_str_param(params, "required");
228 let internal = get_opt_str_param(params, "internal");
229 let default_value = get_opt_str_param(params, "default");
230
231 crate::attributes::add_attribute(
232 config,
233 crate::attributes::AddAttributeParams {
234 attribute: attr,
235 feature,
236 element,
237 class,
238 default_value,
239 internal,
240 required,
241 },
242 )
243 .map(|(cfg, _)| cfg)
244 }
245
246 "deleteAttribute" => {
247 let attr = get_str_param(params, "attribute")?;
248 crate::attributes::delete_attribute(config, attr)
249 }
250
251 "setAttribute" => {
252 let set_params = crate::attributes::SetAttributeParams::try_from(params)?;
253 crate::attributes::set_attribute(config, set_params)
254 }
255
256 "addElement" => {
258 let element = get_str_param(params, "element")?;
259 let datatype = get_opt_str_param(params, "datatype");
260
261 let add_params = crate::elements::AddElementParams {
262 code: element,
263 description: None, data_type: datatype,
265 tokenized: None,
266 };
267
268 crate::elements::add_element(config, add_params)
269 }
270
271 "setFeatureElement" => {
272 let feature = get_str_param(params, "feature")?;
273 let element = get_str_param(params, "element")?;
274
275 if let Some(derived) = get_opt_str_param(params, "derived") {
277 crate::elements::set_feature_element_derived(config, feature, element, derived)
278 } else if let Some(display_level) = params.get("displayLevel").and_then(|v| v.as_i64())
279 {
280 crate::elements::set_feature_element_display_level(
281 config,
282 feature,
283 element,
284 display_level,
285 )
286 } else {
287 Err(SzConfigError::InvalidInput(
288 "setFeatureElement requires 'derived' or 'displayLevel'".to_string(),
289 ))
290 }
291 }
292
293 "addFeature" => {
295 let feature = get_str_param(params, "feature")?;
296 let element_list = params
297 .get("elementList")
298 .ok_or_else(|| SzConfigError::MissingField("elementList".to_string()))?;
299
300 crate::features::add_feature(
301 config,
302 crate::features::AddFeatureParams {
303 feature,
304 element_list,
305 class: get_opt_str_param(params, "class"),
306 behavior: get_opt_str_param(params, "behavior"),
307 candidates: get_opt_str_param(params, "candidates"),
308 anonymize: get_opt_str_param(params, "anonymize"),
309 derived: get_opt_str_param(params, "derived"),
310 history: get_opt_str_param(params, "history"),
311 matchkey: get_opt_str_param(params, "matchKey"),
312 standardize: get_opt_str_param(params, "standardize").filter(|s| !s.is_empty()),
313 expression: get_opt_str_param(params, "expression").filter(|s| !s.is_empty()),
314 comparison: get_opt_str_param(params, "comparison").filter(|s| !s.is_empty()),
315 version: params.get("version").and_then(|v| v.as_i64()),
316 rtype_id: params.get("rtypeId").and_then(|v| v.as_i64()),
317 },
318 )
319 }
320
321 "setFeature" => {
322 let feature = get_str_param(params, "feature")?;
323
324 crate::features::set_feature(
325 config,
326 crate::features::SetFeatureParams {
327 feature,
328 candidates: get_opt_str_param(params, "candidates"),
329 anonymize: get_opt_str_param(params, "anonymize"),
330 derived: get_opt_str_param(params, "derived"),
331 history: get_opt_str_param(params, "history"),
332 matchkey: get_opt_str_param(params, "matchKey"),
333 behavior: get_opt_str_param(params, "behavior"),
334 class: get_opt_str_param(params, "class"),
335 version: params.get("version").and_then(|v| v.as_i64()),
336 rtype_id: params.get("rtypeId").and_then(|v| v.as_i64()),
337 },
338 )
339 }
340
341 "addBehaviorOverride" => {
343 let feature = get_str_param(params, "feature")?;
344 let usage_type = get_str_param(params, "usageType")?;
345 let behavior = get_str_param(params, "behavior")?;
346
347 crate::behavior_overrides::add_behavior_override(
348 config,
349 crate::behavior_overrides::AddBehaviorOverrideParams::new(
350 feature, usage_type, behavior,
351 ),
352 )
353 }
354
355 "deleteFragment" => {
357 let fragment = get_str_param(params, "fragment").or_else(|_| {
358 params
360 .as_str()
361 .ok_or_else(|| SzConfigError::MissingField("fragment".to_string()))
362 })?;
363 crate::fragments::delete_fragment(config, fragment)
364 }
365
366 "setFragment" => {
367 let fragment = get_str_param(params, "fragment")?;
368 let source = get_str_param(params, "source")?;
369
370 let fragment_config = serde_json::json!({
372 "ERFRAG_SOURCE": source
373 });
374
375 crate::fragments::set_fragment(config, fragment, &fragment_config)
376 }
377
378 "addFragment" => {
379 crate::fragments::add_fragment(config, params).map(|(cfg, _)| cfg)
381 }
382
383 "addRule" => {
385 let id = params
386 .get("ERRULE_ID")
387 .and_then(|v| v.as_i64())
388 .unwrap_or(0);
389 crate::rules::add_rule(config, id, params).map(|(cfg, _)| cfg)
390 }
391
392 "setRule" => {
393 let set_params = crate::rules::SetRuleParams::try_from(params)?;
394 crate::rules::set_rule(config, set_params)
395 }
396
397 "setSetting" => {
399 let name = get_str_param(params, "name")?;
400 let value = ¶ms["value"];
401 crate::system_params::set_system_parameter(config, name, value)
402 }
403
404 "removeStandardizeFunction" | "deleteStandardizeFunction" => {
406 let func = get_str_param(params, "function")?;
407 crate::functions::standardize::delete_standardize_function(config, func)
408 .map(|(cfg, _)| cfg)
409 }
410
411 "addStandardizeFunction" => {
412 let func = get_str_param(params, "function")?;
413 let connect = get_str_param(params, "connectStr")?;
414 let desc = get_opt_str_param(params, "description");
415 let language = get_opt_str_param(params, "language");
416
417 crate::functions::standardize::add_standardize_function(
418 config,
419 func,
420 crate::functions::standardize::AddStandardizeFunctionParams {
421 connect_str: connect,
422 description: desc,
423 language,
424 },
425 )
426 .map(|(cfg, _)| cfg)
427 }
428
429 "removeComparisonFunction" | "deleteComparisonFunction" => {
431 let func = get_str_param(params, "function")?;
432 crate::functions::comparison::delete_comparison_function(config, func)
433 .map(|(cfg, _)| cfg)
434 }
435
436 "addComparisonFunction" => {
437 let func = get_str_param(params, "function")?;
438 let connect = get_str_param(params, "connectStr")?;
439 let anon = get_opt_str_param(params, "anonSupport");
440 let desc = get_opt_str_param(params, "description");
441
442 crate::functions::comparison::add_comparison_function(
443 config,
444 func,
445 crate::functions::comparison::AddComparisonFunctionParams {
446 connect_str: connect,
447 description: desc,
448 language: None,
449 anon_support: anon,
450 },
451 )
452 .map(|(cfg, _)| cfg)
453 }
454
455 "addExpressionFunction" => {
457 let func = get_str_param(params, "function")?;
458 let connect = get_str_param(params, "connectStr")?;
459 let desc = get_opt_str_param(params, "description");
460 let language = get_opt_str_param(params, "language");
461
462 crate::functions::expression::add_expression_function(
463 config,
464 func,
465 crate::functions::expression::AddExpressionFunctionParams {
466 connect_str: connect,
467 description: desc,
468 language,
469 },
470 )
471 .map(|(cfg, _)| cfg)
472 }
473
474 "addComparisonThreshold" => {
476 let func = get_str_param(params, "function")?;
477 let feature = get_str_param(params, "feature")?;
478 let score_name = get_str_param(params, "scoreName")?;
479 let same = params.get("sameScore").and_then(|v| v.as_i64());
480 let close = params.get("closeScore").and_then(|v| v.as_i64());
481 let likely = params.get("likelyScore").and_then(|v| v.as_i64());
482 let plausible = params.get("plausibleScore").and_then(|v| v.as_i64());
483 let unlikely = params.get("unlikelyScore").and_then(|v| v.as_i64());
484
485 crate::thresholds::add_comparison_threshold(
486 config,
487 crate::thresholds::AddComparisonThresholdParams {
488 cfunc_code: Some(func),
489 ftype_code: if feature.eq_ignore_ascii_case("ALL") {
490 None
491 } else {
492 Some(feature)
493 },
494 cfunc_rtnval: Some(score_name),
495 exec_order: None,
496 same_score: same,
497 close_score: close,
498 likely_score: likely,
499 plausible_score: plausible,
500 un_likely_score: unlikely,
501 },
502 )
503 }
504
505 "addGenericThreshold" => {
506 let threshold_params = crate::thresholds::AddGenericThresholdParams::try_from(params)?;
507 crate::thresholds::add_generic_threshold(config, threshold_params)
508 }
509
510 "addExpressionCall" => {
512 let feature = get_str_param(params, "feature")?;
513 let function = get_str_param(params, "function")?;
514 let exec_order = params.get("execOrder").and_then(|v| v.as_i64());
515 let expr_feature = get_opt_str_param(params, "expressionFeature");
516 let virtual_flag = get_opt_str_param(params, "virtual").unwrap_or("No");
517 let element_list_json = params
518 .get("elementList")
519 .ok_or_else(|| SzConfigError::MissingField("elementList".to_string()))?;
520
521 let element_list = parse_element_list(element_list_json)?;
523
524 let call_params = crate::calls::expression::AddExpressionCallParams {
525 efunc_code: function,
526 element_list,
527 ftype_code: Some(feature),
528 felem_code: None,
529 exec_order,
530 expression_feature: expr_feature,
531 is_virtual: virtual_flag,
532 };
533
534 let (new_config, _) =
535 crate::calls::expression::add_expression_call(config, call_params)?;
536
537 Ok(new_config)
538 }
539
540 "deleteComparisonCallElement" => {
542 let feature = get_str_param(params, "feature")?;
543 let element = get_str_param(params, "element")?;
544
545 let ftype_id = crate::helpers::lookup_feature_id(config, feature)?;
547 let felem_id = crate::helpers::lookup_element_id(config, element)?;
548
549 let config_val: Value = serde_json::from_str(config)?;
551 let cfcall_array = config_val["G2_CONFIG"]["CFG_CFCALL"]
552 .as_array()
553 .ok_or_else(|| SzConfigError::MissingSection("CFG_CFCALL".to_string()))?;
554
555 let cfcall = cfcall_array
557 .iter()
558 .find(|call| call["FTYPE_ID"].as_i64() == Some(ftype_id))
559 .ok_or_else(|| {
560 SzConfigError::NotFound(format!(
561 "No comparison call found for feature {feature}"
562 ))
563 })?;
564
565 let cfcall_id = cfcall["CFCALL_ID"]
566 .as_i64()
567 .ok_or_else(|| SzConfigError::InvalidStructure("CFCALL_ID missing".to_string()))?;
568
569 let cfbom_array = config_val["G2_CONFIG"]["CFG_CFBOM"]
571 .as_array()
572 .ok_or_else(|| SzConfigError::MissingSection("CFG_CFBOM".to_string()))?;
573
574 let cfbom = cfbom_array
575 .iter()
576 .find(|bom| {
577 bom["CFCALL_ID"].as_i64() == Some(cfcall_id)
578 && bom["FTYPE_ID"].as_i64() == Some(ftype_id)
579 && bom["FELEM_ID"].as_i64() == Some(felem_id)
580 })
581 .ok_or_else(|| {
582 SzConfigError::NotFound(format!(
583 "Element {element} not found in comparison call for feature {feature}"
584 ))
585 })?;
586
587 let exec_order = cfbom["EXEC_ORDER"]
588 .as_i64()
589 .ok_or_else(|| SzConfigError::InvalidStructure("EXEC_ORDER missing".to_string()))?;
590
591 crate::calls::comparison::delete_comparison_call_element(
593 config,
594 cfcall_id,
595 crate::calls::comparison::DeleteComparisonCallElementParams {
596 ftype_id,
597 felem_id,
598 exec_order,
599 },
600 )
601 }
602
603 "addComparisonCallElement" => {
604 let feature = get_str_param(params, "feature")?;
605 let element = get_str_param(params, "element")?;
606
607 let ftype_id = crate::helpers::lookup_feature_id(config, feature)?;
609 let felem_id = crate::helpers::lookup_element_id(config, element)?;
610
611 let config_val: Value = serde_json::from_str(config)?;
613 let cfcall_array = config_val["G2_CONFIG"]["CFG_CFCALL"]
614 .as_array()
615 .ok_or_else(|| SzConfigError::MissingSection("CFG_CFCALL".to_string()))?;
616
617 let cfcall = cfcall_array
618 .iter()
619 .find(|call| call["FTYPE_ID"].as_i64() == Some(ftype_id))
620 .ok_or_else(|| {
621 SzConfigError::NotFound(format!(
622 "No comparison call found for feature {feature}"
623 ))
624 })?;
625
626 let cfcall_id = cfcall["CFCALL_ID"]
627 .as_i64()
628 .ok_or_else(|| SzConfigError::InvalidStructure("CFCALL_ID missing".to_string()))?;
629
630 let cfbom_array = config_val["G2_CONFIG"]["CFG_CFBOM"]
632 .as_array()
633 .ok_or_else(|| SzConfigError::MissingSection("CFG_CFBOM".to_string()))?;
634
635 let next_order = cfbom_array
636 .iter()
637 .filter(|bom| {
638 bom["CFCALL_ID"].as_i64() == Some(cfcall_id)
639 && bom["FTYPE_ID"].as_i64() == Some(ftype_id)
640 })
641 .filter_map(|bom| bom["EXEC_ORDER"].as_i64())
642 .max()
643 .unwrap_or(0)
644 + 1;
645
646 crate::calls::comparison::add_comparison_call_element(
648 config,
649 crate::calls::comparison::AddComparisonCallElementParams {
650 cfcall_id,
651 ftype_id,
652 felem_id,
653 exec_order: next_order,
654 },
655 )
656 .map(|(cfg, _)| cfg)
657 }
658
659 "deleteDistinctCallElement" => {
661 let feature = get_str_param(params, "feature")?;
662 let element = get_str_param(params, "element")?;
663
664 let ftype_id = crate::helpers::lookup_feature_id(config, feature)?;
666 let felem_id = crate::helpers::lookup_element_id(config, element)?;
667
668 let config_val: Value = serde_json::from_str(config)?;
670 let dfcall_array = config_val["G2_CONFIG"]["CFG_DFCALL"]
671 .as_array()
672 .ok_or_else(|| SzConfigError::MissingSection("CFG_DFCALL".to_string()))?;
673
674 let dfcall = dfcall_array
675 .iter()
676 .find(|call| call["FTYPE_ID"].as_i64() == Some(ftype_id))
677 .ok_or_else(|| {
678 SzConfigError::NotFound(format!("No distinct call found for feature {feature}"))
679 })?;
680
681 let dfcall_id = dfcall["DFCALL_ID"]
682 .as_i64()
683 .ok_or_else(|| SzConfigError::InvalidStructure("DFCALL_ID missing".to_string()))?;
684
685 let dfbom_array = config_val["G2_CONFIG"]["CFG_DFBOM"]
687 .as_array()
688 .ok_or_else(|| SzConfigError::MissingSection("CFG_DFBOM".to_string()))?;
689
690 let dfbom = dfbom_array
691 .iter()
692 .find(|bom| {
693 bom["DFCALL_ID"].as_i64() == Some(dfcall_id)
694 && bom["FTYPE_ID"].as_i64() == Some(ftype_id)
695 && bom["FELEM_ID"].as_i64() == Some(felem_id)
696 })
697 .ok_or_else(|| {
698 SzConfigError::NotFound(format!(
699 "Element {element} not found in distinct call for feature {feature}"
700 ))
701 })?;
702
703 let exec_order = dfbom["EXEC_ORDER"]
704 .as_i64()
705 .ok_or_else(|| SzConfigError::InvalidStructure("EXEC_ORDER missing".to_string()))?;
706
707 crate::calls::distinct::delete_distinct_call_element(
709 config,
710 crate::calls::distinct::DeleteDistinctCallElementParams {
711 dfcall_id,
712 ftype_id,
713 felem_id,
714 exec_order,
715 },
716 )
717 }
718
719 "save" => Ok(config.to_string()),
721
722 _ => Err(SzConfigError::InvalidInput(format!(
724 "Unknown command: '{cmd}'"
725 ))),
726 }
727}
728
729fn get_str_param<'a>(params: &'a Value, key: &str) -> Result<&'a str> {
733 params[key]
734 .as_str()
735 .ok_or_else(|| SzConfigError::MissingField(key.to_string()))
736}
737
738fn get_opt_str_param<'a>(params: &'a Value, key: &str) -> Option<&'a str> {
740 params.get(key).and_then(|v| v.as_str())
741}
742
743fn parse_element_list(list: &Value) -> Result<Vec<(String, String, Option<String>)>> {
745 let arr = list
746 .as_array()
747 .ok_or_else(|| SzConfigError::InvalidInput("elementList must be array".to_string()))?;
748
749 arr.iter()
750 .map(|item| {
751 let element = item["element"]
752 .as_str()
753 .ok_or_else(|| SzConfigError::MissingField("element".to_string()))?
754 .to_string();
755 let required = item
756 .get("required")
757 .and_then(|v| v.as_str())
758 .unwrap_or("No")
759 .to_string();
760 let feature = item
761 .get("feature")
762 .and_then(|v| v.as_str())
763 .map(|s| s.to_string());
764
765 Ok((element, required, feature))
766 })
767 .collect()
768}
769
770#[cfg(test)]
771mod tests {
772 use super::*;
773
774 const TEST_CONFIG: &str = r#"{
775 "G2_CONFIG": {
776 "CFG_DSRC": [],
777 "CFG_ATTR": [],
778 "CFG_FTYPE": [],
779 "CFG_FELEM": [],
780 "CFG_FCLASS": [
781 {"FCLASS_ID": 1, "FCLASS_CODE": "OTHER"}
782 ],
783 "CFG_FBOVR": [],
784 "CFG_ERFRAG": [],
785 "CONFIG_BASE_VERSION": {
786 "VERSION": "4.0.0",
787 "BUILD_VERSION": "4.0.0.0",
788 "BUILD_DATE": "2024-01-01",
789 "COMPATIBILITY_VERSION": {
790 "CONFIG_VERSION": "10"
791 }
792 }
793 }
794}"#;
795
796 #[test]
797 fn test_parse_command_line() {
798 let (cmd, params) =
799 parse_command_line(r#"addAttribute {"attribute": "TEST", "class": "OTHER"}"#)
800 .expect("Failed to parse");
801
802 assert_eq!(cmd, "addAttribute");
803 assert_eq!(params["attribute"], "TEST");
804 assert_eq!(params["class"], "OTHER");
805 }
806
807 #[test]
808 fn test_parse_command_line_no_params() {
809 let (cmd, params) = parse_command_line("save").expect("Failed to parse");
810
811 assert_eq!(cmd, "save");
812 assert!(params.is_null());
813 }
814
815 #[test]
816 fn test_command_processor_simple_script() {
817 let script = r#"
818verifyCompatibilityVersion {"expectedVersion": "10"}
819updateCompatibilityVersion {"fromVersion": "10", "toVersion": "11"}
820save
821"#;
822
823 let mut processor = CommandProcessor::new(TEST_CONFIG.to_string());
824 let result = processor.process_script(script);
825
826 assert!(result.is_ok());
827 assert_eq!(processor.commands_executed.len(), 2); let config: Value = serde_json::from_str(&result.unwrap()).unwrap();
830 assert_eq!(
831 config["G2_CONFIG"]["CONFIG_BASE_VERSION"]["COMPATIBILITY_VERSION"]["CONFIG_VERSION"],
832 "11"
833 );
834 }
835
836 #[test]
837 fn test_command_processor_dry_run() {
838 let script = r#"updateCompatibilityVersion {"fromVersion": "10", "toVersion": "11"}"#;
839
840 let mut processor = CommandProcessor::new(TEST_CONFIG.to_string()).dry_run(true);
841 let result = processor.process_script(script);
842
843 assert!(result.is_ok());
844 assert_eq!(processor.commands_executed.len(), 1);
845
846 let config: Value = serde_json::from_str(&result.unwrap()).unwrap();
848 assert_eq!(
849 config["G2_CONFIG"]["CONFIG_BASE_VERSION"]["COMPATIBILITY_VERSION"]["CONFIG_VERSION"],
850 "10"
851 );
852 }
853
854 #[test]
855 fn test_command_processor_invalid_command() {
856 let script = r#"unknownCommand {"param": "value"}"#;
857
858 let mut processor = CommandProcessor::new(TEST_CONFIG.to_string());
859 let result = processor.process_script(script);
860
861 assert!(result.is_err());
862 assert!(result.unwrap_err().to_string().contains("Unknown command"));
863 }
864
865 #[test]
866 fn test_command_processor_invalid_json() {
867 let script = r#"addAttribute {invalid json}"#;
868
869 let mut processor = CommandProcessor::new(TEST_CONFIG.to_string());
870 let result = processor.process_script(script);
871
872 assert!(result.is_err());
873 assert!(result.unwrap_err().to_string().contains("Invalid JSON"));
874 }
875
876 #[test]
877 fn test_execute_add_behavior_override() {
878 let config_with_feature = r#"{
879 "G2_CONFIG": {
880 "CFG_FTYPE": [
881 {"FTYPE_ID": 1, "FTYPE_CODE": "TEST"}
882 ],
883 "CFG_FBOVR": []
884 }
885}"#;
886
887 let params = serde_json::json!({
888 "feature": "TEST",
889 "usageType": "BUSINESS",
890 "behavior": "F1E"
891 });
892
893 let result = execute_command(config_with_feature, "addBehaviorOverride", ¶ms);
894 assert!(result.is_ok());
895
896 let config: Value = serde_json::from_str(&result.unwrap()).unwrap();
897 let overrides = &config["G2_CONFIG"]["CFG_FBOVR"];
898 assert_eq!(overrides.as_array().unwrap().len(), 1);
899 assert_eq!(overrides[0]["UTYPE_CODE"], "BUSINESS");
900 }
901}