quicknode-cli 0.1.6

Command-line interface for the Quicknode SDK
Documentation
//! Stage-3 admin-surface integration tests (endpoint tags/bulk, teams, usage,
//! metrics, chain, billing).

mod common;

use common::run_qn;
use serde_json::json;
use wiremock::matchers::{body_json, method, path, query_param};
use wiremock::{Mock, MockServer, ResponseTemplate};

#[tokio::test]
async fn tag_list() {
    let server = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/v0/endpoints/tags"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "data": { "tags": [{ "id": 1, "label": "prod", "usage_count": 3 }] }
        })))
        .mount(&server)
        .await;
    assert_eq!(
        run_qn(&server.uri(), &["endpoint", "tag", "list"])
            .await
            .exit_code,
        0
    );
}

#[tokio::test]
async fn tag_rename_sends_label() {
    let server = MockServer::start().await;
    Mock::given(method("PATCH"))
        .and(path("/v0/endpoints/tags/42"))
        .and(body_json(json!({ "label": "staging" })))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "data": { "id": 42, "label": "staging", "usage_count": 0 }
        })))
        .mount(&server)
        .await;
    let out = run_qn(
        &server.uri(),
        &["endpoint", "tag", "rename", "42", "staging"],
    )
    .await;
    assert_eq!(out.exit_code, 0, "stderr={}", out.stderr);
}

#[tokio::test]
async fn tag_delete_needs_yes() {
    let server = MockServer::start().await;
    Mock::given(method("DELETE"))
        .and(path("/v0/endpoints/tags/42"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "data": { "success": true }
        })))
        .mount(&server)
        .await;
    let no = run_qn(&server.uri(), &["endpoint", "tag", "delete", "42"]).await;
    assert_eq!(no.exit_code, 5);
    let yes = run_qn(&server.uri(), &["endpoint", "tag", "delete", "42", "--yes"]).await;
    assert_eq!(yes.exit_code, 0, "stderr={}", yes.stderr);
}

#[tokio::test]
async fn team_list() {
    let server = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/v0/teams"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "data": [{ "id": 7, "name": "Payments", "members_count": 4 }]
        })))
        .mount(&server)
        .await;
    let out = run_qn(&server.uri(), &["team", "list"]).await;
    assert_eq!(out.exit_code, 0, "stderr={}", out.stderr);
}

#[tokio::test]
async fn team_create_sends_name() {
    let server = MockServer::start().await;
    Mock::given(method("POST"))
        .and(path("/v0/teams"))
        .and(body_json(json!({ "name": "Ops" })))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "data": { "id": 8, "name": "Ops" }
        })))
        .mount(&server)
        .await;
    let out = run_qn(&server.uri(), &["team", "create", "--name", "Ops"]).await;
    assert_eq!(out.exit_code, 0, "stderr={}", out.stderr);
}

#[tokio::test]
async fn team_member_invite() {
    let server = MockServer::start().await;
    Mock::given(method("POST"))
        .and(path("/v0/teams/7/members"))
        .and(body_json(json!({ "email": "a@x.io", "role": "viewer" })))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "data": { "id": 99, "email": "a@x.io" }
        })))
        .mount(&server)
        .await;
    let out = run_qn(
        &server.uri(),
        &[
            "team", "member", "invite", "7", "--email", "a@x.io", "--role", "viewer",
        ],
    )
    .await;
    assert_eq!(out.exit_code, 0, "stderr={}", out.stderr);
}

#[tokio::test]
async fn usage_summary() {
    let server = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/v0/usage/rpc"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "data": {
                "credits_used": 1000,
                "credits_remaining": 9000,
                "limit": 10000,
                "start_time": 0,
                "end_time": 0
            }
        })))
        .mount(&server)
        .await;
    let out = run_qn(&server.uri(), &["usage", "summary"]).await;
    assert_eq!(out.exit_code, 0, "stderr={}", out.stderr);
}

#[tokio::test]
async fn usage_by_endpoint_with_range() {
    let server = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/v0/usage/rpc/by-endpoint"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "data": { "endpoints": [] }
        })))
        .mount(&server)
        .await;
    let out = run_qn(
        &server.uri(),
        &["usage", "by-endpoint", "--from", "7d", "--to", "now"],
    )
    .await;
    assert_eq!(out.exit_code, 0, "stderr={}", out.stderr);
}

#[tokio::test]
async fn chain_list() {
    let server = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/v0/chains"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "data": [
                { "slug": "ethereum", "networks": [{ "slug": "mainnet", "name": "Mainnet" }] }
            ]
        })))
        .mount(&server)
        .await;
    let out = run_qn(&server.uri(), &["chain", "list"]).await;
    assert_eq!(out.exit_code, 0, "stderr={}", out.stderr);
}

#[tokio::test]
async fn metrics_account_with_percentile() {
    let server = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/v0/metrics"))
        .and(query_param("period", "day"))
        .and(query_param("metric", "credits_over_time"))
        .and(query_param("percentile", "p95"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({"data": []})))
        .mount(&server)
        .await;
    let out = run_qn(
        &server.uri(),
        &[
            "metrics",
            "account",
            "--period",
            "day",
            "--metric",
            "credits_over_time",
            "--percentile",
            "p95",
        ],
    )
    .await;
    assert_eq!(out.exit_code, 0, "stderr={}", out.stderr);
}

#[tokio::test]
async fn billing_invoices() {
    let server = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/v0/billing/invoices"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "data": { "invoices": [] }
        })))
        .mount(&server)
        .await;
    let out = run_qn(&server.uri(), &["billing", "invoices"]).await;
    assert_eq!(out.exit_code, 0, "stderr={}", out.stderr);
}

#[tokio::test]
async fn bulk_status_paused() {
    let server = MockServer::start().await;
    Mock::given(method("POST"))
        .and(path("/v0/endpoints/bulk/status"))
        .and(body_json(
            json!({ "ids": ["ep-1", "ep-2"], "status": "paused" }),
        ))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "data": { "total": 2, "updated_count": 2, "failed_count": 0, "results": [] }
        })))
        .mount(&server)
        .await;
    let out = run_qn(
        &server.uri(),
        &["endpoint", "bulk", "pause", "ep-1", "ep-2", "--yes"],
    )
    .await;
    assert_eq!(out.exit_code, 0, "stderr={}", out.stderr);
}

#[tokio::test]
async fn bulk_pause_without_yes_needs_confirmation_and_sends_nothing() {
    let server = MockServer::start().await;
    Mock::given(method("POST"))
        .and(path("/v0/endpoints/bulk/status"))
        .respond_with(ResponseTemplate::new(200))
        .expect(0)
        .mount(&server)
        .await;
    let out = run_qn(&server.uri(), &["endpoint", "bulk", "pause", "ep-1"]).await;
    assert_eq!(out.exit_code, 5, "stderr={}", out.stderr);
}

#[tokio::test]
async fn bulk_resume_does_not_need_confirmation() {
    let server = MockServer::start().await;
    Mock::given(method("POST"))
        .and(path("/v0/endpoints/bulk/status"))
        .and(body_json(json!({ "ids": ["ep-1"], "status": "active" })))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "data": { "total": 1, "updated_count": 1, "failed_count": 0, "results": [] }
        })))
        .expect(1)
        .mount(&server)
        .await;
    let out = run_qn(&server.uri(), &["endpoint", "bulk", "resume", "ep-1"]).await;
    assert_eq!(out.exit_code, 0, "stderr={}", out.stderr);
}

#[tokio::test]
async fn bulk_tag_add() {
    let server = MockServer::start().await;
    Mock::given(method("POST"))
        .and(path("/v0/endpoints/bulk/tags"))
        .and(body_json(json!({ "ids": ["ep-1"], "label": "prod" })))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "data": { "total": 1, "updated_count": 1, "failed_count": 0, "results": [], "tag": { "tag_id": 1, "label": "prod" } }
        })))
        .mount(&server)
        .await;
    let out = run_qn(
        &server.uri(),
        &["endpoint", "bulk", "tag", "add", "--label", "prod", "ep-1"],
    )
    .await;
    assert_eq!(out.exit_code, 0, "stderr={}", out.stderr);
}

#[tokio::test]
async fn team_set_endpoints_no_ids_fails_before_api_call() {
    let server = MockServer::start().await;
    let out = run_qn(&server.uri(), &["team", "set-endpoints", "7"]).await;
    assert_eq!(out.exit_code, 1, "stderr={}", out.stderr);
    assert!(out.stderr.contains("endpoint id"), "stderr={}", out.stderr);
    assert_eq!(server.received_requests().await.unwrap().len(), 0);
}