1use crate::Result;
2use crate::UptrakitClient;
3use crate::types_impl::batch_actions::{BatchActionRequest, BatchActionResponse};
4use crate::types_impl::pagination::{PaginatedResponse, PaginationParams};
5use crate::types_impl::software_items::{
6 AssignHostsRequest, CreateSoftwareItemRequest, ListSoftwareItemsParams,
7 MergeSoftwareItemsExecuteRequest, MergeSoftwareItemsExecuteResponse,
8 MergeSoftwareItemsPreviewRequest, MergeSoftwareItemsPreviewResponse,
9 SoftwareItemDetailResponse, SoftwareItemResponse, TriggerUpdateRequest, TriggerUpdateResponse,
10 TriggerVersionCheckResponse, UpdateHostAssignmentRequest, UpdateSoftwareItemRequest,
11};
12use uuid::Uuid;
13
14impl UptrakitClient {
15 pub async fn list_software_items(
17 &self,
18 params: &ListSoftwareItemsParams,
19 ) -> Result<PaginatedResponse<SoftwareItemResponse>> {
20 self.get_with_query(crate::paths::software_items::BASE, params)
21 .await
22 }
23
24 pub async fn preview_software_item_merge(
26 &self,
27 req: &MergeSoftwareItemsPreviewRequest,
28 ) -> Result<MergeSoftwareItemsPreviewResponse> {
29 self.post_json(crate::paths::software_items::MERGE_PREVIEW, req)
30 .await
31 }
32
33 pub async fn execute_software_item_merge(
35 &self,
36 req: &MergeSoftwareItemsExecuteRequest,
37 ) -> Result<MergeSoftwareItemsExecuteResponse> {
38 self.post_json(crate::paths::software_items::MERGE_EXECUTE, req)
39 .await
40 }
41
42 pub async fn list_all_software_items(&self) -> Result<Vec<SoftwareItemResponse>> {
50 let base = PaginationParams {
51 page: None,
52 per_page: None,
53 };
54 self.fetch_all_pages(crate::paths::software_items::BASE, &base)
55 .await
56 }
57
58 pub async fn get_software_item(&self, id: &Uuid) -> Result<SoftwareItemDetailResponse> {
60 self.get(&crate::paths::software_items::by_id(id)).await
61 }
62
63 pub async fn create_software_item(
65 &self,
66 req: &CreateSoftwareItemRequest,
67 ) -> Result<SoftwareItemResponse> {
68 self.post_json(crate::paths::software_items::BASE, req)
69 .await
70 }
71
72 pub async fn update_software_item(
74 &self,
75 id: &Uuid,
76 req: &UpdateSoftwareItemRequest,
77 ) -> Result<SoftwareItemResponse> {
78 self.put_json(&crate::paths::software_items::by_id(id), req)
79 .await
80 }
81
82 pub async fn delete_software_item(&self, id: &Uuid) -> Result<()> {
84 self.delete(&crate::paths::software_items::by_id(id)).await
85 }
86
87 pub async fn assign_hosts(
92 &self,
93 id: &Uuid,
94 req: &AssignHostsRequest,
95 ) -> Result<SoftwareItemDetailResponse> {
96 self.post_json(&crate::paths::software_items::hosts(id), req)
97 .await
98 }
99
100 pub async fn unassign_host(&self, item_id: &Uuid, host_id: &Uuid) -> Result<()> {
102 self.delete(&crate::paths::software_items::host(item_id, host_id))
103 .await
104 }
105
106 pub async fn unassign_host_with_ignore(&self, item_id: &Uuid, host_id: &Uuid) -> Result<()> {
112 #[derive(serde::Serialize)]
113 struct IgnoreQuery {
114 ignore: bool,
115 }
116 self.delete_with_query(
117 &crate::paths::software_items::host(item_id, host_id),
118 &IgnoreQuery { ignore: true },
119 )
120 .await
121 }
122
123 pub async fn update_host_assignment(
125 &self,
126 item_id: &Uuid,
127 host_id: &Uuid,
128 req: &UpdateHostAssignmentRequest,
129 ) -> Result<SoftwareItemDetailResponse> {
130 self.put_json(&crate::paths::software_items::host(item_id, host_id), req)
131 .await
132 }
133
134 pub async fn delete_plugin_assignment(
136 &self,
137 item_id: &Uuid,
138 host_id: &Uuid,
139 role: &str,
140 ordinal: i32,
141 ) -> Result<SoftwareItemDetailResponse> {
142 self.delete_json(&crate::paths::software_items::host_plugin_assignment(
143 item_id, host_id, role, ordinal,
144 ))
145 .await
146 }
147
148 pub async fn check_versions(&self, item_id: &Uuid) -> Result<TriggerVersionCheckResponse> {
150 self.post_empty(&crate::paths::software_items::check_versions(item_id))
151 .await
152 }
153
154 pub async fn check_versions_host(
156 &self,
157 item_id: &Uuid,
158 host_id: &Uuid,
159 ) -> Result<TriggerVersionCheckResponse> {
160 self.post_empty(&crate::paths::software_items::host_check_versions(
161 item_id, host_id,
162 ))
163 .await
164 }
165
166 pub async fn trigger_update(
168 &self,
169 item_id: &Uuid,
170 host_id: &Uuid,
171 req: &TriggerUpdateRequest,
172 ) -> Result<TriggerUpdateResponse> {
173 self.post_json(
174 &crate::paths::software_items::host_update(item_id, host_id),
175 req,
176 )
177 .await
178 }
179
180 pub async fn batch_software_items(
184 &self,
185 req: &BatchActionRequest,
186 ) -> Result<BatchActionResponse> {
187 self.post_json(crate::paths::software_items::BATCH, req)
188 .await
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use crate::shared_types_impl::PluginRole;
195 use crate::types_impl::software_items::{
196 AssignHostsRequest, CreateSoftwareItemRequest, HostPluginRoleAssignment,
197 HostSoftwareAssignment, ReleaseInfoRequest, TriggerUpdateRequest,
198 UpdateHostAssignmentRequest, UpdateSoftwareItemRequest,
199 };
200 use uuid::Uuid;
201
202 #[test]
203 fn create_software_item_request_serialization() {
204 let req = CreateSoftwareItemRequest {
205 name: "Node.js".to_string(),
206 featured: true,
207 icon_url: None,
208 };
209 let json = serde_json::to_value(&req).expect("serialize");
210 assert_eq!(json["name"], "Node.js");
211 assert_eq!(json["featured"], true);
212 assert!(json.get("provider_config_id").is_none());
214 assert!(json.get("package_identifier").is_none());
215 }
216
217 #[test]
218 fn update_software_item_request_serialization() {
219 use crate::types_impl::software_items::IconUrlPatch;
220 let req = UpdateSoftwareItemRequest {
221 name: Some("Node.js LTS".to_string()),
222 featured: Some(false),
223 icon_url: IconUrlPatch::Keep,
224 };
225 let json = serde_json::to_value(&req).expect("serialize");
226 assert_eq!(json["name"], "Node.js LTS");
227 assert_eq!(json["featured"], false);
228 assert!(json.get("package_identifier").is_none());
230 assert!(json.get("config_override").is_none());
231 }
232
233 #[test]
234 fn assign_hosts_request_serialization() {
235 let pc_id = Uuid::parse_str("a1a2a3a4-b1b2-c1c2-d1d2-e1e2e3e4e5e6").expect("valid uuid");
236 let host1 = Uuid::parse_str("11111111-1111-1111-1111-111111111111").expect("valid uuid");
237 let host2 = Uuid::parse_str("22222222-2222-2222-2222-222222222222").expect("valid uuid");
238
239 let req = AssignHostsRequest {
240 host_assignments: vec![
241 HostSoftwareAssignment {
242 host_id: host1,
243 plugins: vec![HostPluginRoleAssignment {
244 role: PluginRole::DetectVersion,
245 ordinal: 0,
246 plugin_config_id: Some(pc_id),
247 plugin_config: None,
248 package_identifier: "nodejs/node".to_string(),
249 config_override: None,
250 execution_site: "auto".to_string(),
251 }],
252 },
253 HostSoftwareAssignment {
254 host_id: host2,
255 plugins: vec![HostPluginRoleAssignment {
256 role: PluginRole::DetectVersion,
257 ordinal: 0,
258 plugin_config_id: Some(pc_id),
259 plugin_config: None,
260 package_identifier: "nodejs/node".to_string(),
261 config_override: None,
262 execution_site: "auto".to_string(),
263 }],
264 },
265 ],
266 };
267 let json = serde_json::to_value(&req).expect("serialize");
268 let assignments = json["host_assignments"].as_array().expect("array");
269 assert_eq!(assignments.len(), 2);
270 assert_eq!(
271 assignments[0]["host_id"],
272 "11111111-1111-1111-1111-111111111111"
273 );
274 let plugins = assignments[0]["plugins"].as_array().expect("plugins array");
275 assert_eq!(plugins.len(), 1);
276 assert_eq!(
277 plugins[0]["plugin_config_id"],
278 "a1a2a3a4-b1b2-c1c2-d1d2-e1e2e3e4e5e6"
279 );
280 assert_eq!(plugins[0]["package_identifier"], "nodejs/node");
281 assert_eq!(plugins[0]["role"], "detect_version");
282 }
283
284 #[test]
285 fn update_host_assignment_request_serialization() {
286 let pc_id = Uuid::parse_str("a1a2a3a4-b1b2-c1c2-d1d2-e1e2e3e4e5e6").expect("valid uuid");
287 use crate::types_impl::software_items::JsonObjectMapPatch;
288 let req = UpdateHostAssignmentRequest {
289 role: PluginRole::ExecuteUpdate,
290 ordinal: 0,
291 plugin_config_id: Some(pc_id),
292 plugin_config: None,
293 plugin_type: None,
294 package_identifier: Some("homebrew/cask/firefox".to_string()),
295 config_override: JsonObjectMapPatch::Keep,
296 execution_site: None,
297 };
298 let json = serde_json::to_value(&req).expect("serialize");
299 assert_eq!(
300 json["plugin_config_id"],
301 "a1a2a3a4-b1b2-c1c2-d1d2-e1e2e3e4e5e6"
302 );
303 assert_eq!(json["package_identifier"], "homebrew/cask/firefox");
304 assert_eq!(json["role"], "execute_update");
305 }
306
307 #[test]
308 fn trigger_update_request_without_release_info() {
309 let req = TriggerUpdateRequest {
310 to_version: "2.0.0".to_string(),
311 release_info: None,
312 interactive: false,
313 };
314 let json = serde_json::to_value(&req).expect("serialize");
315 assert_eq!(json["to_version"], "2.0.0");
316 assert!(json["release_info"].is_null());
317 }
318
319 #[test]
320 fn trigger_update_request_with_release_info() {
321 let req = TriggerUpdateRequest {
322 to_version: "2.0.0".to_string(),
323 release_info: Some(ReleaseInfoRequest {
324 tag: "v2.0.0".to_string(),
325 release_url: "https://example.com/releases/v2.0.0".to_string(),
326 assets: vec![],
327 }),
328 interactive: false,
329 };
330 let json = serde_json::to_value(&req).expect("serialize");
331 assert_eq!(json["to_version"], "2.0.0");
332 assert_eq!(json["release_info"]["tag"], "v2.0.0");
333 }
334}