htb-cli 0.1.8

Hack The Box CLI
Documentation
use std::sync::Arc;

use htb_cli::cache::Cache;

use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};

fn temp_cache(name: &str) -> Cache {
    let dir = std::env::temp_dir().join(format!("htb-cache-integ-{name}-{}", std::process::id()));
    let _ = std::fs::remove_dir_all(&dir);
    Cache::new(dir, true)
}

#[tokio::test]
async fn cached_get_hits_server_once() {
    let server = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/api/v4/machine/profile/TestBox"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "info": {"id": 1, "name": "TestBox", "os": "Linux",
                     "points": 30, "userOwnsCount": 0, "rootOwnsCount": 0}
        })))
        .expect(1)
        .mount(&server)
        .await;

    let cache = Arc::new(temp_cache("hit-once"));
    let client =
        htb_cli::api::HtbClient::with_base_url_and_cache("test-token".into(), server.uri(), cache);

    let result: serde_json::Value = client.get("/api/v4/machine/profile/TestBox").await.unwrap();
    assert_eq!(result["info"]["name"], "TestBox");

    let cached: serde_json::Value = client.get("/api/v4/machine/profile/TestBox").await.unwrap();
    assert_eq!(cached["info"]["name"], "TestBox");
}

#[tokio::test]
async fn invalidation_after_post_clears_machine_cache() {
    let server = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/api/v4/machine/profile/Box"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "info": {"id": 927, "name": "Box", "os": "Linux",
                     "points": 30, "userOwnsCount": 0, "rootOwnsCount": 0}
        })))
        .expect(2)
        .mount(&server)
        .await;

    Mock::given(method("POST"))
        .and(path("/api/v4/vm/spawn"))
        .respond_with(
            ResponseTemplate::new(200)
                .set_body_json(serde_json::json!({"message": "Spawned", "success": true})),
        )
        .mount(&server)
        .await;

    let cache = Arc::new(temp_cache("invalidation"));
    let client =
        htb_cli::api::HtbClient::with_base_url_and_cache("test-token".into(), server.uri(), cache);

    let _: serde_json::Value = client.get("/api/v4/machine/profile/Box").await.unwrap();

    let _: serde_json::Value = client
        .post("/api/v4/vm/spawn", &serde_json::json!({"machine_id": 927}))
        .await
        .unwrap();

    let _: serde_json::Value = client.get("/api/v4/machine/profile/Box").await.unwrap();
}

#[tokio::test]
async fn uncached_endpoints_always_hit_server() {
    let server = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/api/v5/virtual_machine/active"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({"info": null})))
        .expect(2)
        .mount(&server)
        .await;

    let cache = Arc::new(temp_cache("uncached"));
    let client =
        htb_cli::api::HtbClient::with_base_url_and_cache("test-token".into(), server.uri(), cache);

    let _: serde_json::Value = client.get("/api/v5/virtual_machine/active").await.unwrap();
    let _: serde_json::Value = client.get("/api/v5/virtual_machine/active").await.unwrap();
}

#[tokio::test]
async fn disabled_cache_always_hits_server() {
    let server = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/api/v4/machine/profile/NoCacheBox"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "info": {"id": 1, "name": "NoCacheBox", "os": "Linux",
                     "points": 30, "userOwnsCount": 0, "rootOwnsCount": 0}
        })))
        .expect(2)
        .mount(&server)
        .await;

    let dir = std::env::temp_dir().join(format!("htb-cache-integ-disabled-{}", std::process::id()));
    let cache = Arc::new(Cache::new(dir, false));
    let client =
        htb_cli::api::HtbClient::with_base_url_and_cache("test-token".into(), server.uri(), cache);

    let _: serde_json::Value = client
        .get("/api/v4/machine/profile/NoCacheBox")
        .await
        .unwrap();
    let _: serde_json::Value = client
        .get("/api/v4/machine/profile/NoCacheBox")
        .await
        .unwrap();
}