omni_dev/atlassian/
transition_fields.rs1use std::collections::BTreeMap;
11
12use anyhow::{bail, Result};
13
14use crate::atlassian::custom_fields::resolve_custom_fields;
15use crate::atlassian::jira_types::EditMeta;
16
17#[derive(Debug)]
19pub struct ResolvedTransitionFields {
20 pub fields: BTreeMap<String, serde_json::Value>,
23
24 pub comment_on_screen: bool,
28}
29
30pub fn resolve_transition_fields(
40 scalars: &BTreeMap<String, serde_yaml::Value>,
41 resolution: Option<&str>,
42 editmeta: &EditMeta,
43) -> Result<ResolvedTransitionFields> {
44 let mut fields = resolve_custom_fields(scalars, &[], editmeta)?;
45
46 if let Some(value) = resolution {
47 if fields.contains_key("resolution") {
48 bail!("`--resolution` collides with `--set-field resolution=…`; supply only one");
49 }
50 fields.insert(
51 "resolution".to_string(),
52 serde_json::json!({ "name": value }),
53 );
54 }
55
56 let comment_on_screen = editmeta.fields.contains_key("comment");
57
58 Ok(ResolvedTransitionFields {
59 fields,
60 comment_on_screen,
61 })
62}
63
64#[cfg(test)]
65#[allow(clippy::unwrap_used, clippy::expect_used)]
66mod tests {
67 use super::*;
68 use crate::atlassian::jira_types::{EditMetaField, EditMetaSchema};
69
70 fn meta(entries: &[(&str, &str, &str, Option<&str>)]) -> EditMeta {
72 let mut fields = BTreeMap::new();
73 for (id, name, kind, custom) in entries {
74 fields.insert(
75 (*id).to_string(),
76 EditMetaField {
77 name: (*name).to_string(),
78 schema: EditMetaSchema {
79 kind: (*kind).to_string(),
80 custom: custom.map(str::to_string),
81 ..EditMetaSchema::default()
82 },
83 allowed_values: Vec::new(),
84 },
85 );
86 }
87 EditMeta { fields }
88 }
89
90 fn scalar(name: &str, value: &str) -> BTreeMap<String, serde_yaml::Value> {
91 let mut m = BTreeMap::new();
92 m.insert(
93 name.to_string(),
94 serde_yaml::Value::String(value.to_string()),
95 );
96 m
97 }
98
99 #[test]
100 fn resolution_becomes_name_shape() {
101 let editmeta = meta(&[("resolution", "Resolution", "resolution", None)]);
102 let resolved =
103 resolve_transition_fields(&BTreeMap::new(), Some("Fixed"), &editmeta).unwrap();
104 assert_eq!(
105 resolved.fields.get("resolution"),
106 Some(&serde_json::json!({ "name": "Fixed" }))
107 );
108 }
109
110 #[test]
111 fn set_field_resolves_against_screen_metadata() {
112 let editmeta = meta(&[("customfield_100", "Reason", "string", None)]);
114 let resolved =
115 resolve_transition_fields(&scalar("Reason", "because"), None, &editmeta).unwrap();
116 assert_eq!(
117 resolved.fields.get("customfield_100"),
118 Some(&serde_json::json!("because"))
119 );
120 }
121
122 #[test]
123 fn resolution_collides_with_set_field_resolution() {
124 let editmeta = meta(&[("resolution", "Resolution", "option", None)]);
129 let err =
130 resolve_transition_fields(&scalar("resolution", "Done"), Some("Fixed"), &editmeta)
131 .unwrap_err();
132 assert!(err.to_string().contains("collides"));
133 }
134
135 #[test]
136 fn comment_on_screen_detected() {
137 let with = meta(&[("comment", "Comment", "comment", None)]);
138 let without = meta(&[("resolution", "Resolution", "resolution", None)]);
139 assert!(
140 resolve_transition_fields(&BTreeMap::new(), None, &with)
141 .unwrap()
142 .comment_on_screen
143 );
144 assert!(
145 !resolve_transition_fields(&BTreeMap::new(), None, &without)
146 .unwrap()
147 .comment_on_screen
148 );
149 }
150
151 #[test]
152 fn empty_inputs_yield_empty_fields() {
153 let editmeta = meta(&[("resolution", "Resolution", "resolution", None)]);
154 let resolved = resolve_transition_fields(&BTreeMap::new(), None, &editmeta).unwrap();
155 assert!(resolved.fields.is_empty());
156 }
157
158 #[test]
159 fn unknown_set_field_errors_with_candidates() {
160 let editmeta = meta(&[("customfield_100", "Reason", "string", None)]);
161 let err =
162 resolve_transition_fields(&scalar("Nonexistent", "x"), None, &editmeta).unwrap_err();
163 let msg = err.to_string();
164 assert!(msg.contains("Nonexistent"));
165 assert!(msg.contains("Reason"));
166 }
167}