1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use thiserror::Error;
4
5use super::token_type::TokenType;
6
7#[derive(Debug, Error)]
8pub enum ProfileError {
9 #[error("Profile name already exists: {0}")]
10 DuplicateName(String),
11}
12
13#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
16pub struct Profile {
17 pub team_id: String,
18 pub user_id: String,
19 pub team_name: Option<String>,
20 pub user_name: Option<String>,
21 #[serde(skip_serializing_if = "Option::is_none")]
23 pub client_id: Option<String>,
24 #[serde(skip_serializing_if = "Option::is_none")]
26 pub redirect_uri: Option<String>,
27 #[serde(skip_serializing_if = "Option::is_none")]
29 pub scopes: Option<Vec<String>>,
30 #[serde(skip_serializing_if = "Option::is_none")]
32 pub bot_scopes: Option<Vec<String>>,
33 #[serde(skip_serializing_if = "Option::is_none")]
35 pub user_scopes: Option<Vec<String>>,
36 #[serde(skip_serializing_if = "Option::is_none")]
38 pub default_token_type: Option<TokenType>,
39}
40
41impl Profile {
42 pub fn get_bot_scopes(&self) -> Option<Vec<String>> {
44 self.bot_scopes.clone().or_else(|| self.scopes.clone())
45 }
46
47 pub fn get_user_scopes(&self) -> Option<Vec<String>> {
49 self.user_scopes.clone()
50 }
51
52 #[allow(clippy::too_many_arguments)]
54 pub fn with_scopes(
55 team_id: String,
56 user_id: String,
57 team_name: Option<String>,
58 user_name: Option<String>,
59 client_id: Option<String>,
60 redirect_uri: Option<String>,
61 bot_scopes: Option<Vec<String>>,
62 user_scopes: Option<Vec<String>>,
63 ) -> Self {
64 Self {
65 team_id,
66 user_id,
67 team_name,
68 user_name,
69 client_id,
70 redirect_uri,
71 scopes: None, bot_scopes,
73 user_scopes,
74 default_token_type: None,
75 }
76 }
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
81pub struct ProfilesConfig {
82 pub version: u32,
83 pub profiles: HashMap<String, Profile>,
84}
85
86impl ProfilesConfig {
87 pub fn new() -> Self {
88 Self {
89 version: 1,
90 profiles: HashMap::new(),
91 }
92 }
93
94 pub fn get(&self, name: &str) -> Option<&Profile> {
96 self.profiles.get(name)
97 }
98
99 pub fn set(&mut self, name: String, profile: Profile) {
102 self.profiles.insert(name, profile);
103 }
104
105 pub fn add(&mut self, name: String, profile: Profile) -> Result<(), ProfileError> {
107 if self.profiles.contains_key(&name) {
108 return Err(ProfileError::DuplicateName(name));
109 }
110 self.profiles.insert(name, profile);
111 Ok(())
112 }
113
114 pub fn set_or_update(&mut self, name: String, profile: Profile) -> Result<(), ProfileError> {
119 if let Some(existing) = self.profiles.get(&name) {
121 let existing_is_placeholder =
123 existing.team_id == "PLACEHOLDER" || existing.user_id == "PLACEHOLDER";
124 let profile_is_placeholder =
125 profile.team_id == "PLACEHOLDER" || profile.user_id == "PLACEHOLDER";
126
127 if existing_is_placeholder {
129 self.profiles.insert(name, profile);
130 return Ok(());
131 }
132
133 if profile_is_placeholder {
135 return Ok(());
136 }
137
138 if existing.team_id != profile.team_id || existing.user_id != profile.user_id {
140 return Err(ProfileError::DuplicateName(name));
141 }
142 self.profiles.insert(name, profile);
144 return Ok(());
145 }
146
147 if profile.team_id != "PLACEHOLDER" && profile.user_id != "PLACEHOLDER" {
150 if let Some((existing_name, _)) = self.profiles.iter().find(|(_, p)| {
151 p.team_id != "PLACEHOLDER"
152 && p.user_id != "PLACEHOLDER"
153 && p.team_id == profile.team_id
154 && p.user_id == profile.user_id
155 }) {
156 let existing_name = existing_name.clone();
158 self.profiles.insert(existing_name, profile);
159 return Ok(());
160 }
161 }
162
163 self.profiles.insert(name, profile);
165 Ok(())
166 }
167
168 pub fn remove(&mut self, name: &str) -> Option<Profile> {
170 self.profiles.remove(name)
171 }
172
173 pub fn list_names(&self) -> Vec<String> {
175 self.profiles.keys().cloned().collect()
176 }
177}
178
179impl Default for ProfilesConfig {
180 fn default() -> Self {
181 Self::new()
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn test_profiles_config_new() {
191 let config = ProfilesConfig::new();
192 assert_eq!(config.version, 1);
193 assert!(config.profiles.is_empty());
194 }
195
196 #[test]
197 fn test_profiles_config_get_set() {
198 let mut config = ProfilesConfig::new();
199 let profile = Profile {
200 team_id: "T123".to_string(),
201 user_id: "U456".to_string(),
202 team_name: Some("Test Team".to_string()),
203 user_name: Some("Test User".to_string()),
204 client_id: None,
205 redirect_uri: None,
206 scopes: None,
207 bot_scopes: None,
208 user_scopes: None,
209 default_token_type: None,
210 };
211
212 config.set("default".to_string(), profile.clone());
213 assert_eq!(config.get("default"), Some(&profile));
214 assert_eq!(config.get("nonexistent"), None);
215 }
216
217 #[test]
218 fn test_profiles_config_remove() {
219 let mut config = ProfilesConfig::new();
220 let profile = Profile {
221 team_id: "T123".to_string(),
222 user_id: "U456".to_string(),
223 team_name: None,
224 user_name: None,
225 client_id: None,
226 redirect_uri: None,
227 scopes: None,
228 bot_scopes: None,
229 user_scopes: None,
230 default_token_type: None,
231 };
232
233 config.set("test".to_string(), profile.clone());
234 let removed = config.remove("test");
235 assert_eq!(removed, Some(profile));
236 assert_eq!(config.get("test"), None);
237 }
238
239 #[test]
240 fn test_profiles_config_list_names() {
241 let mut config = ProfilesConfig::new();
242 config.set(
243 "profile1".to_string(),
244 Profile {
245 team_id: "T1".to_string(),
246 user_id: "U1".to_string(),
247 team_name: None,
248 user_name: None,
249 client_id: None,
250 redirect_uri: None,
251 scopes: None,
252 bot_scopes: None,
253 user_scopes: None,
254 default_token_type: None,
255 },
256 );
257 config.set(
258 "profile2".to_string(),
259 Profile {
260 team_id: "T2".to_string(),
261 user_id: "U2".to_string(),
262 team_name: None,
263 user_name: None,
264 client_id: None,
265 redirect_uri: None,
266 scopes: None,
267 bot_scopes: None,
268 user_scopes: None,
269 default_token_type: None,
270 },
271 );
272
273 let mut names = config.list_names();
274 names.sort();
275 assert_eq!(names, vec!["profile1", "profile2"]);
276 }
277
278 #[test]
279 fn test_profile_serialization() {
280 let profile = Profile {
281 team_id: "T123".to_string(),
282 user_id: "U456".to_string(),
283 team_name: Some("Test Team".to_string()),
284 user_name: Some("Test User".to_string()),
285 client_id: None,
286 redirect_uri: None,
287 scopes: None,
288 bot_scopes: None,
289 user_scopes: None,
290 default_token_type: None,
291 };
292
293 let json = serde_json::to_string(&profile).unwrap();
294 let deserialized: Profile = serde_json::from_str(&json).unwrap();
295 assert_eq!(profile, deserialized);
296 }
297
298 #[test]
299 fn test_profiles_config_serialization() {
300 let mut config = ProfilesConfig::new();
301 config.set(
302 "default".to_string(),
303 Profile {
304 team_id: "T123".to_string(),
305 user_id: "U456".to_string(),
306 team_name: Some("Test Team".to_string()),
307 user_name: Some("Test User".to_string()),
308 client_id: None,
309 redirect_uri: None,
310 scopes: None,
311 bot_scopes: None,
312 user_scopes: None,
313 default_token_type: None,
314 },
315 );
316
317 let json = serde_json::to_string_pretty(&config).unwrap();
318 let deserialized: ProfilesConfig = serde_json::from_str(&json).unwrap();
319 assert_eq!(config, deserialized);
320 }
321
322 #[test]
323 fn test_profiles_config_add_duplicate_name() {
324 let mut config = ProfilesConfig::new();
325 let profile1 = Profile {
326 team_id: "T123".to_string(),
327 user_id: "U456".to_string(),
328 team_name: None,
329 user_name: None,
330 client_id: None,
331 redirect_uri: None,
332 scopes: None,
333 bot_scopes: None,
334 user_scopes: None,
335 default_token_type: None,
336 };
337 let profile2 = Profile {
338 team_id: "T789".to_string(),
339 user_id: "U012".to_string(),
340 team_name: None,
341 user_name: None,
342 client_id: None,
343 redirect_uri: None,
344 scopes: None,
345 bot_scopes: None,
346 user_scopes: None,
347 default_token_type: None,
348 };
349
350 assert!(config.add("default".to_string(), profile1).is_ok());
352
353 let result = config.add("default".to_string(), profile2);
355 assert!(result.is_err());
356 match result {
357 Err(ProfileError::DuplicateName(name)) => {
358 assert_eq!(name, "default");
359 }
360 _ => panic!("Expected DuplicateName error"),
361 }
362 }
363
364 #[test]
365 fn test_profiles_config_set_or_update_new() {
366 let mut config = ProfilesConfig::new();
367 let profile = Profile {
368 team_id: "T123".to_string(),
369 user_id: "U456".to_string(),
370 team_name: Some("Test Team".to_string()),
371 user_name: Some("Test User".to_string()),
372 client_id: None,
373 redirect_uri: None,
374 scopes: None,
375 bot_scopes: None,
376 user_scopes: None,
377 default_token_type: None,
378 };
379
380 assert!(config
382 .set_or_update("default".to_string(), profile.clone())
383 .is_ok());
384 assert_eq!(config.get("default"), Some(&profile));
385 }
386
387 #[test]
388 fn test_profiles_config_set_or_update_same_identity() {
389 let mut config = ProfilesConfig::new();
390 let profile1 = Profile {
391 team_id: "T123".to_string(),
392 user_id: "U456".to_string(),
393 team_name: Some("Test Team".to_string()),
394 user_name: Some("Test User".to_string()),
395 client_id: None,
396 redirect_uri: None,
397 scopes: None,
398 bot_scopes: None,
399 user_scopes: None,
400 default_token_type: None,
401 };
402 let profile2 = Profile {
403 team_id: "T123".to_string(),
404 user_id: "U456".to_string(),
405 team_name: Some("Updated Team".to_string()),
406 user_name: Some("Updated User".to_string()),
407 client_id: None,
408 redirect_uri: None,
409 scopes: None,
410 bot_scopes: None,
411 user_scopes: None,
412 default_token_type: None,
413 };
414
415 config
416 .set_or_update("default".to_string(), profile1)
417 .unwrap();
418
419 assert!(config
421 .set_or_update("default".to_string(), profile2.clone())
422 .is_ok());
423 assert_eq!(config.get("default"), Some(&profile2));
424 }
425
426 #[test]
427 fn test_profiles_config_set_or_update_different_identity() {
428 let mut config = ProfilesConfig::new();
429 let profile1 = Profile {
430 team_id: "T123".to_string(),
431 user_id: "U456".to_string(),
432 team_name: None,
433 user_name: None,
434 client_id: None,
435 redirect_uri: None,
436 scopes: None,
437 bot_scopes: None,
438 user_scopes: None,
439 default_token_type: None,
440 };
441 let profile2 = Profile {
442 team_id: "T789".to_string(),
443 user_id: "U012".to_string(),
444 team_name: None,
445 user_name: None,
446 client_id: None,
447 redirect_uri: None,
448 scopes: None,
449 bot_scopes: None,
450 user_scopes: None,
451 default_token_type: None,
452 };
453
454 config
455 .set_or_update("default".to_string(), profile1)
456 .unwrap();
457
458 let result = config.set_or_update("default".to_string(), profile2);
460 assert!(result.is_err());
461 match result {
462 Err(ProfileError::DuplicateName(_)) => {}
463 _ => panic!("Expected DuplicateName error"),
464 }
465 }
466
467 #[test]
468 fn test_profiles_config_set_or_update_same_identity_different_name() {
469 let mut config = ProfilesConfig::new();
470 let profile1 = Profile {
471 team_id: "T123".to_string(),
472 user_id: "U456".to_string(),
473 team_name: Some("Test Team".to_string()),
474 user_name: Some("Test User".to_string()),
475 client_id: None,
476 redirect_uri: None,
477 scopes: None,
478 bot_scopes: None,
479 user_scopes: None,
480 default_token_type: None,
481 };
482 let profile2 = Profile {
483 team_id: "T123".to_string(),
484 user_id: "U456".to_string(),
485 team_name: Some("Updated Team".to_string()),
486 user_name: Some("Updated User".to_string()),
487 client_id: None,
488 redirect_uri: None,
489 scopes: None,
490 bot_scopes: None,
491 user_scopes: None,
492 default_token_type: None,
493 };
494
495 config.set_or_update("old".to_string(), profile1).unwrap();
496
497 assert!(config
499 .set_or_update("new".to_string(), profile2.clone())
500 .is_ok());
501
502 assert_eq!(config.get("old"), Some(&profile2));
504 assert_eq!(config.get("new"), None);
506 }
507
508 #[test]
509 fn test_backward_compatibility_profile_without_client_id() {
510 let json = r#"{
512 "version": 1,
513 "profiles": {
514 "default": {
515 "team_id": "T123",
516 "user_id": "U456",
517 "team_name": "Test Team",
518 "user_name": "Test User"
519 }
520 }
521 }"#;
522
523 let config: ProfilesConfig = serde_json::from_str(json).unwrap();
524 assert_eq!(config.version, 1);
525 assert_eq!(config.profiles.len(), 1);
526
527 let profile = config.get("default").unwrap();
528 assert_eq!(profile.team_id, "T123");
529 assert_eq!(profile.user_id, "U456");
530 assert_eq!(profile.client_id, None);
531 }
532
533 #[test]
534 fn test_profile_with_client_id_serialization() {
535 let profile = Profile {
537 team_id: "T123".to_string(),
538 user_id: "U456".to_string(),
539 team_name: Some("Test Team".to_string()),
540 user_name: Some("Test User".to_string()),
541 client_id: Some("client-123".to_string()),
542 redirect_uri: None,
543 scopes: None,
544 bot_scopes: None,
545 user_scopes: None,
546 default_token_type: None,
547 };
548
549 let json = serde_json::to_string(&profile).unwrap();
550 let deserialized: Profile = serde_json::from_str(&json).unwrap();
551
552 assert_eq!(profile, deserialized);
553 assert_eq!(deserialized.client_id, Some("client-123".to_string()));
554 }
555
556 #[test]
557 fn test_profile_without_client_id_omits_field() {
558 let profile = Profile {
560 team_id: "T123".to_string(),
561 user_id: "U456".to_string(),
562 team_name: Some("Test Team".to_string()),
563 user_name: Some("Test User".to_string()),
564 client_id: None,
565 redirect_uri: None,
566 scopes: None,
567 bot_scopes: None,
568 user_scopes: None,
569 default_token_type: None,
570 };
571
572 let json = serde_json::to_string(&profile).unwrap();
573 assert!(!json.contains("client_id"));
575 }
576
577 #[test]
578 fn test_profile_with_oauth_config_serialization() {
579 let profile = Profile {
581 team_id: "T123".to_string(),
582 user_id: "U456".to_string(),
583 team_name: Some("Test Team".to_string()),
584 user_name: Some("Test User".to_string()),
585 client_id: Some("client-123".to_string()),
586 redirect_uri: Some("http://127.0.0.1:8765/callback".to_string()),
587 scopes: Some(vec!["chat:write".to_string(), "users:read".to_string()]),
588 bot_scopes: None,
589 user_scopes: None,
590 default_token_type: None,
591 };
592
593 let json = serde_json::to_string(&profile).unwrap();
594 let deserialized: Profile = serde_json::from_str(&json).unwrap();
595
596 assert_eq!(profile, deserialized);
597 assert_eq!(deserialized.client_id, Some("client-123".to_string()));
598 assert_eq!(
599 deserialized.redirect_uri,
600 Some("http://127.0.0.1:8765/callback".to_string())
601 );
602 assert_eq!(
603 deserialized.scopes,
604 Some(vec!["chat:write".to_string(), "users:read".to_string()])
605 );
606 }
607
608 #[test]
609 fn test_profile_without_oauth_config_omits_fields() {
610 let profile = Profile {
612 team_id: "T123".to_string(),
613 user_id: "U456".to_string(),
614 team_name: Some("Test Team".to_string()),
615 user_name: Some("Test User".to_string()),
616 client_id: None,
617 redirect_uri: None,
618 scopes: None,
619 bot_scopes: None,
620 user_scopes: None,
621 default_token_type: None,
622 };
623
624 let json = serde_json::to_string(&profile).unwrap();
625 assert!(!json.contains("client_id"));
627 assert!(!json.contains("redirect_uri"));
628 assert!(!json.contains("scopes"));
629 }
630
631 #[test]
632 fn test_set_or_update_placeholder_to_real() {
633 let mut config = ProfilesConfig::new();
635
636 let placeholder_profile = Profile {
638 team_id: "PLACEHOLDER".to_string(),
639 user_id: "PLACEHOLDER".to_string(),
640 team_name: None,
641 user_name: None,
642 client_id: Some("client-123".to_string()),
643 redirect_uri: Some("http://localhost:8765/callback".to_string()),
644 scopes: Some(vec!["chat:write".to_string()]),
645 bot_scopes: None,
646 user_scopes: None,
647 default_token_type: None,
648 };
649
650 config
651 .set_or_update("work".to_string(), placeholder_profile)
652 .unwrap();
653
654 let real_profile = Profile {
656 team_id: "T123".to_string(),
657 user_id: "U456".to_string(),
658 team_name: Some("Real Team".to_string()),
659 user_name: Some("Real User".to_string()),
660 client_id: Some("client-123".to_string()),
661 redirect_uri: Some("http://localhost:8765/callback".to_string()),
662 scopes: Some(vec!["chat:write".to_string()]),
663 bot_scopes: None,
664 user_scopes: None,
665 default_token_type: None,
666 };
667
668 assert!(config
670 .set_or_update("work".to_string(), real_profile.clone())
671 .is_ok());
672
673 let updated = config.get("work").unwrap();
675 assert_eq!(updated.team_id, "T123");
676 assert_eq!(updated.user_id, "U456");
677 }
678
679 #[test]
680 fn test_set_or_update_real_to_placeholder_keeps_real() {
681 let mut config = ProfilesConfig::new();
683
684 let real_profile = Profile {
686 team_id: "T123".to_string(),
687 user_id: "U456".to_string(),
688 team_name: Some("Real Team".to_string()),
689 user_name: Some("Real User".to_string()),
690 client_id: Some("client-123".to_string()),
691 redirect_uri: Some("http://localhost:8765/callback".to_string()),
692 scopes: Some(vec!["chat:write".to_string()]),
693 bot_scopes: None,
694 user_scopes: None,
695 default_token_type: None,
696 };
697
698 config
699 .set_or_update("work".to_string(), real_profile.clone())
700 .unwrap();
701
702 let placeholder_profile = Profile {
704 team_id: "PLACEHOLDER".to_string(),
705 user_id: "PLACEHOLDER".to_string(),
706 team_name: None,
707 user_name: None,
708 client_id: Some("client-456".to_string()),
709 redirect_uri: None,
710 scopes: None,
711 bot_scopes: None,
712 user_scopes: None,
713 default_token_type: None,
714 };
715
716 assert!(config
718 .set_or_update("work".to_string(), placeholder_profile)
719 .is_ok());
720
721 let updated = config.get("work").unwrap();
723 assert_eq!(updated.team_id, "T123");
724 assert_eq!(updated.user_id, "U456");
725 }
726
727 #[test]
728 fn test_backward_compatibility_scopes_to_bot_scopes() {
729 let json = r#"{
731 "version": 1,
732 "profiles": {
733 "default": {
734 "team_id": "T123",
735 "user_id": "U456",
736 "team_name": "Test Team",
737 "user_name": "Test User",
738 "scopes": ["chat:write", "users:read"]
739 }
740 }
741 }"#;
742
743 let config: ProfilesConfig = serde_json::from_str(json).unwrap();
744 let profile = config.get("default").unwrap();
745
746 assert_eq!(
748 profile.get_bot_scopes(),
749 Some(vec!["chat:write".to_string(), "users:read".to_string()])
750 );
751 assert_eq!(profile.get_user_scopes(), None);
752 }
753
754 #[test]
755 fn test_new_profile_with_bot_and_user_scopes() {
756 let profile = Profile {
758 team_id: "T123".to_string(),
759 user_id: "U456".to_string(),
760 team_name: Some("Test Team".to_string()),
761 user_name: Some("Test User".to_string()),
762 client_id: Some("client-123".to_string()),
763 redirect_uri: Some("http://localhost:8765/callback".to_string()),
764 scopes: None,
765 bot_scopes: Some(vec!["chat:write".to_string()]),
766 user_scopes: Some(vec!["users:read".to_string()]),
767 default_token_type: None,
768 };
769
770 assert_eq!(
771 profile.get_bot_scopes(),
772 Some(vec!["chat:write".to_string()])
773 );
774 assert_eq!(
775 profile.get_user_scopes(),
776 Some(vec!["users:read".to_string()])
777 );
778 }
779
780 #[test]
781 fn test_backward_compatibility_profile_without_default_token_type() {
782 let json = r#"{
784 "version": 1,
785 "profiles": {
786 "default": {
787 "team_id": "T123",
788 "user_id": "U456",
789 "team_name": "Test Team",
790 "user_name": "Test User"
791 }
792 }
793 }"#;
794
795 let config: ProfilesConfig = serde_json::from_str(json).unwrap();
796 assert_eq!(config.version, 1);
797 assert_eq!(config.profiles.len(), 1);
798
799 let profile = config.get("default").unwrap();
800 assert_eq!(profile.team_id, "T123");
801 assert_eq!(profile.user_id, "U456");
802 assert_eq!(profile.default_token_type, None);
803 }
804
805 #[test]
806 fn test_profile_with_default_token_type_serialization() {
807 let profile = Profile {
809 team_id: "T123".to_string(),
810 user_id: "U456".to_string(),
811 team_name: Some("Test Team".to_string()),
812 user_name: Some("Test User".to_string()),
813 client_id: None,
814 redirect_uri: None,
815 scopes: None,
816 bot_scopes: None,
817 user_scopes: None,
818 default_token_type: Some(super::super::token_type::TokenType::Bot),
819 };
820
821 let json = serde_json::to_string(&profile).unwrap();
822 let deserialized: Profile = serde_json::from_str(&json).unwrap();
823
824 assert_eq!(profile.team_id, deserialized.team_id);
825 assert_eq!(
826 deserialized.default_token_type,
827 Some(super::super::token_type::TokenType::Bot)
828 );
829 }
830
831 #[test]
832 fn test_profile_without_default_token_type_omits_field() {
833 let profile = Profile {
835 team_id: "T123".to_string(),
836 user_id: "U456".to_string(),
837 team_name: Some("Test Team".to_string()),
838 user_name: Some("Test User".to_string()),
839 client_id: None,
840 redirect_uri: None,
841 scopes: None,
842 bot_scopes: None,
843 user_scopes: None,
844 default_token_type: None,
845 };
846
847 let json = serde_json::to_string(&profile).unwrap();
848 assert!(!json.contains("default_token_type"));
850 }
851
852 #[test]
853 fn test_set_or_update_placeholder_no_conflict_with_other_profiles() {
854 let mut config = ProfilesConfig::new();
856
857 let real_profile = Profile {
859 team_id: "T123".to_string(),
860 user_id: "U456".to_string(),
861 team_name: Some("Real Team".to_string()),
862 user_name: None,
863 client_id: None,
864 redirect_uri: None,
865 scopes: None,
866 bot_scopes: None,
867 user_scopes: None,
868 default_token_type: None,
869 };
870 config
871 .set_or_update("existing".to_string(), real_profile)
872 .unwrap();
873
874 let placeholder_profile = Profile {
876 team_id: "PLACEHOLDER".to_string(),
877 user_id: "PLACEHOLDER".to_string(),
878 team_name: None,
879 user_name: None,
880 client_id: Some("client-789".to_string()),
881 redirect_uri: None,
882 scopes: None,
883 bot_scopes: None,
884 user_scopes: None,
885 default_token_type: None,
886 };
887
888 assert!(config
890 .set_or_update("new".to_string(), placeholder_profile)
891 .is_ok());
892
893 assert!(config.get("existing").is_some());
895 assert!(config.get("new").is_some());
896 }
897}
898
899#[cfg(test)]
900mod backward_compat_tests {
901 use super::*;
902
903 #[test]
904 fn test_get_bot_scopes_from_legacy_scopes() {
905 let profile = Profile {
907 team_id: "T123".to_string(),
908 user_id: "U456".to_string(),
909 team_name: None,
910 user_name: None,
911 client_id: None,
912 redirect_uri: None,
913 scopes: Some(vec!["chat:write".to_string(), "users:read".to_string()]),
914 bot_scopes: None,
915 user_scopes: None,
916 default_token_type: None,
917 };
918
919 let bot_scopes = profile.get_bot_scopes();
920 assert_eq!(
921 bot_scopes,
922 Some(vec!["chat:write".to_string(), "users:read".to_string()])
923 );
924 }
925
926 #[test]
927 fn test_get_bot_scopes_prefers_bot_scopes_over_scopes() {
928 let profile = Profile {
930 team_id: "T123".to_string(),
931 user_id: "U456".to_string(),
932 team_name: None,
933 user_name: None,
934 client_id: None,
935 redirect_uri: None,
936 scopes: Some(vec!["old:scope".to_string()]),
937 bot_scopes: Some(vec!["new:scope".to_string()]),
938 user_scopes: None,
939 default_token_type: None,
940 };
941
942 let bot_scopes = profile.get_bot_scopes();
943 assert_eq!(bot_scopes, Some(vec!["new:scope".to_string()]));
944 }
945
946 #[test]
947 fn test_get_user_scopes_returns_none_for_legacy() {
948 let profile = Profile {
950 team_id: "T123".to_string(),
951 user_id: "U456".to_string(),
952 team_name: None,
953 user_name: None,
954 client_id: None,
955 redirect_uri: None,
956 scopes: Some(vec!["chat:write".to_string()]),
957 bot_scopes: None,
958 user_scopes: None,
959 default_token_type: None,
960 };
961
962 let user_scopes = profile.get_user_scopes();
963 assert_eq!(user_scopes, None);
964 }
965
966 #[test]
967 fn test_deserialize_old_profile_format() {
968 let json = r#"{
970 "team_id": "T123",
971 "user_id": "U456",
972 "team_name": "Test Team",
973 "user_name": "Test User",
974 "scopes": ["chat:write", "users:read"]
975 }"#;
976
977 let profile: Profile = serde_json::from_str(json).unwrap();
978 assert_eq!(profile.team_id, "T123");
979 assert_eq!(profile.user_id, "U456");
980 assert_eq!(
981 profile.scopes,
982 Some(vec!["chat:write".to_string(), "users:read".to_string()])
983 );
984 assert_eq!(profile.bot_scopes, None);
985 assert_eq!(profile.user_scopes, None);
986
987 let bot_scopes = profile.get_bot_scopes();
989 assert_eq!(
990 bot_scopes,
991 Some(vec!["chat:write".to_string(), "users:read".to_string()])
992 );
993 }
994}