use serde_json::Value;
use super::http::MockHttpResponse;
use super::server::MockSqlApi;
macro_rules! kx6_fixture {
($name:literal) => {
include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/fixtures/sqlapi/",
$name
))
};
}
pub const SUBMIT_SELECT_REQUEST: &[u8] = kx6_fixture!("submit_select_request.json");
pub const SUBMIT_WITH_BINDINGS_REQUEST: &[u8] = kx6_fixture!("submit_with_bindings_request.json");
pub const RESP_200_SINGLE: &[u8] = kx6_fixture!("resp_200_resultset_single_partition.json");
pub const RESP_200_MULTI: &[u8] = kx6_fixture!("resp_200_resultset_multi_partition.json");
pub const RESP_200_MULTI_STATEMENT: &[u8] = kx6_fixture!("resp_200_multi_statement.json");
pub const RESP_202_RUNNING: &[u8] = kx6_fixture!("resp_202_running.json");
pub const RESP_408_TIMEOUT: &[u8] = kx6_fixture!("resp_408_statement_timeout.json");
pub const RESP_422_FAILURE: &[u8] = kx6_fixture!("resp_422_failure.json");
pub const CANCEL_RESPONSE: &[u8] = kx6_fixture!("cancel_response.json");
pub const RESP_429_RATE_LIMITED: &[u8] =
br#"{"code":"429","message":"Request rate limit exceeded. Back off and retry."}"#;
pub const PARTITION_1_GZIP: &[u8] = include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/fixtures/packets/partition_1.json.gz"
));
pub const PARTITION_1_PLAIN: &[u8] = br#"{"data":[["3","gamma"],["4","delta"]]}"#;
pub const PARTITION_BARE_ARRAY_FALLBACK: &[u8] = br#"[["5","epsilon"]]"#;
pub const DEFAULT_HANDLE: &str = "01b2c3d4-0000-0000-0000-000000000002";
#[must_use]
pub fn ok_single_partition() -> MockHttpResponse {
MockHttpResponse::json(200, RESP_200_SINGLE.to_vec())
}
#[must_use]
pub fn ok_multi_partition() -> MockHttpResponse {
MockHttpResponse::json(200, RESP_200_MULTI.to_vec())
}
#[must_use]
pub fn multi_statement() -> MockHttpResponse {
MockHttpResponse::json(200, RESP_200_MULTI_STATEMENT.to_vec())
}
#[must_use]
pub fn running() -> MockHttpResponse {
MockHttpResponse::json(202, RESP_202_RUNNING.to_vec())
}
#[must_use]
pub fn statement_timeout() -> MockHttpResponse {
MockHttpResponse::json(408, RESP_408_TIMEOUT.to_vec())
}
#[must_use]
pub fn statement_failed() -> MockHttpResponse {
MockHttpResponse::json(422, RESP_422_FAILURE.to_vec())
}
#[must_use]
pub fn rate_limited() -> MockHttpResponse {
MockHttpResponse::json(429, RESP_429_RATE_LIMITED.to_vec())
}
#[must_use]
pub fn gzip_partition() -> MockHttpResponse {
MockHttpResponse::gzip_json(200, PARTITION_1_GZIP.to_vec())
}
#[must_use]
pub fn cancel() -> MockHttpResponse {
MockHttpResponse::json(200, CANCEL_RESPONSE.to_vec())
}
#[must_use]
pub fn default_async_lifecycle() -> MockSqlApi {
MockSqlApi::new(DEFAULT_HANDLE, running(), ok_single_partition(), cancel())
.with_polls_before_complete(2)
.with_partition(1, gzip_partition())
}
#[must_use]
pub fn poll_progression() -> Vec<MockHttpResponse> {
vec![running(), running(), ok_single_partition()]
}
pub fn ok_single_partition_typed() -> Result<Value, serde_json::Error> {
serde_json::from_slice(RESP_200_SINGLE)
}
pub fn running_typed() -> Result<Value, serde_json::Error> {
serde_json::from_slice(RESP_202_RUNNING)
}
pub fn failure_typed(body: &[u8]) -> Result<Value, serde_json::Error> {
serde_json::from_slice(body)
}
pub fn cancel_typed() -> Result<Value, serde_json::Error> {
serde_json::from_slice(CANCEL_RESPONSE)
}
#[cfg(test)]
mod tests {
use super::super::http::ResponseClass;
use super::*;
#[test]
fn embedded_kx6_bodies_parse_into_their_schemas() -> Result<(), String> {
let result = ok_single_partition_typed().map_err(|e| e.to_string())?;
assert_eq!(result["resultSetMetaData"]["numRows"], 2);
let status = running_typed().map_err(|e| e.to_string())?;
assert_eq!(status["statementHandle"], DEFAULT_HANDLE);
let timeout = failure_typed(RESP_408_TIMEOUT).map_err(|e| e.to_string())?;
assert!(
timeout["message"]
.as_str()
.is_some_and(|message| !message.is_empty())
);
cancel_typed().map_err(|e| e.to_string())?;
Ok(())
}
#[test]
fn status_codes_classify_distinctly() {
assert_eq!(running().class(), ResponseClass::Running);
assert_eq!(ok_single_partition().class(), ResponseClass::Completed);
assert_eq!(statement_timeout().class(), ResponseClass::StatementTimeout);
assert_eq!(statement_failed().class(), ResponseClass::StatementFailed);
assert_eq!(rate_limited().class(), ResponseClass::RateLimited);
}
#[test]
fn canonical_partition_fixture_is_the_live_object_form() -> Result<(), String> {
assert!(PARTITION_1_PLAIN.starts_with(br#"{"data":"#));
assert!(PARTITION_BARE_ARRAY_FALLBACK.starts_with(b"[["));
use asupersync::http::compress::{DecompressionLimit, Decompressor, GzipDecompressor};
let mut decompressor = GzipDecompressor::new(DecompressionLimit::new(4096));
let mut plain = Vec::new();
decompressor
.decompress(PARTITION_1_GZIP, &mut plain)
.and_then(|()| decompressor.finish(&mut plain))
.map_err(|e| format!("gzip packet decodes: {e}"))?;
assert_eq!(plain.as_slice(), PARTITION_1_PLAIN);
Ok(())
}
#[test]
fn gzip_partition_packet_is_deterministic_and_advertised() {
let response = gzip_partition();
assert!(response.has_header("Content-Encoding"));
assert_eq!(&response.body[0..2], &[0x1f, 0x8b]);
assert_eq!(&response.body[4..8], &[0x00, 0x00, 0x00, 0x00]);
}
}