#![cfg(not(target_os = "windows"))]
use faucet_conformance::{assert_config_schema_valid_value, assert_errors_not_panics};
use faucet_source_gcs::{GcsCredentials, GcsSource, GcsSourceConfig};
use testcontainers::{
GenericImage, ImageExt,
core::{IntoContainerPort, WaitFor},
runners::AsyncRunner,
};
#[test]
fn conformance_config_schema_valid() {
let schema = serde_json::to_value(schemars::schema_for!(GcsSourceConfig)).unwrap();
assert_config_schema_valid_value(&schema, "faucet-source-gcs");
}
async fn spawn_fake_gcs() -> Option<(String, String)> {
let image = GenericImage::new("fsouza/fake-gcs-server", "latest")
.with_exposed_port(4443.tcp())
.with_wait_for(WaitFor::message_on_stderr("server started at"))
.with_cmd(vec![
"-scheme=http".to_string(),
"-public-host=0.0.0.0:4443".to_string(),
]);
let container = match image.start().await {
Ok(c) => c,
Err(e) => {
eprintln!("Skipping: Docker not available ({e})");
return None;
}
};
let port = container.get_host_port_ipv4(4443).await.ok()?;
let host = format!("http://127.0.0.1:{port}");
let bucket = "faucet-conformance".to_string();
let client = reqwest::Client::new();
let resp = client
.post(format!("{host}/storage/v1/b"))
.json(&serde_json::json!({"name": bucket}))
.send()
.await
.ok()?;
if !resp.status().is_success() && resp.status() != reqwest::StatusCode::CONFLICT {
eprintln!("Skipping: could not create bucket ({})", resp.status());
return None;
}
std::mem::forget(container);
Some((host, bucket))
}
async fn seed_object(host: &str, bucket: &str, name: &str, body: &str, content_type: &str) {
let client = reqwest::Client::new();
let url = format!(
"{host}/upload/storage/v1/b/{bucket}/o?uploadType=media&name={}",
urlencoding::encode(name)
);
client
.post(url)
.header("Content-Type", content_type)
.body(body.to_string())
.send()
.await
.unwrap()
.error_for_status()
.unwrap();
}
fn jsonl_body(n: i64) -> String {
let mut out = String::new();
for i in 1..=n {
out.push_str(&format!("{{\"id\":{i}}}\n"));
}
out
}
#[tokio::test]
#[ignore = "requires a real GCS-compatible gRPC backend; fake-gcs-server only speaks REST. Run with `cargo test -- --ignored` against a live backend."]
async fn conformance_bounded_memory() {
let Some((host, bucket)) = spawn_fake_gcs().await else {
return;
};
seed_object(
&host,
&bucket,
"data/events.jsonl",
&jsonl_body(5_000),
"application/x-ndjson",
)
.await;
let config = GcsSourceConfig::new(&bucket)
.prefix("data/")
.auth(GcsCredentials::Anonymous)
.storage_host(&host)
.with_batch_size(250);
let source = GcsSource::new(config).await.unwrap();
faucet_conformance::assert_bounded_memory(&source, 250, 5_000).await;
}
#[tokio::test(flavor = "multi_thread")]
async fn conformance_errors_not_panics() {
let config = GcsSourceConfig::new("does-not-exist")
.prefix("data/")
.auth(GcsCredentials::Anonymous)
.storage_host("http://127.0.0.1:1")
.with_batch_size(250);
let source = GcsSource::new(config).await.expect("source builds lazily");
assert_errors_not_panics(&source).await;
}