squarecloud 0.1.1

Async Rust client for the SquareCloud API
Documentation
use serde_json::json;
use squarecloud::types::CredentialType;
use wiremock::matchers::{method, path};
use wiremock::{Mock, ResponseTemplate};

#[tokio::test]
async fn database_info_returns_info() {
    let (client, server) = crate::mock_client().await;
    Mock::given(method("GET"))
        .and(path("/databases/db-123"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "status": "success",
            "response": {
                "id": "db-123",
                "name": "my-db",
                "owner": "user-123",
                "cluster": "florida-1",
                "ram": 256,
                "type": "postgresql",
                "port": 5432,
                "created_at": "2024-01-01T00:00:00Z"
            }
        })))
        .mount(&server)
        .await;

    let info = client
        .database("db-123")
        .info()
        .await
        .expect("database info() should succeed with mocked 200");
    assert_eq!(info.id, "db-123");
    assert_eq!(info.name, "my-db");
    assert_eq!(info.ram, 256);
}

#[tokio::test]
async fn database_status_returns_runtime_stats() {
    let (client, server) = crate::mock_client().await;
    Mock::given(method("GET"))
        .and(path("/databases/db-123/status"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "status": "success",
            "response": {
                "cpu": "1.5%",
                "ram": "64/256MB",
                "status": "running",
                "running": true,
                "storage": "10MB",
                "network": { "total": "1MB", "now": "0KB" },
                "uptime": null
            }
        })))
        .mount(&server)
        .await;

    let status = client
        .database("db-123")
        .status()
        .await
        .expect("database status() should succeed with mocked 200");
    assert!(!status.cpu.is_empty());
    assert!(!status.ram.is_empty());
    assert!(!status.status.is_empty());
}

#[tokio::test]
async fn database_metrics_returns_vec() {
    let (client, server) = crate::mock_client().await;
    Mock::given(method("GET"))
        .and(path("/databases/db-123/metrics"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "status": "success",
            "response": [
                {
                    "date": "2024-01-01T00:00:00Z",
                    "cpu": 1.5,
                    "ram": 25.0,
                    "net": [1000, 2000]
                }
            ]
        })))
        .mount(&server)
        .await;

    let result = client.database("db-123").metrics().await;
    assert!(result.is_ok(), "metrics() failed: {:?}", result.err());
    assert_eq!(result.expect("metrics() should return vec").len(), 1);
}

#[tokio::test]
async fn database_edit_name() {
    let (client, server) = crate::mock_client().await;
    Mock::given(method("PATCH"))
        .and(path("/databases/db-123"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "status": "success"
        })))
        .mount(&server)
        .await;

    let result = client.database("db-123").edit(Some("new-name"), None).await;
    assert!(result.is_ok(), "edit() failed: {:?}", result.err());
    assert!(result.expect("edit(name) should return true"));
}

#[tokio::test]
async fn database_edit_ram() {
    let (client, server) = crate::mock_client().await;
    Mock::given(method("PATCH"))
        .and(path("/databases/db-123"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "status": "success"
        })))
        .mount(&server)
        .await;

    let result = client.database("db-123").edit(None, Some(512)).await;
    assert!(result.is_ok(), "edit(ram) failed: {:?}", result.err());
    assert!(result.expect("edit(ram) should return true"));
}

#[tokio::test]
async fn database_edit_none_returns_false() {
    let (client, _server) = crate::mock_client().await;

    let result = client.database("db-123").edit(None, None).await;
    assert_eq!(
        result.expect("edit(None, None) should return Ok(false)"),
        false
    );
}

#[tokio::test]
async fn database_certificate_returns_string() {
    let (client, server) = crate::mock_client().await;
    Mock::given(method("GET"))
        .and(path("/databases/db-123/credentials/certificate"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "status": "success",
            "response": {
                "certificate": "-----BEGIN CERTIFICATE-----\nMIIB...\n-----END CERTIFICATE-----"
            }
        })))
        .mount(&server)
        .await;

    let result = client.database("db-123").certificate().await;
    assert!(result.is_ok(), "certificate() failed: {:?}", result.err());
    assert!(
        !result
            .expect("certificate() should return non-empty string")
            .is_empty()
    );
}

#[tokio::test]
async fn database_redefine_credential_password() {
    let (client, server) = crate::mock_client().await;
    Mock::given(method("POST"))
        .and(path("/databases/db-123/credentials/reset"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "status": "success",
            "response": {
                "password": "new-secret-password"
            }
        })))
        .mount(&server)
        .await;

    let result = client
        .database("db-123")
        .redefine_credential(CredentialType::Password)
        .await;
    assert!(
        result.is_ok(),
        "redefine_credential(Password) failed: {:?}",
        result.err()
    );
    assert_eq!(
        result.expect(
            "redefine_credential(Password) should return new password"
        ),
        "new-secret-password"
    );
}

#[tokio::test]
async fn database_redefine_credential_certificate() {
    let (client, server) = crate::mock_client().await;
    Mock::given(method("POST"))
        .and(path("/databases/db-123/credentials/reset"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "status": "success",
            "response": {
                "certificate": "-----BEGIN CERTIFICATE-----\nMIIB...\n-----END CERTIFICATE-----"
            }
        })))
        .mount(&server)
        .await;

    let result = client
        .database("db-123")
        .redefine_credential(CredentialType::Certificate)
        .await;
    assert!(
        result.is_ok(),
        "redefine_credential(Certificate) failed: {:?}",
        result.err()
    );
    assert!(
        !result
            .expect(
                "redefine_credential(Certificate) should return certificate"
            )
            .is_empty()
    );
}

#[tokio::test]
async fn database_snapshot_lifecycle() {
    let (client, server) = crate::mock_client().await;
    Mock::given(method("POST"))
        .and(path("/databases/db-123/snapshots"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "status": "success",
            "response": {
                "url": "https://storage.example.com/snap.tar.gz",
                "key": "AWSAccessKeyId=FAKE&Expires=9999&Signature=sig&versionId=dead-beef-0000"
            }
        })))
        .mount(&server)
        .await;
    Mock::given(method("GET"))
        .and(path("/databases/db-123/snapshots"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "status": "success",
            "response": [
                {
                    "name": "db-123",
                    "size": 2048,
                    "modified": "2024-01-01T00:00:00Z",
                    "key": "AWSAccessKeyId=FAKE&Expires=9999&Signature=sig&versionId=dead-beef-0000"
                }
            ]
        })))
        .mount(&server)
        .await;
    Mock::given(method("POST"))
        .and(path("/databases/db-123/snapshots/restore"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "status": "success"
        })))
        .mount(&server)
        .await;

    let db = client.database("db-123");

    let snap = db
        .create_snapshot()
        .await
        .expect("create_snapshot() should succeed with mocked 200");
    assert_eq!(snap.url, "https://storage.example.com/snap.tar.gz");
    assert!(!snap.key.is_empty());

    let snapshots = db
        .list_snapshots()
        .await
        .expect("list_snapshots() should succeed with mocked 200");
    assert_eq!(snapshots.len(), 1);

    let first = &snapshots[0];
    assert!(
        db.restore_snapshot(
            first.name.clone(),
            first.version_id().to_string()
        )
        .await
        .expect("restore_snapshot() should succeed with mocked 200")
    );
}

#[tokio::test]
async fn database_start_returns_true() {
    let (client, server) = crate::mock_client().await;
    Mock::given(method("POST"))
        .and(path("/databases/db-123/start"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "status": "success"
        })))
        .mount(&server)
        .await;

    let result = client.database("db-123").start().await;
    assert!(result.is_ok(), "start() failed: {:?}", result.err());
    assert!(result.expect("database start() should return true"));
}

#[tokio::test]
async fn database_stop_returns_true() {
    let (client, server) = crate::mock_client().await;
    Mock::given(method("POST"))
        .and(path("/databases/db-123/stop"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "status": "success"
        })))
        .mount(&server)
        .await;

    let result = client.database("db-123").stop().await;
    assert!(result.is_ok(), "stop() failed: {:?}", result.err());
    assert!(result.expect("database stop() should return true"));
}

#[tokio::test]
async fn database_delete_returns_true() {
    let (client, server) = crate::mock_client().await;
    Mock::given(method("DELETE"))
        .and(path("/databases/db-123"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "status": "success",
            "response": true
        })))
        .mount(&server)
        .await;

    let result = client.database("db-123").delete().await;
    assert!(result.is_ok(), "delete() failed: {:?}", result.err());
    assert!(result.expect("database delete() should return true"));
}