- Replace form_urlencoded with RFC 3986 compliant URI encoding - Implement aws_uri_encode() matching AWS SigV4 spec exactly - Unreserved chars (A-Z,a-z,0-9,-,_,.,~) not encoded - All other chars percent-encoded with uppercase hex - Preserve slashes in paths, encode in query params - Normalize empty paths to '/' per AWS spec - Fix test expectations (body hash, HMAC values) - Add comprehensive SigV4 signature determinism test This fixes the canonicalization mismatch that caused signature validation failures in T047. Auth can now be enabled for production. Refs: T058.S1
204 lines
6.1 KiB
Rust
204 lines
6.1 KiB
Rust
//! 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 = nightlight_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 = nightlight_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 = nightlight_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 = nightlight_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 = nightlight_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 = nightlight_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 = nightlight_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 = nightlight_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 = nightlight_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());
|
|
}
|