1pub use uptrakit_shared_types::SecretString;
4
5pub use uptrakit_shared_types::PluginRole;
8
9pub mod access_presets;
10pub mod agents;
11pub mod api_tokens;
12pub mod audit_logs;
13pub mod auth;
14pub mod autodiscovery;
15pub mod batch_actions;
16pub mod command_validation;
17pub mod device_auth;
18pub mod discovery_allowlist;
19pub mod enrollment_tokens;
20pub mod error;
21pub mod events;
22pub mod host_tags;
23pub mod hosts;
24pub mod masked_url;
25pub mod notifications;
26pub mod oidc_auth;
27pub mod oidc_providers;
28pub mod pagination;
29pub mod permissions;
30pub mod plugin_config_test;
31pub mod plugin_configs;
32pub mod plugin_type_settings;
33pub mod prelude;
34pub mod profile;
35pub mod registration;
36pub mod roles;
37pub mod scheduler;
38pub mod server_cert;
39pub mod services;
40pub mod settings;
41pub mod settings_agent_certs;
42pub mod settings_auth;
43pub mod settings_ca;
44pub mod settings_combined;
45pub mod settings_network;
46pub mod settings_reset;
47pub mod system_services;
48pub use masked_url::MaskedUrl;
49pub mod settings_nats;
50pub mod settings_provider_github;
51pub mod settings_zeroconf;
52pub mod software_items;
53pub mod surfaces;
54pub mod system_alerts;
55pub mod system_enrollment_tokens;
56pub mod update_batches;
57pub mod update_history;
58pub mod users;
59pub mod validation;
60
61pub fn default_enabled() -> bool {
66 true
67}
68
69pub fn default_featured() -> bool {
74 true
75}
76
77#[cfg(test)]
78mod tests {
79 use crate::auth::{AuthResponse, UserResponse};
80 use crate::device_auth::DeviceAuthPollResponse;
81 use crate::error::ErrorResponse;
82 use crate::oidc_providers::CreateOidcProviderRequest;
83 use crate::permissions::Permission;
84 use crate::plugin_configs::CreatePluginConfigRequest;
85 use crate::registration::RegistrationMode;
86 use crate::software_items::CreateSoftwareItemRequest;
87 use crate::update_history::UpdateStatus;
88 use strum::IntoEnumIterator;
89 use uptrakit_shared_types::{DeviceAuthStatus, SecretString};
90 use uuid::Uuid;
91
92 #[test]
95 fn permission_serde_round_trip() {
96 for perm in Permission::iter() {
97 let json = serde_json::to_string(&perm).unwrap();
98 let deserialized: Permission = serde_json::from_str(&json).unwrap();
99 assert_eq!(deserialized, perm);
100 }
101 }
102
103 #[test]
104 fn permission_as_str_values() {
105 assert_eq!(Permission::ViewSettings.as_str(), "view_settings");
106 assert_eq!(
107 Permission::ManageAuthSettings.as_str(),
108 "manage_auth_settings"
109 );
110 assert_eq!(Permission::ViewServices.as_str(), "view_services");
111 assert_eq!(Permission::UpdateServices.as_str(), "update_services");
112 assert_eq!(
113 Permission::ManageGlobalSettings.as_str(),
114 "manage_global_settings"
115 );
116 }
117
118 #[test]
119 fn permission_from_str_valid() {
120 assert_eq!(
121 "view_settings".parse::<Permission>().ok(),
122 Some(Permission::ViewSettings)
123 );
124 assert_eq!(
125 "manage_auth_settings".parse::<Permission>().ok(),
126 Some(Permission::ManageAuthSettings)
127 );
128 assert_eq!(
129 "view_services".parse::<Permission>().ok(),
130 Some(Permission::ViewServices)
131 );
132 assert_eq!(
133 "update_services".parse::<Permission>().ok(),
134 Some(Permission::UpdateServices)
135 );
136 assert_eq!(
137 "manage_global_settings".parse::<Permission>().ok(),
138 Some(Permission::ManageGlobalSettings)
139 );
140 }
141
142 #[test]
143 fn permission_from_str_invalid_returns_err() {
144 assert!("nonexistent".parse::<Permission>().is_err());
145 assert!("".parse::<Permission>().is_err());
146 assert!("VIEW_SETTINGS".parse::<Permission>().is_err());
147 }
148
149 #[test]
150 fn permission_display_matches_as_str() {
151 for perm in Permission::iter() {
152 assert_eq!(format!("{perm}"), perm.as_str());
153 }
154 }
155
156 #[test]
157 fn permission_iter_covers_all_variants() {
158 assert_eq!(Permission::iter().count(), 33);
159 }
160
161 #[test]
162 fn permission_as_str_round_trips_through_from_str() {
163 for perm in Permission::iter() {
164 let s = perm.as_str();
165 let parsed: Permission = s
166 .parse()
167 .expect("from_str should succeed for as_str output");
168 assert_eq!(parsed, perm);
169 }
170 }
171
172 #[test]
175 fn registration_mode_serde_round_trip() {
176 for mode in RegistrationMode::iter() {
177 let json = serde_json::to_string(&mode).unwrap();
178 let deserialized: RegistrationMode = serde_json::from_str(&json).unwrap();
179 assert_eq!(deserialized, mode);
180 }
181 }
182
183 #[test]
184 fn registration_mode_as_str_values() {
185 assert_eq!(RegistrationMode::Open.as_str(), "open");
186 assert_eq!(RegistrationMode::Invite.as_str(), "invite");
187 assert_eq!(RegistrationMode::Closed.as_str(), "closed");
188 }
189
190 #[test]
191 fn registration_mode_from_str_valid() {
192 assert_eq!(
193 "open".parse::<RegistrationMode>().ok(),
194 Some(RegistrationMode::Open)
195 );
196 assert_eq!(
197 "invite".parse::<RegistrationMode>().ok(),
198 Some(RegistrationMode::Invite)
199 );
200 assert_eq!(
201 "closed".parse::<RegistrationMode>().ok(),
202 Some(RegistrationMode::Closed)
203 );
204 }
205
206 #[test]
207 fn registration_mode_from_str_invalid_returns_none() {
208 assert!("disabled".parse::<RegistrationMode>().is_err());
209 assert!("".parse::<RegistrationMode>().is_err());
210 assert!("OPEN".parse::<RegistrationMode>().is_err());
211 }
212
213 #[test]
214 fn registration_mode_as_str_round_trips_through_from_str() {
215 for mode in RegistrationMode::iter() {
216 let s = mode.as_str();
217 let parsed: RegistrationMode = s
218 .parse()
219 .expect("from_str should succeed for as_str output");
220 assert_eq!(parsed, mode);
221 }
222 }
223
224 #[test]
225 fn registration_mode_display_matches_as_str() {
226 for mode in RegistrationMode::iter() {
227 assert_eq!(format!("{mode}"), mode.as_str());
228 }
229 }
230
231 #[test]
234 fn create_oidc_provider_request_default_scopes() {
235 let json = serde_json::json!({
236 "name": "Test Provider",
237 "slug": "test",
238 "issuer_url": "https://issuer.example.com",
239 "client_id": "client-id",
240 "client_secret": "client-secret"
241 });
242 let req: CreateOidcProviderRequest = serde_json::from_value(json).unwrap();
243 assert_eq!(req.scopes, "openid email profile groups");
244 }
245
246 #[test]
247 fn create_oidc_provider_request_default_auto_create() {
248 let json = serde_json::json!({
249 "name": "Test Provider",
250 "slug": "test",
251 "issuer_url": "https://issuer.example.com",
252 "client_id": "client-id",
253 "client_secret": "client-secret"
254 });
255 let req: CreateOidcProviderRequest = serde_json::from_value(json).unwrap();
256 assert!(req.auto_create_users);
257 }
258
259 #[test]
260 fn create_oidc_provider_request_explicit_overrides_defaults() {
261 let json = serde_json::json!({
262 "name": "Test Provider",
263 "slug": "test",
264 "issuer_url": "https://issuer.example.com",
265 "client_id": "client-id",
266 "client_secret": "client-secret",
267 "scopes": "openid",
268 "auto_create_users": false
269 });
270 let req: CreateOidcProviderRequest = serde_json::from_value(json).unwrap();
271 assert_eq!(req.scopes, "openid");
272 assert!(!req.auto_create_users);
273 }
274
275 #[test]
276 fn create_software_item_request_default_featured() {
277 let json = serde_json::json!({ "name": "Node.js" });
278 let req: CreateSoftwareItemRequest = serde_json::from_value(json).unwrap();
279 assert!(req.featured);
280 }
281
282 #[test]
283 fn create_software_item_request_explicit_featured_false() {
284 let json = serde_json::json!({
285 "name": "Node.js",
286 "featured": false
287 });
288 let req: CreateSoftwareItemRequest = serde_json::from_value(json).unwrap();
289 assert!(!req.featured);
290 }
291
292 #[test]
293 fn create_plugin_config_request_default_enabled() {
294 let json = serde_json::json!({
295 "name": "GitHub Releases",
296 "plugin_type": "releases_github",
297 "config": {}
298 });
299 let req: CreatePluginConfigRequest = serde_json::from_value(json).unwrap();
300 assert!(req.enabled);
301 }
302
303 #[test]
304 fn create_plugin_config_request_explicit_enabled_false() {
305 let json = serde_json::json!({
306 "name": "GitHub Releases",
307 "plugin_type": "releases_github",
308 "config": {},
309 "enabled": false
310 });
311 let req: CreatePluginConfigRequest = serde_json::from_value(json).unwrap();
312 assert!(!req.enabled);
313 }
314
315 #[test]
318 fn device_auth_poll_response_serializes_none_as_null() {
319 let resp = DeviceAuthPollResponse {
320 status: DeviceAuthStatus::Pending,
321 token: None,
322 token_name: None,
323 };
324 let json = serde_json::to_value(&resp).unwrap();
325 let obj = json.as_object().unwrap();
326
327 assert!(obj.contains_key("status"));
328 assert!(
329 obj.get("token").unwrap().is_null(),
330 "token should serialize as null when None"
331 );
332 assert!(
333 obj.get("token_name").unwrap().is_null(),
334 "token_name should serialize as null when None"
335 );
336 }
337
338 #[test]
339 fn device_auth_poll_response_includes_some_fields() {
340 let resp = DeviceAuthPollResponse {
341 status: DeviceAuthStatus::Authorized,
342 token: Some(SecretString::new("secret-token-value")),
343 token_name: Some("my-device".to_string()),
344 };
345 let json = serde_json::to_value(&resp).unwrap();
346 let obj = json.as_object().unwrap();
347
348 assert_eq!(obj.get("status").unwrap(), "authorized");
349 assert_eq!(obj.get("token").unwrap(), "secret-token-value");
350 assert_eq!(obj.get("token_name").unwrap(), "my-device");
351 }
352
353 #[test]
354 fn device_auth_poll_response_round_trip_with_none() {
355 let resp = DeviceAuthPollResponse {
356 status: DeviceAuthStatus::Pending,
357 token: None,
358 token_name: None,
359 };
360 let json = serde_json::to_string(&resp).unwrap();
361 let deserialized: DeviceAuthPollResponse = serde_json::from_str(&json).unwrap();
362 assert_eq!(deserialized.status, DeviceAuthStatus::Pending);
363 assert!(deserialized.token.is_none());
364 assert!(deserialized.token_name.is_none());
365 }
366
367 #[test]
368 fn device_auth_poll_response_round_trip_with_some() {
369 let resp = DeviceAuthPollResponse {
370 status: DeviceAuthStatus::Authorized,
371 token: Some(SecretString::new("tok")),
372 token_name: Some("dev".to_string()),
373 };
374 let json = serde_json::to_string(&resp).unwrap();
375 let deserialized: DeviceAuthPollResponse = serde_json::from_str(&json).unwrap();
376 assert_eq!(deserialized.status, DeviceAuthStatus::Authorized);
377 assert_eq!(
378 deserialized.token.as_ref().map(|t| t.expose_secret()),
379 Some("tok")
380 );
381 assert_eq!(deserialized.token_name.as_deref(), Some("dev"));
382 }
383
384 #[test]
387 fn user_response_round_trip() {
388 let user_id = Uuid::parse_str("00000000-0000-0000-0000-000000000001").expect("valid uuid");
389 let user = UserResponse {
390 id: user_id,
391 email: "owner@example.com".to_string(),
392 first_name: "Owner".to_string(),
393 last_name: "User".to_string(),
394 permissions: vec![
395 Permission::ViewSettings,
396 Permission::ManageAuthSettings,
397 Permission::ViewServices,
398 Permission::UpdateServices,
399 Permission::ManageGlobalSettings,
400 ],
401 has_pending_email_change: false,
402 };
403 let json = serde_json::to_string(&user).unwrap();
404 let deserialized: UserResponse = serde_json::from_str(&json).unwrap();
405
406 assert_eq!(deserialized.id, user_id);
407 assert_eq!(deserialized.email, "owner@example.com");
408 assert_eq!(deserialized.first_name, "Owner");
409 assert_eq!(deserialized.last_name, "User");
410 assert_eq!(deserialized.permissions.len(), 5);
411 assert_eq!(deserialized.permissions[0], Permission::ViewSettings);
412 assert_eq!(deserialized.permissions[1], Permission::ManageAuthSettings);
413 assert_eq!(deserialized.permissions[2], Permission::ViewServices);
414 assert_eq!(deserialized.permissions[3], Permission::UpdateServices);
415 assert_eq!(
416 deserialized.permissions[4],
417 Permission::ManageGlobalSettings
418 );
419 }
420
421 #[test]
422 fn user_response_empty_permissions() {
423 let user_id = Uuid::parse_str("00000000-0000-0000-0000-000000000002").expect("valid uuid");
424 let user = UserResponse {
425 id: user_id,
426 email: "viewer@example.com".to_string(),
427 first_name: "View".to_string(),
428 last_name: "Only".to_string(),
429 permissions: vec![],
430 has_pending_email_change: false,
431 };
432 let json = serde_json::to_string(&user).unwrap();
433 let deserialized: UserResponse = serde_json::from_str(&json).unwrap();
434
435 assert!(deserialized.permissions.is_empty());
436 }
437
438 #[test]
439 fn auth_response_round_trip() {
440 let user_id = Uuid::parse_str("00000000-0000-0000-0000-000000000001").expect("valid uuid");
441 let auth = AuthResponse {
442 access_token: SecretString::new("eyJhbGciOiJIUzI1NiJ9.test"),
443 refresh_token: SecretString::new("refresh-token-value"),
444 expires_in: 3600,
445 token_type: "Bearer".to_string(),
446 user: UserResponse {
447 id: user_id,
448 email: "admin@example.com".to_string(),
449 first_name: "Admin".to_string(),
450 last_name: "User".to_string(),
451 permissions: vec![Permission::ViewSettings, Permission::UpdateServices],
452 has_pending_email_change: false,
453 },
454 };
455 let json = serde_json::to_string(&auth).unwrap();
456 let deserialized: AuthResponse = serde_json::from_str(&json).unwrap();
457
458 assert_eq!(
459 deserialized.access_token.expose_secret(),
460 "eyJhbGciOiJIUzI1NiJ9.test"
461 );
462 assert_eq!(
463 deserialized.refresh_token.expose_secret(),
464 "refresh-token-value"
465 );
466 assert_eq!(deserialized.expires_in, 3600);
467 assert_eq!(deserialized.token_type, "Bearer");
468 assert_eq!(deserialized.user.id, user_id);
469 assert_eq!(deserialized.user.email, "admin@example.com");
470 assert_eq!(deserialized.user.permissions.len(), 2);
471 assert_eq!(deserialized.user.permissions[0], Permission::ViewSettings);
472 assert_eq!(deserialized.user.permissions[1], Permission::UpdateServices);
473 }
474
475 #[test]
478 fn update_status_serde_round_trip() {
479 for status in UpdateStatus::iter() {
480 let json = serde_json::to_string(&status).unwrap();
481 let deserialized: UpdateStatus = serde_json::from_str(&json).unwrap();
482 assert_eq!(deserialized, status);
483 }
484 }
485
486 #[test]
487 fn update_status_as_str_values() {
488 assert_eq!(UpdateStatus::Queued.as_str(), "queued");
489 assert_eq!(UpdateStatus::Pending.as_str(), "pending");
490 assert_eq!(UpdateStatus::InProgress.as_str(), "in_progress");
491 assert_eq!(UpdateStatus::Completed.as_str(), "completed");
492 assert_eq!(UpdateStatus::Failed.as_str(), "failed");
493 }
494
495 #[test]
496 fn update_status_from_str_valid() {
497 assert_eq!(
498 "queued".parse::<UpdateStatus>().ok(),
499 Some(UpdateStatus::Queued)
500 );
501 assert_eq!(
502 "pending".parse::<UpdateStatus>().ok(),
503 Some(UpdateStatus::Pending)
504 );
505 assert_eq!(
506 "in_progress".parse::<UpdateStatus>().ok(),
507 Some(UpdateStatus::InProgress)
508 );
509 assert_eq!(
510 "completed".parse::<UpdateStatus>().ok(),
511 Some(UpdateStatus::Completed)
512 );
513 assert_eq!(
514 "failed".parse::<UpdateStatus>().ok(),
515 Some(UpdateStatus::Failed)
516 );
517 }
518
519 #[test]
520 fn update_status_from_str_invalid_returns_none() {
521 assert!("unknown".parse::<UpdateStatus>().is_err());
522 assert!("".parse::<UpdateStatus>().is_err());
523 assert!("PENDING".parse::<UpdateStatus>().is_err());
524 }
525
526 #[test]
527 fn update_status_as_str_round_trips_through_from_str() {
528 for status in UpdateStatus::iter() {
529 let s = status.as_str();
530 let parsed: UpdateStatus = s
531 .parse()
532 .expect("from_str should succeed for as_str output");
533 assert_eq!(parsed, status);
534 }
535 }
536
537 #[test]
538 fn update_status_display_matches_as_str() {
539 for status in UpdateStatus::iter() {
540 assert_eq!(format!("{status}"), status.as_str());
541 }
542 }
543
544 #[test]
547 fn error_response_serialization_without_code() {
548 let resp = ErrorResponse {
549 error: "Something went wrong".to_string(),
550 code: None,
551 };
552 let json = serde_json::to_value(&resp).unwrap();
553 let obj = json.as_object().unwrap();
554
555 assert_eq!(obj.get("error").unwrap(), "Something went wrong");
556 assert!(
557 obj.get("code").unwrap().is_null(),
558 "code should serialize as null when None"
559 );
560 }
561
562 #[test]
563 fn error_response_serialization_with_code() {
564 let resp = ErrorResponse {
565 error: "Not found".to_string(),
566 code: Some("not_found".to_string()),
567 };
568 let json = serde_json::to_value(&resp).unwrap();
569 let obj = json.as_object().unwrap();
570
571 assert_eq!(obj.get("error").unwrap(), "Not found");
572 assert_eq!(obj.get("code").unwrap(), "not_found");
573 }
574
575 #[test]
576 fn error_response_round_trip_without_code() {
577 let resp = ErrorResponse {
578 error: "Bad request".to_string(),
579 code: None,
580 };
581 let json = serde_json::to_string(&resp).unwrap();
582 let deserialized: ErrorResponse = serde_json::from_str(&json).unwrap();
583
584 assert_eq!(deserialized.error, "Bad request");
585 assert!(deserialized.code.is_none());
586 }
587
588 #[test]
589 fn error_response_round_trip_with_code() {
590 let resp = ErrorResponse {
591 error: "Forbidden".to_string(),
592 code: Some("insufficient_permissions".to_string()),
593 };
594 let json = serde_json::to_string(&resp).unwrap();
595 let deserialized: ErrorResponse = serde_json::from_str(&json).unwrap();
596
597 assert_eq!(deserialized.error, "Forbidden");
598 assert_eq!(
599 deserialized.code.as_deref(),
600 Some("insufficient_permissions")
601 );
602 }
603
604 #[test]
605 fn error_response_deserialization_without_code_field() {
606 let json = r#"{"error":"test error"}"#;
607 let resp: ErrorResponse = serde_json::from_str(json).unwrap();
608
609 assert_eq!(resp.error, "test error");
610 assert!(resp.code.is_none());
611 }
612
613 #[test]
616 fn pagination_resolve_defaults() {
617 use crate::pagination::{DEFAULT_PER_PAGE, PaginationParams};
618 let params = PaginationParams {
619 page: None,
620 per_page: None,
621 };
622 let resolved = params.resolve();
623 assert_eq!(resolved.page, 1);
624 assert_eq!(resolved.per_page, DEFAULT_PER_PAGE);
625 }
626
627 #[test]
628 fn pagination_resolve_explicit_values() {
629 use crate::pagination::PaginationParams;
630 let params = PaginationParams {
631 page: Some(3),
632 per_page: Some(50),
633 };
634 let resolved = params.resolve();
635 assert_eq!(resolved.page, 3);
636 assert_eq!(resolved.per_page, 50);
637 }
638
639 #[test]
640 fn pagination_resolve_clamps_page_zero_to_one() {
641 use crate::pagination::PaginationParams;
642 let params = PaginationParams {
643 page: Some(0),
644 per_page: None,
645 };
646 let resolved = params.resolve();
647 assert_eq!(resolved.page, 1);
648 }
649
650 #[test]
651 fn pagination_resolve_clamps_per_page_zero_to_one() {
652 use crate::pagination::PaginationParams;
653 let params = PaginationParams {
654 page: None,
655 per_page: Some(0),
656 };
657 let resolved = params.resolve();
658 assert_eq!(resolved.per_page, 1);
659 }
660
661 #[test]
662 fn pagination_resolve_clamps_per_page_above_max() {
663 use crate::pagination::{MAX_PER_PAGE, PaginationParams};
664 let params = PaginationParams {
665 page: None,
666 per_page: Some(5000),
667 };
668 let resolved = params.resolve();
669 assert_eq!(resolved.per_page, MAX_PER_PAGE);
670 }
671
672 #[test]
673 fn pagination_offset_calculation() {
674 use crate::pagination::ResolvedPagination;
675 let p = ResolvedPagination {
676 page: 1,
677 per_page: 20,
678 };
679 assert_eq!(p.offset(), 0);
680
681 let p = ResolvedPagination {
682 page: 2,
683 per_page: 20,
684 };
685 assert_eq!(p.offset(), 20);
686
687 let p = ResolvedPagination {
688 page: 3,
689 per_page: 50,
690 };
691 assert_eq!(p.offset(), 100);
692 }
693
694 #[test]
695 fn pagination_total_pages_calculation() {
696 use crate::pagination::ResolvedPagination;
697 let p = ResolvedPagination {
698 page: 1,
699 per_page: 20,
700 };
701 assert_eq!(p.total_pages(0), 0);
702 assert_eq!(p.total_pages(1), 1);
703 assert_eq!(p.total_pages(20), 1);
704 assert_eq!(p.total_pages(21), 2);
705 assert_eq!(p.total_pages(100), 5);
706 }
707
708 #[test]
709 fn paginated_response_serialization_round_trip() {
710 use crate::pagination::{PaginatedResponse, ResolvedPagination};
711 let pagination = ResolvedPagination {
712 page: 2,
713 per_page: 10,
714 };
715 let resp = PaginatedResponse::new(vec!["a".to_string(), "b".to_string()], 25, pagination);
716
717 let json = serde_json::to_string(&resp).unwrap();
718 let deserialized: PaginatedResponse<String> = serde_json::from_str(&json).unwrap();
719
720 assert_eq!(deserialized.items, vec!["a", "b"]);
721 assert_eq!(deserialized.total, 25);
722 assert_eq!(deserialized.page, 2);
723 assert_eq!(deserialized.per_page, 10);
724 assert_eq!(deserialized.total_pages, 3);
725 }
726
727 #[test]
728 fn paginated_response_empty_items() {
729 use crate::pagination::{PaginatedResponse, ResolvedPagination};
730 let pagination = ResolvedPagination {
731 page: 1,
732 per_page: 20,
733 };
734 let resp = PaginatedResponse::<String>::new(vec![], 0, pagination);
735
736 assert!(resp.items.is_empty());
737 assert_eq!(resp.total, 0);
738 assert_eq!(resp.total_pages, 0);
739 }
740
741 #[test]
744 fn prelude_re_exports_resolve() {
745 use crate::prelude::*;
746
747 let _: AuthResponse;
749 let _: UserResponse;
750
751 let _ = ServiceStatus::Pending;
753
754 let _: HostResponse;
756
757 let _: SoftwareItemResponse;
759 let _ = TriggerUpdateStatus::Pending;
760
761 let _: PluginConfigResponse;
763
764 let _ = UpdateStatus::Pending;
766
767 let _: ApiTokenListResponse;
769 let _: CreateApiTokenResponse;
770
771 let _: OidcProviderResponse;
773 let _: AuthMethodsResponse;
774
775 let _ = NotificationEventType::UpdateAvailable;
777 let _ = NotificationDeliveryStatus::Pending;
778 let _: NotificationChannelResponse;
779 let _: NotificationRuleResponse;
780 let _: NotificationLogResponse;
781 let _: TestNotificationResponse;
782
783 let _: ErrorResponse;
785 let _: PaginatedResponse<String>;
786 let _: PaginationParams;
787 let _ = Permission::ViewSettings;
788 let _ = RegistrationMode::Open;
789 }
790
791 #[test]
794 fn register_request_valid() {
795 use crate::auth::RegisterRequest;
796 use crate::validation::Validate;
797 let req = RegisterRequest {
798 email: "user@example.com".to_string(),
799 first_name: "John".to_string(),
800 last_name: "Doe".to_string(),
801 password: SecretString::new("password12345678"),
802 registration_token: None,
803 };
804 assert!(req.validate().is_ok());
805 }
806
807 #[test]
808 fn register_request_email_missing_at() {
809 use crate::auth::RegisterRequest;
810 use crate::validation::Validate;
811 let req = RegisterRequest {
812 email: "invalid-email".to_string(),
813 first_name: "John".to_string(),
814 last_name: "Doe".to_string(),
815 password: SecretString::new("password12345678"),
816 registration_token: None,
817 };
818 let err = req.validate().unwrap_err();
819 assert_eq!(err.field, "email");
820 }
821
822 #[test]
823 fn register_request_password_too_short() {
824 use crate::auth::RegisterRequest;
825 use crate::validation::Validate;
826 let req = RegisterRequest {
827 email: "user@example.com".to_string(),
828 first_name: "John".to_string(),
829 last_name: "Doe".to_string(),
830 password: SecretString::new("short"),
831 registration_token: None,
832 };
833 let err = req.validate().unwrap_err();
834 assert_eq!(err.field, "password");
835 }
836
837 #[test]
838 fn register_request_empty_first_name() {
839 use crate::auth::RegisterRequest;
840 use crate::validation::Validate;
841 let req = RegisterRequest {
842 email: "user@example.com".to_string(),
843 first_name: "".to_string(),
844 last_name: "Doe".to_string(),
845 password: SecretString::new("password12345678"),
846 registration_token: None,
847 };
848 let err = req.validate().unwrap_err();
849 assert_eq!(err.field, "first_name");
850 }
851
852 #[test]
853 fn login_request_valid() {
854 use crate::auth::LoginRequest;
855 use crate::validation::Validate;
856 let req = LoginRequest {
857 email: "user@example.com".to_string(),
858 password: SecretString::new("any-password"),
859 };
860 assert!(req.validate().is_ok());
861 }
862
863 #[test]
864 fn login_request_empty_password() {
865 use crate::auth::LoginRequest;
866 use crate::validation::Validate;
867 let req = LoginRequest {
868 email: "user@example.com".to_string(),
869 password: SecretString::new(""),
870 };
871 let err = req.validate().unwrap_err();
872 assert_eq!(err.field, "password");
873 }
874
875 #[test]
876 fn create_oidc_provider_valid() {
877 use crate::validation::Validate;
878 let req = CreateOidcProviderRequest {
879 name: "Test Provider".to_string(),
880 slug: "test-provider".to_string(),
881 logo_url: None,
882 issuer_url: "https://issuer.example.com".to_string(),
883 client_id: "client-id".to_string(),
884 client_secret: SecretString::new("secret"),
885 scopes: "openid email".to_string(),
886 auto_create_users: true,
887 allow_private_network_issuers: Some(true),
888 role_claim_path: None,
889 role_mapping: Default::default(),
890 };
891 assert!(req.validate().is_ok());
892 }
893
894 #[test]
895 fn create_oidc_provider_invalid_slug() {
896 use crate::validation::Validate;
897 let req = CreateOidcProviderRequest {
898 name: "Test".to_string(),
899 slug: "INVALID_SLUG".to_string(),
900 logo_url: None,
901 issuer_url: "https://issuer.example.com".to_string(),
902 client_id: "client-id".to_string(),
903 client_secret: SecretString::new("secret"),
904 scopes: "openid".to_string(),
905 auto_create_users: true,
906 allow_private_network_issuers: Some(true),
907 role_claim_path: None,
908 role_mapping: Default::default(),
909 };
910 let err = req.validate().unwrap_err();
911 assert_eq!(err.field, "slug");
912 }
913
914 #[test]
915 fn create_oidc_provider_invalid_issuer_url() {
916 use crate::validation::Validate;
917 let req = CreateOidcProviderRequest {
918 name: "Test".to_string(),
919 slug: "test".to_string(),
920 logo_url: None,
921 issuer_url: "ftp://issuer.example.com".to_string(),
922 client_id: "client-id".to_string(),
923 client_secret: SecretString::new("secret"),
924 scopes: "openid".to_string(),
925 auto_create_users: true,
926 allow_private_network_issuers: Some(true),
927 role_claim_path: None,
928 role_mapping: Default::default(),
929 };
930 let err = req.validate().unwrap_err();
931 assert_eq!(err.field, "issuer_url");
932 }
933
934 #[test]
935 fn create_software_item_valid() {
936 use crate::validation::Validate;
937 let req = CreateSoftwareItemRequest {
938 name: "Node.js".to_string(),
939 featured: true,
940 icon_url: None,
941 };
942 assert!(req.validate().is_ok());
943 }
944
945 #[test]
946 fn create_software_item_empty_name_fails() {
947 use crate::validation::Validate;
948 let req = CreateSoftwareItemRequest {
949 name: "".to_string(),
950 featured: true,
951 icon_url: None,
952 };
953 let err = req.validate().unwrap_err();
954 assert_eq!(err.field, "name");
955 }
956
957 #[test]
958 fn create_plugin_config_valid() {
959 use crate::validation::Validate;
960 use uptrakit_shared_types::plugin_ids;
961 let req = CreatePluginConfigRequest {
962 name: "GitHub Releases".to_string(),
963 plugin_type: plugin_ids::RELEASES_GITHUB.clone(),
964 config: serde_json::json!({}),
965 enabled: true,
966 };
967 assert!(req.validate().is_ok());
968 }
969
970 #[test]
971 fn create_plugin_config_empty_name() {
972 use crate::validation::Validate;
973 use uptrakit_shared_types::plugin_ids;
974 let req = CreatePluginConfigRequest {
975 name: "".to_string(),
976 plugin_type: plugin_ids::RELEASES_GITHUB.clone(),
977 config: serde_json::json!({}),
978 enabled: true,
979 };
980 let err = req.validate().unwrap_err();
981 assert_eq!(err.field, "name");
982 }
983
984 #[test]
985 fn update_scheduled_task_valid_interval() {
986 use crate::scheduler::UpdateScheduledTaskRequest;
987 use crate::validation::Validate;
988 let req = UpdateScheduledTaskRequest {
989 interval_seconds: Some(300),
990 jitter_seconds: Some(30),
991 enabled: None,
992 task_config: None,
993 };
994 assert!(req.validate().is_ok());
995 }
996
997 #[test]
998 fn update_scheduled_task_invalid_interval() {
999 use crate::scheduler::UpdateScheduledTaskRequest;
1000 use crate::validation::Validate;
1001 let req = UpdateScheduledTaskRequest {
1002 interval_seconds: Some(0),
1003 jitter_seconds: None,
1004 enabled: None,
1005 task_config: None,
1006 };
1007 let err = req.validate().unwrap_err();
1008 assert_eq!(err.field, "interval_seconds");
1009 }
1010
1011 #[test]
1012 fn update_network_settings_valid() {
1013 use crate::settings_network::UpdateNetworkSettingsRequest;
1014 use crate::validation::Validate;
1015 let req = UpdateNetworkSettingsRequest {
1016 trusted_proxies: Some(vec!["10.0.0.0/8".to_string()]),
1017 real_ip_header: Some("X-Real-IP".to_string()),
1018 sans: None,
1019 https_addr: None,
1020 forwarded_client_cert_info_header: None,
1021 forwarded_client_cert_pem_header: None,
1022 pki_addr: Some("https://pki.example.com".to_string()),
1023 regenerate_cert: None,
1024 };
1025 assert!(req.validate().is_ok());
1026 }
1027
1028 #[test]
1029 fn update_network_settings_empty_trusted_proxy() {
1030 use crate::settings_network::UpdateNetworkSettingsRequest;
1031 use crate::validation::Validate;
1032 let req = UpdateNetworkSettingsRequest {
1033 trusted_proxies: Some(vec!["".to_string()]),
1034 real_ip_header: None,
1035 sans: None,
1036 https_addr: None,
1037 forwarded_client_cert_info_header: None,
1038 forwarded_client_cert_pem_header: None,
1039 pki_addr: None,
1040 regenerate_cert: None,
1041 };
1042 let err = req.validate().unwrap_err();
1043 assert_eq!(err.field, "trusted_proxies");
1044 }
1045
1046 #[test]
1049 fn device_auth_authorized_sse_round_trip() {
1050 use crate::device_auth::DeviceAuthAuthorizedSse;
1051 let event = DeviceAuthAuthorizedSse {
1052 token: SecretString::new("secret-api-token"),
1053 token_name: "my-device".to_string(),
1054 };
1055 let json = serde_json::to_string(&event).unwrap();
1056 let deserialized: DeviceAuthAuthorizedSse = serde_json::from_str(&json).unwrap();
1057 assert_eq!(deserialized.token.expose_secret(), "secret-api-token");
1058 assert_eq!(deserialized.token_name, "my-device");
1059 }
1060
1061 #[test]
1062 fn device_auth_expired_sse_round_trip() {
1063 use crate::device_auth::DeviceAuthExpiredSse;
1064 let event = DeviceAuthExpiredSse {
1065 message: "Device flow expired".to_string(),
1066 };
1067 let json = serde_json::to_string(&event).unwrap();
1068 let deserialized: DeviceAuthExpiredSse = serde_json::from_str(&json).unwrap();
1069 assert_eq!(deserialized.message, "Device flow expired");
1070 }
1071}