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