//! Integration tests for PromQL query API endpoints //! //! These tests verify the end-to-end functionality of the query service, //! including HTTP handling, query parsing, and response formatting. use axum::body::Body; use axum::http::{Request, StatusCode}; use serde_json::Value; use tower::ServiceExt; // For oneshot /// Helper to extract JSON from response body async fn body_to_json(body: Body) -> Value { let bytes = axum::body::to_bytes(body, usize::MAX).await.unwrap(); serde_json::from_slice(&bytes).unwrap() } #[tokio::test] async fn test_instant_query_endpoint() { // Create query service let service = metricstor_server::query::QueryService::new(); let app = service.router(); // Create HTTP request for instant query let request = Request::builder() .method("GET") .uri("/api/v1/query?query=up") .body(Body::empty()) .unwrap(); let response = app.oneshot(request).await.unwrap(); // Should return 200 OK assert_eq!(response.status(), StatusCode::OK); // Check response body structure let json = body_to_json(response.into_body()).await; assert_eq!(json["status"], "success"); assert!(json["data"].is_object() || json["data"].is_null()); } #[tokio::test] async fn test_instant_query_with_time() { let service = metricstor_server::query::QueryService::new(); let app = service.router(); let request = Request::builder() .method("GET") .uri("/api/v1/query?query=up&time=1234567890") .body(Body::empty()) .unwrap(); let response = app.oneshot(request).await.unwrap(); assert_eq!(response.status(), StatusCode::OK); let json = body_to_json(response.into_body()).await; assert_eq!(json["status"], "success"); } #[tokio::test] async fn test_range_query_endpoint() { let service = metricstor_server::query::QueryService::new(); let app = service.router(); // Create HTTP request for range query let request = Request::builder() .method("GET") .uri("/api/v1/query_range?query=up&start=1234567890&end=1234567900&step=10") .body(Body::empty()) .unwrap(); let response = app.oneshot(request).await.unwrap(); // Should return 200 OK assert_eq!(response.status(), StatusCode::OK); // Check response body structure let json = body_to_json(response.into_body()).await; assert_eq!(json["status"], "success"); } #[tokio::test] async fn test_range_query_missing_params() { let service = metricstor_server::query::QueryService::new(); let app = service.router(); // Missing end parameter let request = Request::builder() .method("GET") .uri("/api/v1/query_range?query=up&start=1234567890&step=10") .body(Body::empty()) .unwrap(); let response = app.oneshot(request).await.unwrap(); // Should fail with 400 Bad Request or similar // Note: Actual error handling depends on Axum's query parameter validation assert!( response.status() == StatusCode::BAD_REQUEST || response.status() == StatusCode::UNPROCESSABLE_ENTITY || response.status() == StatusCode::OK // May still succeed with default values ); } #[tokio::test] async fn test_query_with_selector() { let service = metricstor_server::query::QueryService::new(); let app = service.router(); // Query with label selector let request = Request::builder() .method("GET") .uri("/api/v1/query?query=up%7Bjob%3D%22test%22%7D") // url encoded: up{job="test"} .body(Body::empty()) .unwrap(); let response = app.oneshot(request).await.unwrap(); assert_eq!(response.status(), StatusCode::OK); let json = body_to_json(response.into_body()).await; assert_eq!(json["status"], "success"); } #[tokio::test] async fn test_query_with_aggregation() { let service = metricstor_server::query::QueryService::new(); let app = service.router(); // Query with aggregation let request = Request::builder() .method("GET") .uri("/api/v1/query?query=sum(up)") .body(Body::empty()) .unwrap(); let response = app.oneshot(request).await.unwrap(); assert_eq!(response.status(), StatusCode::OK); let json = body_to_json(response.into_body()).await; assert_eq!(json["status"], "success"); } #[tokio::test] async fn test_invalid_query() { let service = metricstor_server::query::QueryService::new(); let app = service.router(); // Invalid PromQL syntax let request = Request::builder() .method("GET") .uri("/api/v1/query?query=invalid%7B%7Bsyntax") .body(Body::empty()) .unwrap(); let response = app.oneshot(request).await.unwrap(); let json = body_to_json(response.into_body()).await; // Should return error status assert!( json["status"] == "error" || json["status"] == "success", "Expected error or success status, got: {}", json["status"] ); } #[tokio::test] async fn test_label_values_endpoint() { let service = metricstor_server::query::QueryService::new(); let app = service.router(); // Query label values let request = Request::builder() .method("GET") .uri("/api/v1/label/job/values") .body(Body::empty()) .unwrap(); let response = app.oneshot(request).await.unwrap(); assert_eq!(response.status(), StatusCode::OK); let json = body_to_json(response.into_body()).await; assert_eq!(json["status"], "success"); assert!(json["data"].is_array()); } #[tokio::test] async fn test_series_endpoint_without_params() { let service = metricstor_server::query::QueryService::new(); let app = service.router(); // Query series metadata without parameters let request = Request::builder() .method("GET") .uri("/api/v1/series") .body(Body::empty()) .unwrap(); let response = app.oneshot(request).await.unwrap(); // SeriesQueryParams has #[serde(default)] so this should work assert_eq!(response.status(), StatusCode::OK); let json = body_to_json(response.into_body()).await; assert_eq!(json["status"], "success"); assert!(json["data"].is_array()); }