Skip to main content

Firebase in rust


// 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