use std::collections::HashMap;
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};
use super::*;
use crate::classify::sources::{
GithubIssuesSourceConfig, JiraFieldMappings, JiraSourceConfig, SourceConfig,
};
#[tokio::test]
async fn resolver_builds_from_empty_sources() {
let resolver = ExternalSourceResolver::new(&[]);
assert!(resolver.resolve("feat: add login").await.is_none());
}
#[tokio::test]
async fn resolver_returns_none_for_messages_without_keys() {
let config = JiraSourceConfig {
base_url: "https://acme.atlassian.net".to_string(),
token_env: "JIRA_API_TOKEN".to_string(),
username: None,
email_env: None,
project_keys: vec!["PROJ".to_string()],
field_mappings: JiraFieldMappings::default(),
};
let resolver = ExternalSourceResolver::new(&[SourceConfig::Jira(config)]);
let result = resolver.resolve("feat: add login flow").await;
assert!(result.is_none(), "no keys → no signal");
}
#[tokio::test]
async fn resolver_returns_jira_signal_for_bug_issue_type() {
let server = MockServer::start().await;
let body = serde_json::json!({
"key": "PROJ-1234",
"fields": {
"issuetype": {"name": "Bug"},
"labels": [],
"components": []
}
});
Mock::given(method("GET"))
.and(path("/rest/api/3/issue/PROJ-1234"))
.respond_with(ResponseTemplate::new(200).set_body_json(body))
.mount(&server)
.await;
unsafe { std::env::set_var("JIRA_API_TOKEN_TEST_BUG", "test-token") };
let mut issue_type_map = HashMap::new();
issue_type_map.insert("Bug".to_string(), "bug_fix".to_string());
let config = JiraSourceConfig {
base_url: server.uri(),
token_env: "JIRA_API_TOKEN_TEST_BUG".to_string(),
username: None,
email_env: None,
project_keys: vec![],
field_mappings: JiraFieldMappings {
issue_type: issue_type_map,
labels: HashMap::new(),
components: HashMap::new(),
},
};
let resolver = ExternalSourceResolver::new(&[SourceConfig::Jira(config)])
.with_jira_base_url(0, server.uri());
let signal = resolver
.resolve("PROJ-1234 fix null pointer")
.await
.expect("should have signal");
assert_eq!(signal.category, "bug_fix");
assert!(signal.source.contains("issue_type"));
unsafe { std::env::remove_var("JIRA_API_TOKEN_TEST_BUG") };
}
#[tokio::test]
async fn resolver_caches_jira_result_across_calls() {
let server = MockServer::start().await;
let body = serde_json::json!({
"key": "PROJ-99",
"fields": {
"issuetype": {"name": "Story"},
"labels": [],
"components": []
}
});
Mock::given(method("GET"))
.and(path("/rest/api/3/issue/PROJ-99"))
.respond_with(ResponseTemplate::new(200).set_body_json(body))
.expect(1)
.mount(&server)
.await;
unsafe { std::env::set_var("JIRA_API_TOKEN_CACHE_TEST", "test-token") };
let mut issue_type_map = HashMap::new();
issue_type_map.insert("Story".to_string(), "new_feature".to_string());
let config = JiraSourceConfig {
base_url: server.uri(),
token_env: "JIRA_API_TOKEN_CACHE_TEST".to_string(),
username: None,
email_env: None,
project_keys: vec![],
field_mappings: JiraFieldMappings {
issue_type: issue_type_map,
labels: HashMap::new(),
components: HashMap::new(),
},
};
let resolver = ExternalSourceResolver::new(&[SourceConfig::Jira(config)])
.with_jira_base_url(0, server.uri());
let s1 = resolver.resolve("PROJ-99 add widget").await;
assert!(s1.is_some());
let s2 = resolver.resolve("PROJ-99 related commit").await;
assert_eq!(s1, s2);
unsafe { std::env::remove_var("JIRA_API_TOKEN_CACHE_TEST") };
}
#[tokio::test]
async fn resolver_skips_jira_when_token_unset() {
unsafe { std::env::remove_var("JIRA_TOKEN_DEFINITELY_NOT_SET_XYZ") };
let config = JiraSourceConfig {
base_url: "https://acme.atlassian.net".to_string(),
token_env: "JIRA_TOKEN_DEFINITELY_NOT_SET_XYZ".to_string(),
username: None,
email_env: None,
project_keys: vec![],
field_mappings: JiraFieldMappings::default(),
};
let resolver = ExternalSourceResolver::new(&[SourceConfig::Jira(config)]);
let result = resolver.resolve("PROJ-1234 update").await;
assert!(result.is_none(), "missing token must yield None, not panic");
}
#[tokio::test]
async fn resolver_returns_github_signal_for_bug_label() {
let server = MockServer::start().await;
let body = serde_json::json!({
"number": 42,
"labels": [{"name": "bug"}, {"name": "help wanted"}]
});
Mock::given(method("GET"))
.and(path("/repos/acme/widgets/issues/42"))
.respond_with(ResponseTemplate::new(200).set_body_json(body))
.mount(&server)
.await;
unsafe { std::env::set_var("GITHUB_TOKEN_TEST_BUG", "test-token") };
let mut label_map = HashMap::new();
label_map.insert("bug".to_string(), "bug_fix".to_string());
let config = GithubIssuesSourceConfig {
repo: "acme/widgets".to_string(),
token_env: "GITHUB_TOKEN_TEST_BUG".to_string(),
label_mappings: label_map,
};
let resolver = ExternalSourceResolver::new(&[SourceConfig::GithubIssues(config)])
.with_github_api_base(0, server.uri());
let signal = resolver
.resolve("fix: closes #42")
.await
.expect("should have signal");
assert_eq!(signal.category, "bug_fix");
assert!(signal.source.contains("bug"));
unsafe { std::env::remove_var("GITHUB_TOKEN_TEST_BUG") };
}