aster/auto_reply/
whitelist.rs1use std::collections::HashSet;
32
33#[derive(Debug, Clone)]
39pub struct WhitelistManager {
40 allowed_users: HashSet<String>,
42}
43
44impl Default for WhitelistManager {
45 fn default() -> Self {
46 Self::new()
47 }
48}
49
50impl WhitelistManager {
51 pub fn new() -> Self {
64 Self {
65 allowed_users: HashSet::new(),
66 }
67 }
68
69 pub fn from_users(users: Vec<String>) -> Self {
88 Self {
89 allowed_users: users.into_iter().collect(),
90 }
91 }
92
93 pub fn is_allowed(&self, user_id: &str) -> bool {
122 self.allowed_users.is_empty() || self.allowed_users.contains(user_id)
123 }
124
125 pub fn is_enabled(&self) -> bool {
133 !self.allowed_users.is_empty()
134 }
135
136 pub fn add_user(&mut self, user_id: String) {
152 self.allowed_users.insert(user_id);
153 }
154
155 pub fn remove_user(&mut self, user_id: &str) -> bool {
175 self.allowed_users.remove(user_id)
176 }
177
178 pub fn list_users(&self) -> Vec<&String> {
197 self.allowed_users.iter().collect()
198 }
199
200 pub fn len(&self) -> usize {
206 self.allowed_users.len()
207 }
208
209 pub fn is_empty(&self) -> bool {
215 self.allowed_users.is_empty()
216 }
217
218 pub fn clear(&mut self) {
234 self.allowed_users.clear();
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241 use proptest::prelude::*;
242
243 #[derive(Debug, Clone)]
251 enum WhitelistOp {
252 Add(String),
253 Remove(String),
254 Clear,
255 }
256
257 fn arb_user_id() -> impl Strategy<Value = String> {
259 "[a-zA-Z0-9_]{1,20}".prop_map(|s| s)
260 }
261
262 fn arb_whitelist_op() -> impl Strategy<Value = WhitelistOp> {
264 prop_oneof![
265 arb_user_id().prop_map(WhitelistOp::Add),
266 arb_user_id().prop_map(WhitelistOp::Remove),
267 Just(WhitelistOp::Clear),
268 ]
269 }
270
271 fn arb_op_sequence() -> impl Strategy<Value = Vec<WhitelistOp>> {
273 prop::collection::vec(arb_whitelist_op(), 0..50)
274 }
275
276 fn apply_ops_to_set(ops: &[WhitelistOp]) -> std::collections::HashSet<String> {
278 let mut set = std::collections::HashSet::new();
279 for op in ops {
280 match op {
281 WhitelistOp::Add(user_id) => {
282 set.insert(user_id.clone());
283 }
284 WhitelistOp::Remove(user_id) => {
285 set.remove(user_id);
286 }
287 WhitelistOp::Clear => {
288 set.clear();
289 }
290 }
291 }
292 set
293 }
294
295 fn apply_ops_to_whitelist(whitelist: &mut WhitelistManager, ops: &[WhitelistOp]) {
297 for op in ops {
298 match op {
299 WhitelistOp::Add(user_id) => {
300 whitelist.add_user(user_id.clone());
301 }
302 WhitelistOp::Remove(user_id) => {
303 whitelist.remove_user(user_id);
304 }
305 WhitelistOp::Clear => {
306 whitelist.clear();
307 }
308 }
309 }
310 }
311
312 proptest! {
313 #![proptest_config(ProptestConfig::with_cases(20))]
314
315 #[test]
318 fn prop_add_user_then_allowed(user_id in arb_user_id()) {
319 let mut whitelist = WhitelistManager::new();
320 whitelist.add_user(user_id.clone());
321
322 prop_assert!(whitelist.is_allowed(&user_id));
324 }
325
326 #[test]
329 fn prop_empty_whitelist_allows_all(user_id in arb_user_id()) {
330 let whitelist = WhitelistManager::new();
331
332 prop_assert!(whitelist.is_allowed(&user_id));
334 prop_assert!(whitelist.is_empty());
335 }
336
337 #[test]
340 fn prop_non_empty_whitelist_restricts(
341 allowed_user in arb_user_id(),
342 other_user in arb_user_id()
343 ) {
344 prop_assume!(allowed_user != other_user);
346
347 let whitelist = WhitelistManager::from_users(vec![allowed_user.clone()]);
348
349 prop_assert!(whitelist.is_allowed(&allowed_user));
351 prop_assert!(!whitelist.is_allowed(&other_user));
353 }
354
355 #[test]
358 fn prop_remove_user_then_not_allowed(
359 user_to_remove in arb_user_id(),
360 other_user in arb_user_id()
361 ) {
362 prop_assume!(user_to_remove != other_user);
364
365 let mut whitelist = WhitelistManager::from_users(vec![
366 user_to_remove.clone(),
367 other_user.clone(),
368 ]);
369
370 prop_assert!(whitelist.is_allowed(&user_to_remove));
372
373 whitelist.remove_user(&user_to_remove);
375
376 prop_assert!(!whitelist.is_allowed(&user_to_remove));
378 prop_assert!(whitelist.is_allowed(&other_user));
380 }
381
382 #[test]
385 fn prop_remove_last_user_allows_all(
386 user_id in arb_user_id(),
387 test_user in arb_user_id()
388 ) {
389 let mut whitelist = WhitelistManager::from_users(vec![user_id.clone()]);
390
391 if user_id != test_user {
393 prop_assert!(!whitelist.is_allowed(&test_user));
394 }
395
396 whitelist.remove_user(&user_id);
398
399 prop_assert!(whitelist.is_empty());
401 prop_assert!(whitelist.is_allowed(&test_user));
402 prop_assert!(whitelist.is_allowed(&user_id));
403 }
404
405 #[test]
408 fn prop_operations_consistent_with_set_semantics(
409 ops in arb_op_sequence(),
410 test_users in prop::collection::vec(arb_user_id(), 1..10)
411 ) {
412 let mut whitelist = WhitelistManager::new();
413
414 apply_ops_to_whitelist(&mut whitelist, &ops);
416
417 let expected_set = apply_ops_to_set(&ops);
419
420 prop_assert_eq!(whitelist.len(), expected_set.len());
422 prop_assert_eq!(whitelist.is_empty(), expected_set.is_empty());
423
424 for user in &test_users {
426 let expected_allowed = expected_set.is_empty() || expected_set.contains(user);
427 prop_assert_eq!(
428 whitelist.is_allowed(user),
429 expected_allowed,
430 "User {} should be {} but was {}",
431 user,
432 if expected_allowed { "allowed" } else { "not allowed" },
433 if whitelist.is_allowed(user) { "allowed" } else { "not allowed" }
434 );
435 }
436 }
437
438 #[test]
441 fn prop_add_user_idempotent(user_id in arb_user_id(), times in 1usize..10) {
442 let mut whitelist = WhitelistManager::new();
443
444 for _ in 0..times {
446 whitelist.add_user(user_id.clone());
447 }
448
449 prop_assert_eq!(whitelist.len(), 1);
451 prop_assert!(whitelist.is_allowed(&user_id));
453 }
454
455 #[test]
458 fn prop_remove_nonexistent_user_no_effect(
459 existing_user in arb_user_id(),
460 nonexistent_user in arb_user_id()
461 ) {
462 prop_assume!(existing_user != nonexistent_user);
463
464 let mut whitelist = WhitelistManager::from_users(vec![existing_user.clone()]);
465 let len_before = whitelist.len();
466
467 let removed = whitelist.remove_user(&nonexistent_user);
469
470 prop_assert!(!removed);
472 prop_assert_eq!(whitelist.len(), len_before);
474 prop_assert!(whitelist.is_allowed(&existing_user));
476 }
477
478 #[test]
481 fn prop_clear_allows_all(
482 initial_users in prop::collection::vec(arb_user_id(), 1..10),
483 test_user in arb_user_id()
484 ) {
485 let mut whitelist = WhitelistManager::from_users(initial_users);
486
487 whitelist.clear();
489
490 prop_assert!(whitelist.is_empty());
492 prop_assert!(whitelist.is_allowed(&test_user));
493 }
494
495 #[test]
498 fn prop_list_users_complete(users in prop::collection::hash_set(arb_user_id(), 0..20)) {
499 let users_vec: Vec<String> = users.iter().cloned().collect();
500 let whitelist = WhitelistManager::from_users(users_vec);
501
502 let listed: std::collections::HashSet<&String> = whitelist.list_users().into_iter().collect();
503
504 prop_assert_eq!(listed.len(), users.len());
506
507 for user in &users {
509 prop_assert!(listed.contains(user));
510 }
511 }
512 }
513
514 #[test]
520 fn test_new_whitelist_is_empty() {
521 let whitelist = WhitelistManager::new();
522 assert!(whitelist.is_empty());
523 assert!(!whitelist.is_enabled());
524 assert_eq!(whitelist.len(), 0);
525 }
526
527 #[test]
530 fn test_empty_whitelist_allows_all_users() {
531 let whitelist = WhitelistManager::new();
532 assert!(whitelist.is_allowed("user1"));
533 assert!(whitelist.is_allowed("user2"));
534 assert!(whitelist.is_allowed("any_random_user"));
535 }
536
537 #[test]
539 fn test_from_users() {
540 let whitelist =
541 WhitelistManager::from_users(vec!["user1".to_string(), "user2".to_string()]);
542 assert_eq!(whitelist.len(), 2);
543 assert!(whitelist.is_enabled());
544 assert!(whitelist.is_allowed("user1"));
545 assert!(whitelist.is_allowed("user2"));
546 }
547
548 #[test]
551 fn test_non_empty_whitelist_restricts_users() {
552 let whitelist = WhitelistManager::from_users(vec!["allowed_user".to_string()]);
553 assert!(whitelist.is_allowed("allowed_user"));
554 assert!(!whitelist.is_allowed("other_user"));
555 assert!(!whitelist.is_allowed("random_user"));
556 }
557
558 #[test]
561 fn test_add_user() {
562 let mut whitelist = WhitelistManager::new();
563
564 assert!(whitelist.is_allowed("user1"));
566 assert!(whitelist.is_allowed("user2"));
567
568 whitelist.add_user("user1".to_string());
570 assert!(whitelist.is_allowed("user1"));
571 assert!(!whitelist.is_allowed("user2"));
572
573 whitelist.add_user("user2".to_string());
575 assert!(whitelist.is_allowed("user1"));
576 assert!(whitelist.is_allowed("user2"));
577 assert!(!whitelist.is_allowed("user3"));
578 }
579
580 #[test]
583 fn test_remove_user() {
584 let mut whitelist =
585 WhitelistManager::from_users(vec!["user1".to_string(), "user2".to_string()]);
586
587 assert!(whitelist.remove_user("user1"));
589 assert!(!whitelist.is_allowed("user1"));
590 assert!(whitelist.is_allowed("user2"));
591
592 assert!(!whitelist.remove_user("user1"));
594 assert!(!whitelist.remove_user("nonexistent"));
595 }
596
597 #[test]
599 fn test_remove_last_user_allows_all() {
600 let mut whitelist = WhitelistManager::from_users(vec!["user1".to_string()]);
601
602 assert!(!whitelist.is_allowed("other_user"));
603
604 whitelist.remove_user("user1");
605
606 assert!(whitelist.is_allowed("other_user"));
608 assert!(whitelist.is_allowed("any_user"));
609 }
610
611 #[test]
614 fn test_is_allowed() {
615 let whitelist =
616 WhitelistManager::from_users(vec!["user1".to_string(), "user2".to_string()]);
617
618 assert!(whitelist.is_allowed("user1"));
619 assert!(whitelist.is_allowed("user2"));
620 assert!(!whitelist.is_allowed("user3"));
621 }
622
623 #[test]
625 fn test_list_users() {
626 let whitelist =
627 WhitelistManager::from_users(vec!["user1".to_string(), "user2".to_string()]);
628
629 let users = whitelist.list_users();
630 assert_eq!(users.len(), 2);
631 assert!(users.contains(&&"user1".to_string()));
632 assert!(users.contains(&&"user2".to_string()));
633 }
634
635 #[test]
637 fn test_clear() {
638 let mut whitelist =
639 WhitelistManager::from_users(vec!["user1".to_string(), "user2".to_string()]);
640
641 assert!(!whitelist.is_allowed("other_user"));
642
643 whitelist.clear();
644
645 assert!(whitelist.is_empty());
646 assert!(!whitelist.is_enabled());
647 assert!(whitelist.is_allowed("other_user"));
648 assert!(whitelist.is_allowed("any_user"));
649 }
650
651 #[test]
653 fn test_add_duplicate_user() {
654 let mut whitelist = WhitelistManager::new();
655
656 whitelist.add_user("user1".to_string());
657 whitelist.add_user("user1".to_string());
658
659 assert_eq!(whitelist.len(), 1);
660 assert!(whitelist.is_allowed("user1"));
661 }
662
663 #[test]
665 fn test_default() {
666 let whitelist = WhitelistManager::default();
667 assert!(whitelist.is_empty());
668 assert!(whitelist.is_allowed("any_user"));
669 }
670
671 #[test]
673 fn test_clone() {
674 let mut original = WhitelistManager::from_users(vec!["user1".to_string()]);
675 let cloned = original.clone();
676
677 original.add_user("user2".to_string());
679
680 assert!(original.is_allowed("user2"));
681 assert!(!cloned.is_allowed("user2"));
682 }
683}