1use devboy_core::{
2 Error, KnowledgeBaseProvider, MeetingNotesProvider, MessengerProvider, Provider, Result,
3 ToolEnricher,
4};
5
6use crate::context::{
7 ClickUpScope, ConfluenceAuthConfig, ConfluenceScope, GitHubScope, GitLabScope, JiraScope,
8 ProviderConfig, ProviderMetadata, ProxyConfig, SlackScope, TelegramScope,
9};
10
11pub fn create_provider(
21 config: &ProviderConfig,
22 proxy: Option<&ProxyConfig>,
23) -> Result<Box<dyn Provider>> {
24 match config {
25 ProviderConfig::GitLab {
26 base_url,
27 access_token,
28 scope,
29 ..
30 } => match scope {
31 GitLabScope::Project { id } => {
32 let client = if let Some(proxy) = proxy {
33 devboy_gitlab::GitLabClient::with_base_url(
34 &proxy.url,
35 id,
36 access_token.clone(),
37 )
38 .with_proxy(proxy.headers.clone())
39 } else {
40 devboy_gitlab::GitLabClient::with_base_url(
41 base_url,
42 id,
43 access_token.clone(),
44 )
45 };
46 Ok(Box::new(client))
47 }
48 GitLabScope::Group { id } => Err(Error::ProviderUnsupported {
49 provider: "gitlab".into(),
50 operation: format!("group scope (group_id: {id}) not yet implemented"),
51 }),
52 GitLabScope::Global => Err(Error::ProviderUnsupported {
53 provider: "gitlab".into(),
54 operation: "global scope not yet implemented".into(),
55 }),
56 },
57
58 ProviderConfig::GitHub {
59 base_url,
60 access_token,
61 scope,
62 ..
63 } => match scope {
64 GitHubScope::Repository { owner, repo } => {
65 Ok(Box::new(devboy_github::GitHubClient::with_base_url(
66 base_url,
67 owner,
68 repo,
69 access_token.clone(),
70 )))
71 }
72 GitHubScope::Organization { name } => Err(Error::ProviderUnsupported {
73 provider: "github".into(),
74 operation: format!("organization scope (org: {name}) not yet implemented"),
75 }),
76 GitHubScope::Global => Err(Error::ProviderUnsupported {
77 provider: "github".into(),
78 operation: "global scope not yet implemented".into(),
79 }),
80 },
81
82 ProviderConfig::ClickUp {
83 access_token,
84 scope,
85 ..
86 } => match scope {
87 ClickUpScope::List { id, team_id } => {
88 let mut client = devboy_clickup::ClickUpClient::new(id, access_token.clone());
89 if let Some(tid) = team_id {
90 client = client.with_team_id(tid);
91 }
92 Ok(Box::new(client))
93 }
94 },
95
96 ProviderConfig::Jira {
97 base_url,
98 access_token,
99 email,
100 scope,
101 flavor,
102 ..
103 } => match scope {
104 JiraScope::Project { key } => {
105 let mut client = if let Some(proxy) = proxy {
106 devboy_jira::JiraClient::new(
107 &proxy.url,
108 key,
109 email,
110 access_token.clone(),
111 )
112 .with_proxy(proxy.headers.clone())
113 .with_instance_url(base_url)
114 } else {
115 devboy_jira::JiraClient::new(base_url, key, email, access_token.clone())
116 };
117 if let Some(f) = flavor {
118 client = client.with_flavor(*f);
119 }
120 Ok(Box::new(client))
121 }
122 JiraScope::MultiProject { keys } => Err(Error::ProviderUnsupported {
123 provider: "jira".into(),
124 operation: format!(
125 "multi-project scope ({}) not yet implemented",
126 keys.join(", ")
127 ),
128 }),
129 },
130
131 ProviderConfig::Confluence { .. } => Err(Error::ProviderUnsupported {
132 provider: "confluence".into(),
133 operation: "Confluence is a KnowledgeBaseProvider, not a Provider. Use create_knowledge_base_provider() instead.".into(),
134 }),
135
136 ProviderConfig::Fireflies { .. } => Err(Error::ProviderUnsupported {
137 provider: "fireflies".into(),
138 operation: "Fireflies is a MeetingNotesProvider, not a Provider. Use create_meeting_notes_provider() instead.".into(),
139 }),
140
141 ProviderConfig::Slack { .. } => Err(Error::ProviderUnsupported {
142 provider: "slack".into(),
143 operation: "Slack is a MessengerProvider, not a Provider. Use create_messenger_provider() instead.".into(),
144 }),
145
146 ProviderConfig::Telegram { .. } => Err(Error::ProviderUnsupported {
147 provider: "telegram".into(),
148 operation: "Telegram is a MessengerProvider, not a Provider. Use create_messenger_provider() instead.".into(),
149 }),
150
151 ProviderConfig::Custom { name, .. } => Err(Error::ProviderNotFound(format!(
152 "custom provider '{name}' not yet supported"
153 ))),
154 }
155}
156
157pub fn create_knowledge_base_provider(
158 config: &ProviderConfig,
159 proxy: Option<&ProxyConfig>,
160) -> Result<Box<dyn KnowledgeBaseProvider>> {
161 match config {
162 ProviderConfig::Confluence {
163 base_url,
164 auth,
165 api_version,
166 scope: ConfluenceScope::Space { .. },
167 ..
168 } => {
169 let client = if let Some(proxy) = proxy {
170 devboy_confluence::ConfluenceClient::new(
171 &proxy.url,
172 devboy_confluence::ConfluenceAuth::None,
173 )
174 .with_api_version(api_version.as_deref())
175 .with_proxy(proxy.headers.clone())
176 .with_instance_url(base_url)
179 } else {
180 devboy_confluence::ConfluenceClient::new(base_url, confluence_auth(auth))
181 .with_api_version(api_version.as_deref())
182 };
183 Ok(Box::new(client))
184 }
185 other => Err(Error::ProviderUnsupported {
186 provider: other.provider_name().into(),
187 operation: "not a knowledge base provider".into(),
188 }),
189 }
190}
191
192pub fn create_knowledge_base_enricher(config: &ProviderConfig) -> Option<Box<dyn ToolEnricher>> {
198 match config {
199 ProviderConfig::Confluence { .. } => {
200 Some(Box::new(devboy_confluence::ConfluenceSchemaEnricher::new()))
201 }
202 _ => None,
203 }
204}
205
206pub fn create_meeting_notes_provider(
211 config: &ProviderConfig,
212) -> Result<Box<dyn MeetingNotesProvider>> {
213 match config {
214 ProviderConfig::Fireflies { api_key, .. } => Ok(Box::new(
215 devboy_fireflies::FirefliesClient::new(api_key.clone()),
216 )),
217 other => Err(Error::ProviderUnsupported {
218 provider: other.provider_name().into(),
219 operation: "not a meeting notes provider".into(),
220 }),
221 }
222}
223
224pub fn create_messenger_provider(config: &ProviderConfig) -> Result<Box<dyn MessengerProvider>> {
225 match config {
226 ProviderConfig::Slack {
227 base_url,
228 access_token,
229 scope: SlackScope::Workspace { .. },
230 required_scopes,
231 ..
232 } => Ok(Box::new(
233 devboy_slack::SlackClient::new(access_token.clone())
234 .with_base_url(base_url)
235 .with_required_scopes(required_scopes.clone()),
236 )),
237 ProviderConfig::Telegram {
238 base_url,
239 access_token,
240 scope: TelegramScope::Bot { bot_username },
241 ..
242 } => Ok(Box::new(
243 devboy_telegram::TelegramClient::new(access_token.clone())
244 .with_base_url(base_url)
245 .with_bot_username(bot_username.clone()),
246 )),
247 other => Err(Error::ProviderUnsupported {
248 provider: other.provider_name().into(),
249 operation: "not a messenger provider".into(),
250 }),
251 }
252}
253
254pub fn create_enricher(
261 config: &ProviderConfig,
262 metadata: Option<&ProviderMetadata>,
263) -> Option<Box<dyn ToolEnricher>> {
264 match config {
265 ProviderConfig::GitLab { .. } => Some(Box::new(devboy_gitlab::GitLabSchemaEnricher)),
266 ProviderConfig::GitHub { .. } => Some(Box::new(devboy_github::GitHubSchemaEnricher)),
267 ProviderConfig::ClickUp { .. } => {
268 let meta = metadata?;
269 let clickup_meta: devboy_clickup::ClickUpMetadata =
270 serde_json::from_value(meta.data.clone()).ok()?;
271 Some(Box::new(devboy_clickup::ClickUpSchemaEnricher::new(
272 clickup_meta,
273 )))
274 }
275 ProviderConfig::Jira { .. } => {
276 let meta = metadata?;
277 let jira_meta: devboy_jira::JiraMetadata =
278 serde_json::from_value(meta.data.clone()).ok()?;
279 Some(Box::new(devboy_jira::JiraSchemaEnricher::new(jira_meta)))
280 }
281 ProviderConfig::Confluence { .. } => None,
282 ProviderConfig::Fireflies { .. } => {
283 Some(Box::new(devboy_fireflies::FirefliesSchemaEnricher))
284 }
285 ProviderConfig::Slack { .. } => None,
286 ProviderConfig::Telegram { .. } => None,
287 ProviderConfig::Custom { .. } => None,
288 }
289}
290
291fn confluence_auth(auth: &ConfluenceAuthConfig) -> devboy_confluence::ConfluenceAuth {
292 match auth {
293 ConfluenceAuthConfig::BearerToken { token } => {
294 devboy_confluence::ConfluenceAuth::BearerToken(token.clone())
295 }
296 ConfluenceAuthConfig::Basic { username, password } => {
297 devboy_confluence::ConfluenceAuth::Basic {
298 username: username.clone(),
299 password: password.clone(),
300 }
301 }
302 }
303}
304
305#[cfg(test)]
306mod tests {
307 use super::*;
308 use crate::context::*;
309 use devboy_core::{IssueProvider, KnowledgeBaseProvider, MessengerProvider};
310 use httpmock::Method::GET;
311 use httpmock::MockServer;
312 use std::collections::HashMap;
313
314 #[test]
315 fn test_create_gitlab_project_provider() {
316 let config = ProviderConfig::GitLab {
317 base_url: "https://gitlab.com".into(),
318 access_token: "test-token".into(),
319 scope: GitLabScope::Project { id: "12345".into() },
320 extra: HashMap::new(),
321 };
322 let provider = create_provider(&config, None);
323 assert!(provider.is_ok());
324 assert_eq!(
325 IssueProvider::provider_name(provider.unwrap().as_ref()),
326 "gitlab"
327 );
328 }
329
330 #[test]
331 fn test_create_github_repo_provider() {
332 let config = ProviderConfig::GitHub {
333 base_url: "https://api.github.com".into(),
334 access_token: "ghp_test".into(),
335 scope: GitHubScope::Repository {
336 owner: "meteora-pro".into(),
337 repo: "devboy-tools".into(),
338 },
339 extra: HashMap::new(),
340 };
341 let provider = create_provider(&config, None);
342 assert!(provider.is_ok());
343 assert_eq!(
344 IssueProvider::provider_name(provider.unwrap().as_ref()),
345 "github"
346 );
347 }
348
349 #[test]
350 fn test_create_clickup_provider() {
351 let config = ProviderConfig::ClickUp {
352 access_token: "pk_test".into(),
353 scope: ClickUpScope::List {
354 id: "list123".into(),
355 team_id: Some("team456".into()),
356 },
357 extra: HashMap::new(),
358 };
359 let provider = create_provider(&config, None);
360 assert!(provider.is_ok());
361 assert_eq!(
362 IssueProvider::provider_name(provider.unwrap().as_ref()),
363 "clickup"
364 );
365 }
366
367 #[test]
368 fn test_create_telegram_provider() {
369 let config = ProviderConfig::Telegram {
370 base_url: "https://api.telegram.org".into(),
371 access_token: "bot-token".into(),
372 scope: TelegramScope::Bot {
373 bot_username: Some("devboy_bot".into()),
374 },
375 extra: HashMap::new(),
376 };
377 let provider = create_messenger_provider(&config);
378 assert!(provider.is_ok());
379 assert_eq!(
380 MessengerProvider::provider_name(provider.unwrap().as_ref()),
381 "telegram"
382 );
383 }
384
385 #[test]
386 fn test_create_jira_provider() {
387 let config = ProviderConfig::Jira {
388 base_url: "https://myorg.atlassian.net".into(),
389 access_token: "jira-token".into(),
390 email: "user@example.com".into(),
391 scope: JiraScope::Project { key: "PROJ".into() },
392 flavor: None,
393 extra: HashMap::new(),
394 };
395 let provider = create_provider(&config, None);
396 assert!(provider.is_ok());
397 assert_eq!(
398 IssueProvider::provider_name(provider.unwrap().as_ref()),
399 "jira"
400 );
401 }
402
403 #[test]
404 fn test_create_confluence_knowledge_base_provider() {
405 let config = ProviderConfig::Confluence {
406 base_url: "https://wiki.example.com".into(),
407 auth: ConfluenceAuthConfig::BearerToken {
408 token: "test-token".into(),
409 },
410 scope: ConfluenceScope::Space {
411 key: Some("ENG".into()),
412 },
413 api_version: Some("v1".into()),
414 extra: HashMap::new(),
415 };
416 let provider = create_knowledge_base_provider(&config, None);
417 assert!(provider.is_ok());
418 assert_eq!(
419 KnowledgeBaseProvider::provider_name(provider.unwrap().as_ref()),
420 "confluence"
421 );
422 }
423
424 #[test]
425 fn test_create_confluence_knowledge_base_enricher() {
426 let config = ProviderConfig::Confluence {
427 base_url: "https://wiki.example.com".into(),
428 auth: ConfluenceAuthConfig::BearerToken {
429 token: "test-token".into(),
430 },
431 scope: ConfluenceScope::Space {
432 key: Some("ENG".into()),
433 },
434 api_version: Some("v1".into()),
435 extra: HashMap::new(),
436 };
437 let enricher = create_knowledge_base_enricher(&config);
438 assert!(enricher.is_some());
439 assert_eq!(
440 enricher.unwrap().supported_categories(),
441 &[devboy_core::ToolCategory::KnowledgeBase]
442 );
443 }
444
445 #[test]
446 fn test_confluence_is_not_regular_provider() {
447 let config = ProviderConfig::Confluence {
448 base_url: "https://wiki.example.com".into(),
449 auth: ConfluenceAuthConfig::BearerToken {
450 token: "test-token".into(),
451 },
452 scope: ConfluenceScope::Space { key: None },
453 api_version: None,
454 extra: HashMap::new(),
455 };
456
457 let result = create_provider(&config, None);
458 assert!(matches!(
459 result,
460 Err(Error::ProviderUnsupported { provider, .. }) if provider == "confluence"
461 ));
462 }
463
464 #[test]
465 fn test_telegram_is_not_regular_provider() {
466 let config = ProviderConfig::Telegram {
467 base_url: "https://api.telegram.org".into(),
468 access_token: "bot-token".into(),
469 scope: TelegramScope::Bot {
470 bot_username: Some("devboy_bot".into()),
471 },
472 extra: HashMap::new(),
473 };
474
475 let result = create_provider(&config, None);
476 assert!(matches!(
477 result,
478 Err(Error::ProviderUnsupported { provider, .. }) if provider == "telegram"
479 ));
480 }
481
482 #[tokio::test]
483 async fn test_create_confluence_knowledge_base_provider_honors_api_version() {
484 let server = MockServer::start();
485 let mock = server.mock(|when, then| {
486 when.method(GET)
487 .path("/api/v2/space")
488 .query_param("limit", "100")
489 .query_param("type", "global,personal");
490 then.status(200)
491 .header("content-type", "application/json")
492 .body(r#"{"results":[],"start":0,"limit":100,"size":0,"_links":{}}"#);
493 });
494
495 let config = ProviderConfig::Confluence {
496 base_url: server.base_url(),
497 auth: ConfluenceAuthConfig::BearerToken {
498 token: "test-token".into(),
499 },
500 scope: ConfluenceScope::Space { key: None },
501 api_version: Some("v2".into()),
502 extra: HashMap::new(),
503 };
504
505 let provider = create_knowledge_base_provider(&config, None).unwrap();
506 let _ = provider.get_spaces().await.unwrap();
507
508 mock.assert();
509 }
510
511 #[test]
512 fn test_create_custom_provider_unsupported() {
513 let config = ProviderConfig::Custom {
514 name: "my-plugin".into(),
515 config: HashMap::new(),
516 };
517 let result = create_provider(&config, None);
518 assert!(result.is_err());
519 }
520
521 #[test]
522 fn test_gitlab_group_scope_unsupported() {
523 let config = ProviderConfig::GitLab {
524 base_url: "https://gitlab.com".into(),
525 access_token: "token".into(),
526 scope: GitLabScope::Group {
527 id: "group1".into(),
528 },
529 extra: HashMap::new(),
530 };
531 let result = create_provider(&config, None);
532 assert!(result.is_err());
533 }
534
535 #[test]
536 fn test_create_enricher_gitlab_static() {
537 let config = ProviderConfig::GitLab {
538 base_url: "https://gitlab.com".into(),
539 access_token: "token".into(),
540 scope: GitLabScope::Project { id: "123".into() },
541 extra: HashMap::new(),
542 };
543 let enricher = create_enricher(&config, None);
544 assert!(enricher.is_some());
545 }
546
547 #[test]
548 fn test_create_enricher_github_static() {
549 let config = ProviderConfig::GitHub {
550 base_url: "https://api.github.com".into(),
551 access_token: "token".into(),
552 scope: GitHubScope::Repository {
553 owner: "test".into(),
554 repo: "test".into(),
555 },
556 extra: HashMap::new(),
557 };
558 let enricher = create_enricher(&config, None);
559 assert!(enricher.is_some());
560 }
561
562 #[test]
563 fn test_create_enricher_clickup_needs_metadata() {
564 let config = ProviderConfig::ClickUp {
565 access_token: "token".into(),
566 scope: ClickUpScope::List {
567 id: "list1".into(),
568 team_id: None,
569 },
570 extra: HashMap::new(),
571 };
572 assert!(create_enricher(&config, None).is_none());
574
575 let meta = ProviderMetadata::new(serde_json::json!({
577 "statuses": [{ "name": "To Do" }],
578 "custom_fields": []
579 }));
580 assert!(create_enricher(&config, Some(&meta)).is_some());
581 }
582
583 #[test]
584 fn test_create_enricher_jira_needs_metadata() {
585 let config = ProviderConfig::Jira {
586 base_url: "https://test.atlassian.net".into(),
587 access_token: "token".into(),
588 email: "test@test.com".into(),
589 scope: JiraScope::Project { key: "PROJ".into() },
590 flavor: None,
591 extra: HashMap::new(),
592 };
593 assert!(create_enricher(&config, None).is_none());
595
596 let meta = ProviderMetadata::new(serde_json::json!({
598 "flavor": "cloud",
599 "projects": {
600 "PROJ": {
601 "issue_types": [],
602 "priorities": [],
603 "components": [],
604 "link_types": [],
605 "custom_fields": []
606 }
607 }
608 }));
609 assert!(create_enricher(&config, Some(&meta)).is_some());
610 }
611
612 #[test]
615 fn test_create_gitlab_provider_with_proxy() {
616 let config = ProviderConfig::GitLab {
617 base_url: "https://gitlab.internal.com".into(),
618 access_token: "test-token".into(),
619 scope: GitLabScope::Project { id: "99".into() },
620 extra: HashMap::new(),
621 };
622 let mut headers = HashMap::new();
623 headers.insert("X-Proxy-Auth".into(), "proxy-secret".into());
624 let proxy = ProxyConfig {
625 url: "https://proxy.example.com/gitlab".into(),
626 headers,
627 };
628 let provider = create_provider(&config, Some(&proxy));
629 assert!(provider.is_ok());
630 assert_eq!(
631 IssueProvider::provider_name(provider.unwrap().as_ref()),
632 "gitlab"
633 );
634 }
635
636 #[test]
637 fn test_create_jira_provider_with_proxy() {
638 let config = ProviderConfig::Jira {
639 base_url: "https://jira.mycompany.com".into(),
640 access_token: "jira-token".into(),
641 email: "dev@mycompany.com".into(),
642 scope: JiraScope::Project { key: "DEV".into() },
643 flavor: None,
644 extra: HashMap::new(),
645 };
646 let mut headers = HashMap::new();
647 headers.insert("X-Proxy-Auth".into(), "secret".into());
648 headers.insert("X-Route".into(), "jira-dc".into());
649 let proxy = ProxyConfig {
650 url: "https://proxy.internal/jira".into(),
651 headers,
652 };
653 let provider = create_provider(&config, Some(&proxy));
654 assert!(provider.is_ok());
655 assert_eq!(
656 IssueProvider::provider_name(provider.unwrap().as_ref()),
657 "jira"
658 );
659 }
660
661 #[test]
662 fn test_create_jira_provider_with_flavor_cloud() {
663 let config = ProviderConfig::Jira {
664 base_url: "https://myorg.atlassian.net".into(),
665 access_token: "tok".into(),
666 email: "a@b.com".into(),
667 scope: JiraScope::Project { key: "CLD".into() },
668 flavor: Some(devboy_jira::JiraFlavor::Cloud),
669 extra: HashMap::new(),
670 };
671 let provider = create_provider(&config, None);
672 assert!(provider.is_ok());
673 }
674
675 #[test]
676 fn test_create_jira_provider_with_flavor_self_hosted() {
677 let config = ProviderConfig::Jira {
678 base_url: "https://jira.local".into(),
679 access_token: "tok".into(),
680 email: "a@b.com".into(),
681 scope: JiraScope::Project { key: "SH".into() },
682 flavor: Some(devboy_jira::JiraFlavor::SelfHosted),
683 extra: HashMap::new(),
684 };
685 let provider = create_provider(&config, None);
686 assert!(provider.is_ok());
687 }
688
689 #[test]
690 fn test_create_jira_provider_with_proxy_and_flavor_override() {
691 let config = ProviderConfig::Jira {
692 base_url: "https://jira.dc.mycompany.com".into(),
693 access_token: "tok".into(),
694 email: "a@b.com".into(),
695 scope: JiraScope::Project { key: "DC".into() },
696 flavor: Some(devboy_jira::JiraFlavor::SelfHosted),
697 extra: HashMap::new(),
698 };
699 let proxy = ProxyConfig {
700 url: "https://proxy.internal/jira".into(),
701 headers: HashMap::new(),
702 };
703 let provider = create_provider(&config, Some(&proxy));
704 assert!(provider.is_ok());
705 }
706
707 #[test]
710 fn test_gitlab_global_scope_unsupported() {
711 let config = ProviderConfig::GitLab {
712 base_url: "https://gitlab.com".into(),
713 access_token: "tok".into(),
714 scope: GitLabScope::Global,
715 extra: HashMap::new(),
716 };
717 let result = create_provider(&config, None);
718 match result {
719 Err(e) => assert!(e.to_string().contains("global scope")),
720 Ok(_) => panic!("expected error for global scope"),
721 }
722 }
723
724 #[test]
725 fn test_github_organization_scope_unsupported() {
726 let config = ProviderConfig::GitHub {
727 base_url: "https://api.github.com".into(),
728 access_token: "tok".into(),
729 scope: GitHubScope::Organization {
730 name: "myorg".into(),
731 },
732 extra: HashMap::new(),
733 };
734 let result = create_provider(&config, None);
735 match result {
736 Err(e) => assert!(e.to_string().contains("organization scope")),
737 Ok(_) => panic!("expected error for organization scope"),
738 }
739 }
740
741 #[test]
742 fn test_github_global_scope_unsupported() {
743 let config = ProviderConfig::GitHub {
744 base_url: "https://api.github.com".into(),
745 access_token: "tok".into(),
746 scope: GitHubScope::Global,
747 extra: HashMap::new(),
748 };
749 let result = create_provider(&config, None);
750 assert!(result.is_err());
751 }
752
753 #[test]
754 fn test_jira_multi_project_scope_unsupported() {
755 let config = ProviderConfig::Jira {
756 base_url: "https://test.atlassian.net".into(),
757 access_token: "tok".into(),
758 email: "a@b.com".into(),
759 scope: JiraScope::MultiProject {
760 keys: vec!["A".into(), "B".into()],
761 },
762 flavor: None,
763 extra: HashMap::new(),
764 };
765 let result = create_provider(&config, None);
766 match result {
767 Err(e) => assert!(e.to_string().contains("multi-project")),
768 Ok(_) => panic!("expected error for multi-project scope"),
769 }
770 }
771
772 #[test]
775 fn test_create_clickup_provider_without_team_id() {
776 let config = ProviderConfig::ClickUp {
777 access_token: "pk_test".into(),
778 scope: ClickUpScope::List {
779 id: "list999".into(),
780 team_id: None,
781 },
782 extra: HashMap::new(),
783 };
784 let provider = create_provider(&config, None);
785 assert!(provider.is_ok());
786 assert_eq!(
787 IssueProvider::provider_name(provider.unwrap().as_ref()),
788 "clickup"
789 );
790 }
791
792 #[test]
795 fn test_create_enricher_custom_returns_none() {
796 let config = ProviderConfig::Custom {
797 name: "custom-plugin".into(),
798 config: HashMap::new(),
799 };
800 assert!(create_enricher(&config, None).is_none());
801 }
802
803 #[test]
804 fn test_create_enricher_telegram_returns_none() {
805 let config = ProviderConfig::Telegram {
806 base_url: "https://api.telegram.org".into(),
807 access_token: "bot-token".into(),
808 scope: TelegramScope::Bot {
809 bot_username: Some("devboy_bot".into()),
810 },
811 extra: HashMap::new(),
812 };
813 assert!(create_enricher(&config, None).is_none());
814 }
815
816 #[test]
817 fn test_create_knowledge_base_enricher_non_kb_returns_none() {
818 let config = ProviderConfig::GitHub {
819 base_url: "https://api.github.com".into(),
820 access_token: "tok".into(),
821 scope: GitHubScope::Repository {
822 owner: "test".into(),
823 repo: "test".into(),
824 },
825 extra: HashMap::new(),
826 };
827 assert!(create_knowledge_base_enricher(&config).is_none());
828 }
829
830 #[test]
833 fn test_create_enricher_clickup_invalid_metadata_returns_none() {
834 let config = ProviderConfig::ClickUp {
835 access_token: "token".into(),
836 scope: ClickUpScope::List {
837 id: "list1".into(),
838 team_id: None,
839 },
840 extra: HashMap::new(),
841 };
842 let meta = ProviderMetadata::new(serde_json::json!("invalid_data"));
843 assert!(create_enricher(&config, Some(&meta)).is_none());
844 }
845
846 #[test]
847 fn test_create_enricher_jira_invalid_metadata_returns_none() {
848 let config = ProviderConfig::Jira {
849 base_url: "https://test.atlassian.net".into(),
850 access_token: "token".into(),
851 email: "a@b.com".into(),
852 scope: JiraScope::Project { key: "X".into() },
853 flavor: None,
854 extra: HashMap::new(),
855 };
856 let meta = ProviderMetadata::new(serde_json::json!("not_valid"));
857 assert!(create_enricher(&config, Some(&meta)).is_none());
858 }
859}