1use alloc::vec::Vec;
21
22pub use azul_core::keyring::{KeyringRequest, KeyringResult};
26
27use azul_core::dom::DomNodeId;
28use azul_core::events::{
29 EventData, EventProvider, EventSource as CoreEventSource, EventType, SyntheticEvent,
30};
31use azul_core::task::Instant;
32
33#[derive(Debug, Clone, PartialEq, Eq, Default)]
36pub struct KeyringManager {
37 pub last_result: Option<KeyringResult>,
40 pub in_flight: u32,
43 pub pending_event: bool,
49}
50
51impl KeyringManager {
52 #[must_use] pub fn new() -> Self {
53 Self::default()
54 }
55
56 #[must_use] pub const fn last_result(&self) -> Option<&KeyringResult> {
58 self.last_result.as_ref()
59 }
60
61 pub fn set_last_result(&mut self, result: KeyringResult) -> bool {
65 let changed = self.last_result.as_ref() != Some(&result);
66 self.last_result = Some(result);
67 self.pending_event = true;
70 self.in_flight = self.in_flight.saturating_sub(1);
71 changed
72 }
73
74 pub const fn mark_requests_dispatched(&mut self, n: u32) {
77 self.in_flight = self.in_flight.saturating_add(n);
78 }
79
80 pub const fn clear_pending_event(&mut self) {
83 self.pending_event = false;
84 }
85
86 #[must_use] pub const fn has_pending_async(&self) -> bool {
89 self.in_flight > 0
90 }
91}
92
93impl EventProvider for KeyringManager {
94 fn get_pending_events(&self, timestamp: Instant) -> Vec<SyntheticEvent> {
98 if self.pending_event {
99 alloc::vec![SyntheticEvent::new(
100 EventType::KeyringResult,
101 CoreEventSource::User,
102 DomNodeId::ROOT,
103 timestamp,
104 EventData::None,
105 )]
106 } else {
107 Vec::new()
108 }
109 }
110}
111
112static PENDING_REQUESTS: std::sync::Mutex<Vec<KeyringRequest>> =
115 std::sync::Mutex::new(Vec::new());
116
117pub fn push_keyring_request(request: KeyringRequest) {
120 let mut q = PENDING_REQUESTS.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
121 q.push(request);
122}
123
124pub fn drain_keyring_requests() -> Vec<KeyringRequest> {
127 let mut q = PENDING_REQUESTS.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
128 core::mem::take(&mut *q)
129}
130
131pub fn has_queued_requests() -> bool {
134 PENDING_REQUESTS
135 .lock()
136 .map_or_else(|e| !e.into_inner().is_empty(), |q| !q.is_empty())
137}
138
139static PENDING_RESULTS: std::sync::Mutex<Vec<KeyringResult>> =
142 std::sync::Mutex::new(Vec::new());
143
144pub fn push_keyring_result(result: KeyringResult) {
148 let mut q = PENDING_RESULTS.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
149 q.push(result);
150}
151
152pub fn drain_keyring_results() -> Vec<KeyringResult> {
155 let mut q = PENDING_RESULTS.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
156 core::mem::take(&mut *q)
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162 use azul_css::AzString;
163
164 #[test]
165 fn manager_defaults_to_no_result() {
166 let mgr = KeyringManager::new();
167 assert_eq!(mgr.last_result(), None);
168 }
169
170 #[test]
171 fn set_last_result_returns_change_flag() {
172 let mut mgr = KeyringManager::new();
173 assert!(mgr.set_last_result(KeyringResult::Stored));
174 assert_eq!(mgr.last_result(), Some(&KeyringResult::Stored));
175 assert!(!mgr.set_last_result(KeyringResult::Stored));
177 assert!(mgr.set_last_result(KeyringResult::Deleted));
179 }
180
181 #[test]
182 fn result_helpers() {
183 let secret = KeyringResult::Retrieved(AzString::from_const_str("hunter2"));
184 assert_eq!(secret.secret().map(AzString::as_str), Some("hunter2"));
185 assert!(secret.is_ok());
186 assert!(KeyringResult::Stored.is_ok());
187 assert!(KeyringResult::Deleted.is_ok());
188 for r in [
189 KeyringResult::NotFound,
190 KeyringResult::Denied,
191 KeyringResult::Unavailable,
192 KeyringResult::Error,
193 ] {
194 assert!(!r.is_ok(), "{r:?} must not be ok");
195 assert_eq!(r.secret(), None);
196 }
197 }
198
199 #[test]
200 fn requests_round_trip_through_channel() {
201 let _guard = super::autotest_generated::lock_channels();
204 drop(drain_keyring_requests());
205
206 push_keyring_request(KeyringRequest::Store {
207 key: AzString::from_const_str("token"),
208 secret: AzString::from_const_str("abc"),
209 require_biometry: true,
210 });
211 push_keyring_request(KeyringRequest::Get {
212 key: AzString::from_const_str("token"),
213 });
214 let drained = drain_keyring_requests();
215 assert_eq!(drained.len(), 2, "both queued requests drain in order");
216 assert!(matches!(drained[0], KeyringRequest::Store { .. }));
217 assert!(matches!(drained[1], KeyringRequest::Get { .. }));
218 assert!(drain_keyring_requests().is_empty());
219 }
220
221 #[test]
222 fn results_round_trip_through_manager() {
223 let _guard = super::autotest_generated::lock_channels();
226 drop(drain_keyring_results());
227
228 push_keyring_result(KeyringResult::NotFound);
229 push_keyring_result(KeyringResult::Retrieved(AzString::from_const_str("s"))); let drained = drain_keyring_results();
231 assert_eq!(drained.len(), 2);
232
233 let mut mgr = KeyringManager::new();
234 for r in drained {
235 mgr.set_last_result(r);
236 }
237 assert_eq!(
238 mgr.last_result().and_then(|r| r.secret()).map(AzString::as_str),
239 Some("s"),
240 "the last applied result wins"
241 );
242 assert!(drain_keyring_results().is_empty());
243 }
244}
245
246#[cfg(test)]
247mod pump_provider_tests {
248 use super::*;
249 use azul_core::task::{Instant, SystemTick};
250
251 fn ts() -> Instant {
252 Instant::Tick(SystemTick::new(0))
253 }
254
255 #[test]
256 fn in_flight_and_events_track_op_lifecycle() {
257 let mut mgr = KeyringManager::new();
258 assert!(!mgr.has_pending_async());
259 mgr.mark_requests_dispatched(1);
260 assert!(mgr.has_pending_async());
261 assert!(mgr.get_pending_events(ts()).is_empty(), "no outcome yet");
262
263 mgr.set_last_result(KeyringResult::Stored);
264 assert!(!mgr.has_pending_async());
265 let events = mgr.get_pending_events(ts());
266 assert_eq!(events.len(), 1);
267 assert_eq!(events[0].event_type, EventType::KeyringResult);
268 mgr.clear_pending_event();
269
270 mgr.mark_requests_dispatched(1);
272 mgr.set_last_result(KeyringResult::Stored);
273 assert_eq!(mgr.get_pending_events(ts()).len(), 1);
274 }
275}
276
277#[cfg(test)]
278mod autotest_generated {
279 use std::sync::{Mutex, PoisonError};
280
281 use azul_core::task::SystemTick;
282 use azul_css::AzString;
283
284 use super::*;
285
286 static CHANNEL_LOCK: Mutex<()> = Mutex::new(());
292
293 pub(super) fn lock_channels() -> std::sync::MutexGuard<'static, ()> {
294 CHANNEL_LOCK.lock().unwrap_or_else(PoisonError::into_inner)
295 }
296
297 fn ts() -> Instant {
298 Instant::Tick(SystemTick::new(0))
299 }
300
301 const NASTY_BYTES: &str = "pw\0with\u{1}nul\u{7f}\r\n\t";
304 const NASTY_UNICODE: &str = "🔑 ключ 鍵 مفتاح e\u{301}\u{202e}terces\u{202c}\u{fffd}\u{10ffff}";
307
308 fn all_results() -> [KeyringResult; 7] {
310 [
311 KeyringResult::Stored,
312 KeyringResult::Retrieved(AzString::from("s3cr3t")),
313 KeyringResult::Deleted,
314 KeyringResult::NotFound,
315 KeyringResult::Denied,
316 KeyringResult::Unavailable,
317 KeyringResult::Error,
318 ]
319 }
320
321 #[test]
324 fn new_equals_default_and_holds_the_zero_state_invariants() {
325 let a = KeyringManager::new();
326 let b = KeyringManager::default();
327 assert_eq!(a, b, "new() must be exactly default()");
328
329 assert_eq!(a.last_result, None);
332 assert_eq!(a.last_result(), None);
333 assert_eq!(a.in_flight, 0);
334 assert!(!a.has_pending_async());
335 assert!(!a.pending_event);
336 assert!(
337 a.get_pending_events(ts()).is_empty(),
338 "a fresh manager has nothing to report"
339 );
340 }
341
342 #[test]
343 fn new_carries_no_hidden_global_state_between_instances() {
344 let _guard = lock_channels();
347 drop(drain_keyring_results());
348 push_keyring_result(KeyringResult::Retrieved(AzString::from("leaked")));
349
350 let mgr = KeyringManager::new();
351 assert_eq!(
352 mgr.last_result(),
353 None,
354 "construction must not drain the result channel"
355 );
356
357 assert_eq!(drain_keyring_results().len(), 1);
359 }
360
361 #[test]
364 fn last_result_returns_the_exact_outcome_that_was_folded() {
365 let mut mgr = KeyringManager::new();
366 assert_eq!(mgr.last_result(), None, "None until the first op resolves");
367
368 for r in all_results() {
369 mgr.set_last_result(r.clone());
370 assert_eq!(
371 mgr.last_result(),
372 Some(&r),
373 "last_result() must hand back exactly what was folded"
374 );
375 }
376 }
377
378 #[test]
379 fn last_result_preserves_nul_control_and_max_scalar_payloads() {
380 for payload in [NASTY_BYTES, NASTY_UNICODE, ""] {
384 let mut mgr = KeyringManager::new();
385 mgr.set_last_result(KeyringResult::Retrieved(AzString::from(payload)));
386
387 let s = mgr
388 .last_result()
389 .and_then(KeyringResult::secret)
390 .expect("Retrieved must expose its secret");
391 assert_eq!(s.as_str(), payload);
392 assert_eq!(s.as_str().as_bytes(), payload.as_bytes());
393 assert_eq!(s.as_str().len(), payload.len(), "nothing truncated at the NUL");
394 }
395 }
396
397 #[test]
398 fn last_result_handles_a_megabyte_secret() {
399 let big = "k".repeat(1 << 20);
400 let mut mgr = KeyringManager::new();
401 assert!(mgr.set_last_result(KeyringResult::Retrieved(AzString::from(big.clone()))));
402
403 let s = mgr
404 .last_result()
405 .and_then(KeyringResult::secret)
406 .expect("Retrieved must expose its secret");
407 assert_eq!(s.as_str().len(), 1 << 20);
408 assert_eq!(s.as_str(), big.as_str());
409
410 assert!(!mgr.set_last_result(KeyringResult::Retrieved(AzString::from(big))));
413 }
414
415 #[test]
416 fn last_result_survives_the_flag_and_counter_mutators() {
417 let mut mgr = KeyringManager::new();
418 mgr.set_last_result(KeyringResult::Retrieved(AzString::from("keep-me")));
419
420 mgr.clear_pending_event();
421 mgr.mark_requests_dispatched(7);
422 mgr.mark_requests_dispatched(0);
423
424 assert_eq!(
425 mgr.last_result().and_then(KeyringResult::secret).map(AzString::as_str),
426 Some("keep-me"),
427 "neither the event flag nor the in-flight counter may clobber the outcome"
428 );
429 }
430
431 #[test]
434 fn set_last_result_change_flag_is_true_only_on_a_distinct_outcome() {
435 let mut mgr = KeyringManager::new();
436 for r in all_results() {
437 assert!(
438 mgr.set_last_result(r.clone()),
439 "{r:?} differs from the previous outcome → changed"
440 );
441 assert!(
442 !mgr.set_last_result(r.clone()),
443 "re-folding the identical {r:?} is not a change"
444 );
445 }
446 }
447
448 #[test]
449 fn set_last_result_compares_retrieved_payloads_by_value() {
450 let mut mgr = KeyringManager::new();
451 assert!(mgr.set_last_result(KeyringResult::Retrieved(AzString::from("a"))));
452 assert!(
453 !mgr.set_last_result(KeyringResult::Retrieved(AzString::from("a"))),
454 "same payload, freshly allocated → not a change"
455 );
456 assert!(
457 mgr.set_last_result(KeyringResult::Retrieved(AzString::from("b"))),
458 "a different secret under the same variant IS a change"
459 );
460 assert!(mgr.set_last_result(KeyringResult::Retrieved(AzString::from(""))));
462 assert!(!mgr.set_last_result(KeyringResult::Retrieved(AzString::from(""))));
463 assert!(
464 mgr.set_last_result(KeyringResult::NotFound),
465 "Retrieved(\"\") and NotFound must not collapse into one another"
466 );
467 }
468
469 #[test]
470 fn set_last_result_fires_the_event_even_when_the_outcome_is_unchanged() {
471 let mut mgr = KeyringManager::new();
474 mgr.set_last_result(KeyringResult::Denied);
475 mgr.clear_pending_event();
476
477 assert!(!mgr.set_last_result(KeyringResult::Denied), "not a change…");
478 assert!(mgr.pending_event, "…but still a completion → event pending");
479 assert_eq!(mgr.get_pending_events(ts()).len(), 1);
480 }
481
482 #[test]
483 fn set_last_result_without_a_dispatch_does_not_underflow_in_flight() {
484 let mut mgr = KeyringManager::new();
488 assert_eq!(mgr.in_flight, 0);
489 for _ in 0..1_000 {
490 mgr.set_last_result(KeyringResult::Error);
491 }
492 assert_eq!(mgr.in_flight, 0, "saturating_sub must clamp at 0, not wrap");
493 assert!(!mgr.has_pending_async());
494 }
495
496 #[test]
497 fn set_last_result_retires_exactly_one_in_flight_op_per_fold() {
498 let mut mgr = KeyringManager::new();
499 mgr.mark_requests_dispatched(3);
500 for expected in [2_u32, 1, 0] {
501 mgr.set_last_result(KeyringResult::Stored);
502 assert_eq!(mgr.in_flight, expected, "one fold retires exactly one op");
503 }
504 assert!(!mgr.has_pending_async(), "all dispatched ops resolved");
505
506 mgr.set_last_result(KeyringResult::Deleted);
508 assert_eq!(mgr.in_flight, 0);
509 }
510
511 #[test]
512 fn set_last_result_retires_one_op_even_at_the_u32_max_ceiling() {
513 let mut mgr = KeyringManager::new();
514 mgr.in_flight = u32::MAX;
515 mgr.set_last_result(KeyringResult::Stored);
516 assert_eq!(
517 mgr.in_flight,
518 u32::MAX - 1,
519 "the counter is only saturated at the bottom, not stuck at the top"
520 );
521 assert!(mgr.has_pending_async());
522 }
523
524 #[test]
527 fn mark_requests_dispatched_zero_is_a_no_op() {
528 let mut mgr = KeyringManager::new();
529 mgr.mark_requests_dispatched(0);
530 assert_eq!(mgr.in_flight, 0);
531 assert!(
532 !mgr.has_pending_async(),
533 "dispatching nothing must not arm the pump"
534 );
535
536 mgr.mark_requests_dispatched(5);
537 mgr.mark_requests_dispatched(0);
538 assert_eq!(mgr.in_flight, 5, "a zero dispatch must not disturb the count");
539 }
540
541 #[test]
542 fn mark_requests_dispatched_accumulates_across_calls() {
543 let mut mgr = KeyringManager::new();
544 mgr.mark_requests_dispatched(1);
545 mgr.mark_requests_dispatched(2);
546 mgr.mark_requests_dispatched(3);
547 assert_eq!(mgr.in_flight, 6);
548
549 let mut many = KeyringManager::new();
550 for _ in 0..10_000 {
551 many.mark_requests_dispatched(1);
552 }
553 assert_eq!(many.in_flight, 10_000);
554 assert!(many.has_pending_async());
555 }
556
557 #[test]
558 fn mark_requests_dispatched_saturates_at_u32_max_instead_of_overflowing() {
559 let mut mgr = KeyringManager::new();
562 mgr.mark_requests_dispatched(u32::MAX);
563 assert_eq!(mgr.in_flight, u32::MAX);
564
565 mgr.mark_requests_dispatched(1);
566 assert_eq!(mgr.in_flight, u32::MAX, "saturating_add clamps at the ceiling");
567 mgr.mark_requests_dispatched(u32::MAX);
568 assert_eq!(mgr.in_flight, u32::MAX);
569 assert!(mgr.has_pending_async());
570
571 let mut near = KeyringManager::new();
573 near.in_flight = u32::MAX - 1;
574 near.mark_requests_dispatched(2);
575 assert_eq!(near.in_flight, u32::MAX);
576 }
577
578 #[test]
579 fn mark_requests_dispatched_touches_neither_the_event_flag_nor_the_outcome() {
580 let mut mgr = KeyringManager::new();
581 mgr.mark_requests_dispatched(u32::MAX);
582 assert!(
583 !mgr.pending_event,
584 "dispatching is not a completion — no event may fire"
585 );
586 assert_eq!(mgr.last_result(), None);
587 assert!(mgr.get_pending_events(ts()).is_empty());
588 }
589
590 #[test]
593 fn clear_pending_event_is_idempotent_and_only_touches_the_flag() {
594 let mut mgr = KeyringManager::new();
595 mgr.clear_pending_event();
597 assert!(!mgr.pending_event);
598
599 mgr.mark_requests_dispatched(2);
600 mgr.set_last_result(KeyringResult::Stored);
601 assert!(mgr.pending_event);
602
603 mgr.clear_pending_event();
604 mgr.clear_pending_event();
605 assert!(!mgr.pending_event);
606 assert!(
607 mgr.get_pending_events(ts()).is_empty(),
608 "a cleared flag yields no events"
609 );
610
611 assert_eq!(mgr.last_result(), Some(&KeyringResult::Stored));
613 assert_eq!(mgr.in_flight, 1);
614 assert!(
615 mgr.has_pending_async(),
616 "clearing the event flag must not disarm the still-outstanding op"
617 );
618 }
619
620 #[test]
623 fn has_pending_async_is_exactly_in_flight_greater_than_zero() {
624 let mut mgr = KeyringManager::new();
625 for (in_flight, expected) in [(0_u32, false), (1, true), (2, true), (u32::MAX, true)] {
626 mgr.in_flight = in_flight;
627 assert_eq!(
628 mgr.has_pending_async(),
629 expected,
630 "in_flight = {in_flight} → has_pending_async() = {expected}"
631 );
632 }
633 }
634
635 #[test]
636 fn has_pending_async_goes_false_only_when_the_last_op_folds_back() {
637 let mut mgr = KeyringManager::new();
638 assert!(!mgr.has_pending_async());
639 mgr.mark_requests_dispatched(2);
640 assert!(mgr.has_pending_async());
641 mgr.set_last_result(KeyringResult::Stored);
642 assert!(mgr.has_pending_async(), "one op is still outstanding");
643 mgr.set_last_result(KeyringResult::Stored);
644 assert!(!mgr.has_pending_async(), "both ops resolved → pump disarms");
645 }
646
647 #[test]
650 fn pending_event_is_a_root_targeted_user_sourced_keyring_result() {
651 let mut mgr = KeyringManager::new();
652 mgr.set_last_result(KeyringResult::Retrieved(AzString::from("s")));
653
654 let stamp = Instant::Tick(SystemTick::new(u64::MAX));
655 let events = mgr.get_pending_events(stamp.clone());
656 assert_eq!(events.len(), 1, "exactly one event per pass");
657
658 let e = &events[0];
659 assert_eq!(e.event_type, EventType::KeyringResult);
660 assert_eq!(e.source, CoreEventSource::User);
661 assert_eq!(e.target, DomNodeId::ROOT, "keyring events are window-level");
662 assert_eq!(e.current_target, DomNodeId::ROOT);
663 assert_eq!(e.timestamp, stamp, "the caller's timestamp is echoed verbatim");
664 assert!(
665 matches!(e.data, EventData::None),
666 "the outcome is read via CallbackInfo, not carried in the event"
667 );
668 }
669
670 #[test]
671 fn get_pending_events_is_non_destructive_until_cleared() {
672 let mut mgr = KeyringManager::new();
675 mgr.set_last_result(KeyringResult::Deleted);
676
677 assert_eq!(mgr.get_pending_events(ts()).len(), 1);
678 assert_eq!(mgr.get_pending_events(ts()).len(), 1, "still pending");
679 assert!(mgr.pending_event);
680
681 mgr.clear_pending_event();
682 assert!(mgr.get_pending_events(ts()).is_empty());
683 }
684
685 #[test]
688 fn request_channel_is_fifo_across_every_variant() {
689 let _guard = lock_channels();
690 drop(drain_keyring_requests());
691
692 push_keyring_request(KeyringRequest::Store {
693 key: AzString::from("k1"),
694 secret: AzString::from("s1"),
695 require_biometry: true,
696 });
697 push_keyring_request(KeyringRequest::Get {
698 key: AzString::from("k2"),
699 });
700 push_keyring_request(KeyringRequest::Delete {
701 key: AzString::from("k3"),
702 });
703
704 let drained = drain_keyring_requests();
705 assert_eq!(drained.len(), 3);
706 assert_eq!(
707 drained[0],
708 KeyringRequest::Store {
709 key: AzString::from("k1"),
710 secret: AzString::from("s1"),
711 require_biometry: true,
712 },
713 "arrival order and payload preserved exactly"
714 );
715 assert_eq!(
716 drained[1],
717 KeyringRequest::Get {
718 key: AzString::from("k2")
719 }
720 );
721 assert_eq!(
722 drained[2],
723 KeyringRequest::Delete {
724 key: AzString::from("k3")
725 }
726 );
727
728 assert!(drain_keyring_requests().is_empty());
730 }
731
732 #[test]
733 fn draining_an_empty_channel_is_an_empty_vec_not_a_panic() {
734 let _guard = lock_channels();
735 drop(drain_keyring_requests());
736 drop(drain_keyring_results());
737
738 for _ in 0..3 {
739 assert!(drain_keyring_requests().is_empty());
740 assert!(drain_keyring_results().is_empty());
741 }
742 assert!(!has_queued_requests());
743 }
744
745 #[test]
746 fn has_queued_requests_tracks_only_the_request_channel() {
747 let _guard = lock_channels();
748 drop(drain_keyring_requests());
749 drop(drain_keyring_results());
750 assert!(!has_queued_requests(), "empty queue → nothing to pump");
751
752 push_keyring_result(KeyringResult::Stored);
755 assert!(!has_queued_requests());
756
757 push_keyring_request(KeyringRequest::Get {
758 key: AzString::from("k"),
759 });
760 assert!(has_queued_requests(), "a parked-but-undispatched request arms the pump");
761 assert!(has_queued_requests());
763
764 assert_eq!(drain_keyring_requests().len(), 1);
765 assert!(!has_queued_requests(), "draining disarms it");
766
767 drop(drain_keyring_results());
768 }
769
770 #[test]
771 fn request_channel_preserves_nul_unicode_and_megabyte_payloads() {
772 let _guard = lock_channels();
773 drop(drain_keyring_requests());
774
775 let big = "s".repeat(1 << 20);
776 push_keyring_request(KeyringRequest::Store {
777 key: AzString::from(NASTY_BYTES),
778 secret: AzString::from(NASTY_UNICODE),
779 require_biometry: false,
780 });
781 push_keyring_request(KeyringRequest::Get {
782 key: AzString::from(big.clone()),
783 });
784
785 let drained = drain_keyring_requests();
786 assert_eq!(drained.len(), 2);
787
788 let KeyringRequest::Store {
789 key,
790 secret,
791 require_biometry,
792 } = &drained[0]
793 else {
794 panic!("first request must still be the Store: {:?}", drained[0]);
795 };
796 assert_eq!(key.as_str().as_bytes(), NASTY_BYTES.as_bytes());
799 assert_eq!(secret.as_str().as_bytes(), NASTY_UNICODE.as_bytes());
800 assert!(secret.as_str().ends_with('\u{10ffff}'));
801 assert!(!*require_biometry);
802
803 let KeyringRequest::Get { key } = &drained[1] else {
804 panic!("second request must still be the Get: {:?}", drained[1]);
805 };
806 assert_eq!(key.as_str().len(), 1 << 20);
807 assert_eq!(key.as_str(), big.as_str());
808 }
809
810 #[test]
811 fn request_channel_keeps_arrival_order_under_many_pushes() {
812 let _guard = lock_channels();
813 drop(drain_keyring_requests());
814
815 const N: usize = 1_000;
816 for i in 0..N {
817 push_keyring_request(KeyringRequest::Get {
818 key: AzString::from(format!("key-{i}")),
819 });
820 }
821
822 let drained = drain_keyring_requests();
823 assert_eq!(drained.len(), N);
824 for (i, req) in drained.iter().enumerate() {
825 let KeyringRequest::Get { key } = req else {
826 panic!("expected a Get at {i}, got {req:?}");
827 };
828 assert_eq!(key.as_str(), format!("key-{i}"), "FIFO order at {i}");
829 }
830 assert!(drain_keyring_requests().is_empty());
831 }
832
833 #[test]
834 fn concurrent_pushes_from_many_threads_lose_no_requests() {
835 let _guard = lock_channels();
839 drop(drain_keyring_requests());
840
841 const THREADS: usize = 8;
842 const PER_THREAD: usize = 50;
843
844 let handles: Vec<_> = (0..THREADS)
845 .map(|t| {
846 std::thread::spawn(move || {
847 for i in 0..PER_THREAD {
848 push_keyring_request(KeyringRequest::Delete {
849 key: AzString::from(format!("t{t}-{i}")),
850 });
851 }
852 })
853 })
854 .collect();
855 for h in handles {
856 h.join().expect("pushing thread must not panic");
857 }
858
859 let drained = drain_keyring_requests();
860 assert_eq!(drained.len(), THREADS * PER_THREAD, "no request lost or duplicated");
861
862 let mut keys: Vec<String> = drained
865 .iter()
866 .map(|r| match r {
867 KeyringRequest::Delete { key } => key.as_str().to_string(),
868 other => panic!("unexpected request in the channel: {other:?}"),
869 })
870 .collect();
871 keys.sort();
872 let mut expected: Vec<String> = (0..THREADS)
873 .flat_map(|t| (0..PER_THREAD).map(move |i| format!("t{t}-{i}")))
874 .collect();
875 expected.sort();
876 assert_eq!(keys, expected);
877 }
878
879 #[test]
880 fn result_channel_folds_into_the_manager_with_last_write_winning() {
881 let _guard = lock_channels();
882 drop(drain_keyring_results());
883
884 let mut mgr = KeyringManager::new();
885 mgr.mark_requests_dispatched(3);
886
887 push_keyring_result(KeyringResult::Denied);
888 push_keyring_result(KeyringResult::NotFound);
889 push_keyring_result(KeyringResult::Retrieved(AzString::from(NASTY_UNICODE)));
890
891 let drained = drain_keyring_results();
892 assert_eq!(drained.len(), 3, "results drain in arrival order");
893 assert_eq!(drained[0], KeyringResult::Denied);
894 assert_eq!(drained[1], KeyringResult::NotFound);
895
896 for r in drained {
897 mgr.set_last_result(r);
898 }
899 assert_eq!(
900 mgr.last_result().and_then(KeyringResult::secret).map(AzString::as_str),
901 Some(NASTY_UNICODE),
902 "the last applied result wins"
903 );
904 assert_eq!(mgr.in_flight, 0, "all three dispatched ops retired");
905 assert!(!mgr.has_pending_async());
906 assert!(mgr.pending_event);
907 assert!(drain_keyring_results().is_empty());
908 }
909}