use faucet_conformance::{
assert_bounded_memory, assert_config_schema_valid_value, assert_connector_name_nonempty,
assert_errors_not_panics,
};
use faucet_source_snowflake::{SnowflakeAuth, SnowflakeSource, SnowflakeSourceConfig};
use serde_json::{Value, json};
use wiremock::matchers::{body_string_contains, method, path, query_param};
use wiremock::{Mock, MockServer, ResponseTemplate};
const HANDLE: &str = "conformance-handle";
const NUM_PARTITIONS: usize = 12;
const ROWS_PER_PARTITION: usize = 500;
fn metadata() -> Value {
let partition_info: Vec<Value> = (0..NUM_PARTITIONS)
.map(|_| json!({"rowCount": ROWS_PER_PARTITION}))
.collect();
json!({
"rowType": [
{"name": "ID", "type": "fixed"}
],
"partitionInfo": partition_info,
"format": "jsonv2",
"numRows": (NUM_PARTITIONS * ROWS_PER_PARTITION) as u64,
})
}
fn rows_for_partition(p: usize) -> Vec<Vec<Value>> {
(0..ROWS_PER_PARTITION)
.map(|i| vec![json!((p * ROWS_PER_PARTITION + i).to_string())])
.collect()
}
#[test]
fn conformance_config_schema_valid() {
let schema = serde_json::to_value(schemars::schema_for!(SnowflakeSourceConfig)).unwrap();
assert_config_schema_valid_value(&schema, "faucet-source-snowflake");
}
#[tokio::test(flavor = "multi_thread")]
async fn conformance_bounded_memory() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/api/v2/statements"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"code": "090001",
"statementHandle": HANDLE,
"resultSetMetaData": metadata(),
"data": rows_for_partition(0),
})))
.mount(&server)
.await;
for p in 1..NUM_PARTITIONS {
Mock::given(method("GET"))
.and(path(format!("/api/v2/statements/{HANDLE}")))
.and(query_param("partition", p.to_string()))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"code": "090001",
"data": rows_for_partition(p),
})))
.mount(&server)
.await;
}
let config = SnowflakeSourceConfig::new(
"xy12345",
"WH",
"DB",
"PUBLIC",
SnowflakeAuth::OAuth { token: "t".into() },
"SELECT id FROM events",
)
.with_batch_size(250);
let source = SnowflakeSource::new(config)
.expect("source new")
.with_endpoint_base(server.uri());
assert_bounded_memory(&source, 250, NUM_PARTITIONS * ROWS_PER_PARTITION).await;
}
#[tokio::test(flavor = "multi_thread")]
async fn conformance_discover_roundtrips() {
let server = MockServer::start().await;
let catalog_meta = json!({
"rowType": [
{"name": "TABLE_SCHEMA", "type": "text"},
{"name": "TABLE_NAME", "type": "text"},
{"name": "COLUMN_NAME", "type": "text"},
{"name": "DATA_TYPE", "type": "text"},
{"name": "IS_NULLABLE", "type": "text"},
{"name": "ROW_COUNT", "type": "fixed"},
],
"partitionInfo": [{"rowCount": 1}],
"format": "jsonv2",
"numRows": 1,
});
Mock::given(method("POST"))
.and(path("/api/v2/statements"))
.and(body_string_contains("information_schema"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"code": "090001",
"statementHandle": HANDLE,
"resultSetMetaData": catalog_meta,
"data": [["PUBLIC", "ORDERS", "ID", "NUMBER", "NO", "3"]],
})))
.mount(&server)
.await;
let data_meta = json!({
"rowType": [{"name": "ID", "type": "fixed"}],
"partitionInfo": [{"rowCount": 3}],
"format": "jsonv2",
"numRows": 3,
});
Mock::given(method("POST"))
.and(path("/api/v2/statements"))
.and(body_string_contains("ORDERS"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"code": "090001",
"statementHandle": "data-handle",
"resultSetMetaData": data_meta,
"data": [["0"], ["1"], ["2"]],
})))
.mount(&server)
.await;
let base = SnowflakeSourceConfig::new(
"xy12345",
"WH",
"DB",
"PUBLIC",
SnowflakeAuth::OAuth { token: "t".into() },
"SELECT 1",
);
let source = SnowflakeSource::new(base)
.expect("source new")
.with_endpoint_base(server.uri());
faucet_conformance::assert_discover_roundtrips(&source, |patch| {
let uri = server.uri();
async move {
let query = patch["query"].as_str().expect("query patch").to_string();
let cfg = SnowflakeSourceConfig::new(
"xy12345",
"WH",
"DB",
"PUBLIC",
SnowflakeAuth::OAuth { token: "t".into() },
query,
);
Box::new(
SnowflakeSource::new(cfg)
.expect("rebuilt source")
.with_endpoint_base(uri),
) as Box<dyn faucet_core::Source>
}
})
.await;
}
#[tokio::test]
async fn conformance_errors_not_panics() {
let config = SnowflakeSourceConfig::new(
"xy12345",
"WH",
"DB",
"PUBLIC",
SnowflakeAuth::OAuth { token: "t".into() },
"SELECT id FROM events",
);
let source = SnowflakeSource::new(config)
.expect("source new")
.with_endpoint_base("http://127.0.0.1:1");
assert_connector_name_nonempty(&source);
assert_errors_not_panics(&source).await;
}