use std::{sync::Arc, time::Duration};
use tokio::time::timeout;
use wiremock::{
matchers::{header, method, path, query_param},
Mock, ResponseTemplate,
};
use crate::{
client::{
api::GitlabApi,
config::{ClientConfig, PipelineQuery, ProjectQuery},
poller::GitlabPollerBuilder,
service::GitlabService,
},
id::{JobId, PipelineId, ProjectId},
};
use super::{
gitlab_error_response, jobs_json_response, pipelines_json_response, projects_json_response,
test_polling_config, MockServer,
};
#[tokio::test]
async fn test_api_get_projects_success() {
let mock_server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/projects"))
.and(header("PRIVATE-TOKEN", "test-token"))
.and(query_param("search_namespaces", "true"))
.respond_with(ResponseTemplate::new(200).set_body_json(&projects_json_response()))
.mount(&mock_server.server)
.await;
let config = mock_server.test_config();
let api = GitlabApi::new(config).unwrap();
let query = ProjectQuery::new().with_per_page(100);
let projects = api.get_projects(&query).await.unwrap();
assert_eq!(projects.len(), 1);
assert_eq!(projects[0].id, ProjectId::new(123));
assert_eq!(projects[0].path_with_namespace, "group/project");
}
#[tokio::test]
async fn test_api_get_projects_authentication_error() {
let mock_server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/projects"))
.and(header("PRIVATE-TOKEN", "test-token"))
.respond_with(
ResponseTemplate::new(401).set_body_json(&gitlab_error_response("invalid_token", None)),
)
.mount(&mock_server.server)
.await;
let config = mock_server.test_config();
let api = GitlabApi::new(config).unwrap();
let query = ProjectQuery::new();
let result = api.get_projects(&query).await;
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
crate::client::error::ClientError::Authentication
));
}
#[tokio::test]
async fn test_api_get_pipelines_success() {
let mock_server = MockServer::start().await;
let project_id = ProjectId::new(123);
Mock::given(method("GET"))
.and(path(format!("/projects/{}/pipelines", project_id)))
.and(header("PRIVATE-TOKEN", "test-token"))
.respond_with(ResponseTemplate::new(200).set_body_json(&pipelines_json_response()))
.mount(&mock_server.server)
.await;
let config = mock_server.test_config();
let api = GitlabApi::new(config).unwrap();
let query = PipelineQuery::new().with_per_page(60);
let pipelines = api.get_pipelines(project_id, &query).await.unwrap();
assert_eq!(pipelines.len(), 1);
assert_eq!(pipelines[0].id, PipelineId::new(456));
assert_eq!(pipelines[0].project_id, project_id);
}
#[tokio::test]
async fn test_api_get_jobs_success() {
let mock_server = MockServer::start().await;
let project_id = ProjectId::new(123);
let pipeline_id = PipelineId::new(456);
Mock::given(method("GET"))
.and(path(format!(
"/projects/{}/pipelines/{}/jobs",
project_id, pipeline_id
)))
.and(header("PRIVATE-TOKEN", "test-token"))
.respond_with(ResponseTemplate::new(200).set_body_json(&jobs_json_response()))
.mount(&mock_server.server)
.await;
Mock::given(method("GET"))
.and(path(format!(
"/projects/{}/pipelines/{}/bridges",
project_id, pipeline_id
)))
.and(header("PRIVATE-TOKEN", "test-token"))
.respond_with(ResponseTemplate::new(200).set_body_json(&serde_json::json!([])))
.mount(&mock_server.server)
.await;
let config = mock_server.test_config();
let api = GitlabApi::new(config).unwrap();
let jobs = api.get_jobs(project_id, pipeline_id).await.unwrap();
assert_eq!(jobs.len(), 1);
assert_eq!(jobs[0].id, JobId::new(789));
assert_eq!(jobs[0].name, "test-job");
}
#[tokio::test]
async fn test_api_get_job_trace_success() {
let mock_server = MockServer::start().await;
let project_id = ProjectId::new(123);
let job_id = JobId::new(789);
let trace_content = "Build started\nRunning tests\nBuild completed\n";
Mock::given(method("GET"))
.and(path(format!("/projects/{}/jobs/{}/trace", project_id, job_id)))
.and(header("PRIVATE-TOKEN", "test-token"))
.respond_with(ResponseTemplate::new(200).set_body_string(trace_content))
.mount(&mock_server.server)
.await;
let config = mock_server.test_config();
let api = GitlabApi::new(config).unwrap();
let trace = api.get_job_trace(project_id, job_id).await.unwrap();
assert_eq!(trace, trace_content);
}
#[tokio::test]
async fn test_api_validate_connection_success() {
let mock_server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/projects"))
.and(header("PRIVATE-TOKEN", "test-token"))
.and(query_param("per_page", "1"))
.respond_with(ResponseTemplate::new(200).set_body_json(&projects_json_response()))
.mount(&mock_server.server)
.await;
let config = mock_server.test_config();
let api = GitlabApi::new(config).unwrap();
let result = api.validate_connection().await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_service_fetch_projects_with_events() {
let mock_server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/projects"))
.and(header("PRIVATE-TOKEN", "test-token"))
.respond_with(ResponseTemplate::new(200).set_body_json(&projects_json_response()))
.mount(&mock_server.server)
.await;
let config = mock_server.test_config();
let (sender, receiver) = std::sync::mpsc::channel();
let service = GitlabService::new(config, sender).unwrap();
let result = service.fetch_projects(None).await;
assert!(result.is_ok());
let event = receiver.try_recv();
assert!(event.is_ok());
}
#[tokio::test]
async fn test_service_error_handling() {
let mock_server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/projects"))
.and(header("PRIVATE-TOKEN", "test-token"))
.respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
.mount(&mock_server.server)
.await;
let config = mock_server.test_config();
let (sender, receiver) = std::sync::mpsc::channel();
let service = GitlabService::new(config, sender).unwrap();
let result = service.fetch_projects(None).await;
assert!(result.is_err());
let event = receiver.try_recv();
assert!(event.is_ok());
}
#[tokio::test]
async fn test_poller_lifecycle() {
let mock_server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/projects"))
.respond_with(ResponseTemplate::new(200).set_body_json(&projects_json_response()))
.mount(&mock_server.server)
.await;
let config = mock_server.test_config();
let (sender, receiver) = std::sync::mpsc::channel();
let service = Arc::new(GitlabService::new(config, sender).unwrap());
let poller = GitlabPollerBuilder::new()
.service(service)
.config(test_polling_config())
.build()
.unwrap();
let shutdown_sender = poller.shutdown_sender();
let poller_task = tokio::spawn(async move {
poller.start().await
});
tokio::time::sleep(Duration::from_millis(200)).await;
let _ = shutdown_sender.send(());
let result = timeout(Duration::from_secs(1), poller_task).await;
assert!(result.is_ok());
assert!(result.unwrap().is_ok());
let mut event_count = 0;
while receiver.try_recv().is_ok() {
event_count += 1;
}
assert!(event_count > 0);
}
#[tokio::test]
async fn test_config_validation_in_integration() {
let result = GitlabApi::new(ClientConfig::new("not-a-url", "token"));
assert!(result.is_err());
let result = GitlabApi::new(ClientConfig::new("https://gitlab.com", ""));
assert!(result.is_err());
let result = GitlabApi::new(ClientConfig::new("https://gitlab.com", "token"));
assert!(result.is_ok());
}
#[tokio::test]
async fn test_url_building_with_query_params() {
let mock_server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/projects"))
.and(query_param("search", "frontend"))
.and(query_param("search_namespaces", "true"))
.and(query_param("statistics", "true"))
.and(query_param("archived", "false"))
.and(query_param("membership", "true"))
.and(query_param("per_page", "50"))
.respond_with(ResponseTemplate::new(200).set_body_json(&projects_json_response()))
.mount(&mock_server.server)
.await;
let config = mock_server.test_config();
let api = GitlabApi::new(config).unwrap();
let query = ProjectQuery::new()
.with_search_filter(Some("frontend".into()))
.with_per_page(50);
let result = api.get_projects(&query).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_concurrent_requests() {
let mock_server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/projects"))
.respond_with(ResponseTemplate::new(200).set_body_json(&projects_json_response()))
.mount(&mock_server.server)
.await;
Mock::given(method("GET"))
.and(path("/projects/123/pipelines"))
.respond_with(ResponseTemplate::new(200).set_body_json(&pipelines_json_response()))
.mount(&mock_server.server)
.await;
let config = mock_server.test_config();
let api = GitlabApi::new(config).unwrap();
let (projects_result, pipelines_result) = tokio::join!(
api.get_projects(&ProjectQuery::new()),
api.get_pipelines(ProjectId::new(123), &PipelineQuery::new())
);
assert!(projects_result.is_ok());
assert!(pipelines_result.is_ok());
assert_eq!(projects_result.unwrap().len(), 1);
assert_eq!(pipelines_result.unwrap().len(), 1);
}