// Minimal Firestore integration smoke test.
// This test compiles and runs under `cargo test`. It will attempt a small
// credentials check when `GOOGLE_APPLICATION_CREDENTIALS` is set, otherwise
// it will print a message and return immediately (so CI without credentials
// doesn't fail).
#[cfg(test)]
mod tests {
use std::env;
// Use the tokio test runtime which is already a dependency in the project.
// Minimal Firestore integration tests.
// Tests will skip when `GOOGLE_APPLICATION_CREDENTIALS` is not set.
#[tokio::test]
async fn firestore_smoke_credentials_check() {
if let Ok(path) = env::var("GOOGLE_APPLICATION_CREDENTIALS") {
// If the credentials env var is set, ensure the file is readable.
match tokio::fs::metadata(&path).await {
Ok(meta) => {
assert!(meta.is_file(), "GOOGLE_APPLICATION_CREDENTIALS is not a file");
}
Err(e) => panic!("Cannot read GOOGLE_APPLICATION_CREDENTIALS file '{}': {}", path, e),
}
} else {
// No credentials provided: skip the integration portion.
eprintln!("Skipping Firestore integration test: set GOOGLE_APPLICATION_CREDENTIALS to run it");
}
}
// Full write/read/delete integration test using Firestore REST API and ADC token.
// This test requires `gcloud` to be available in PATH and `GOOGLE_APPLICATION_CREDENTIALS`.
#[tokio::test]
async fn firestore_write_read_delete() {
let key_path = match env::var("GOOGLE_APPLICATION_CREDENTIALS") {
Ok(p) => p,
Err(_) => {
eprintln!("Skipping Firestore integration test: set GOOGLE_APPLICATION_CREDENTIALS to run it");
return;
}
};
// Read project_id from the key file
let key_contents = match tokio::fs::read_to_string(&key_path).await {
Ok(s) => s,
Err(e) => panic!("failed to read {}: {}", key_path, e),
};
let key_json: JsonValue = serde_json::from_str(&key_contents).expect("invalid service account json");
let project_id = key_json
.get("project_id")
.and_then(|v| v.as_str())
.expect("project_id missing in service account json");
// Get an access token via gcloud ADC
let out = Command::new("gcloud")
.args(&["auth", "application-default", "print-access-token"])
.output()
.await
.expect("failed to run gcloud to fetch access token");
if !out.status.success() {
panic!("gcloud returned error: {}", String::from_utf8_lossy(&out.stderr));
}
let token = String::from_utf8(out.stdout).expect("invalid utf8 from gcloud").trim().to_string();
let client = Client::new();
let collection = "test_integration_collection";
let doc_id = Uuid::new_v4().to_string();
let create_url = format!(
"https://firestore.googleapis.com/v1/projects/{}/databases/(default)/documents/{}?documentId={}",
project_id, collection, doc_id
);
let body = serde_json::json!({
"fields": {
"hello": { "stringValue": "world" },
"count": { "integerValue": "42" }
}
});
let resp = client
.post(&create_url)
.bearer_auth(&token)
.json(&body)
.send()
.await
.expect("failed to send create request");
if !resp.status().is_success() {
let text = resp.text().await.unwrap_or_default();
panic!("create failed: {} - {}", resp.status(), text);
}
// small delay to allow document to be visible
tokio::time::sleep(Duration::from_millis(200)).await;
let get_url = format!(
"https://firestore.googleapis.com/v1/projects/{}/databases/(default)/documents/{}/{}",
project_id, collection, doc_id
);
let resp = client
.get(&get_url)
.bearer_auth(&token)
.send()
.await
.expect("failed to send get request");
if !resp.status().is_success() {
let text = resp.text().await.unwrap_or_default();
panic!("get failed: {} - {}", resp.status(), text);
}
let json: JsonValue = resp.json().await.expect("invalid json from get");
let hello = json
.get("fields")
.and_then(|f| f.get("hello"))
.and_then(|h| h.get("stringValue"))
.and_then(|v| v.as_str())
.expect("missing hello field");
assert_eq!(hello, "world");
// cleanup: delete the document
let _ = client
.delete(&get_url)
.bearer_auth(&token)
.send()
.await;
}
println!("firebase database successfully working");
}
Comments
Post a Comment