foundation_deployment_docker 0.1.3

Docker Engine API v1.53 client over DynNetClient + valtron (bollard-free)
//! Integration tests for Networks, Volumes, Exec, and System API methods.
//!
//! Run: `cargo test -p foundation_deployment_docker
//!   --features "docker,integration-tests" --profile uat
//!   --test network_volume_exec_integration_tests -- --test-threads=1`

#![cfg(all(unix, feature = "docker", feature = "integration-tests"))]

mod common;

use foundation_core::valtron::valtron_test;
use foundation_deployment_docker::DockerClient;

/// One fresh client per test — no shared state, no pool interference.
/// A client for the local daemon, with `alpine:latest` pulled (the tests
/// create containers from it; a cold daemon does not have it).
async fn client() -> DockerClient {
    common::client_with_alpine().await
}

// ── Network tests ──────────────────────────────────────────────────────

#[valtron_test]
async fn network_inspect_default_bridge() {
    let c = client().await;
    // The "bridge" network always exists on any Docker daemon.
    let _net = c
        .network_inspect("bridge", None, None)
        .await
        .expect("inspect bridge");
}

#[valtron_test]
async fn network_connect_then_disconnect() {
    let c = client().await;

    // Create a sleep container to connect.
    let ctr = c
        .create_container(
            &serde_json::json!({"Image":"alpine:latest","Cmd":["sleep","30"]}),
            Some("ewe-it-nve-netconnect"),
        )
        .await
        .expect("create container");
    c.start_container(&ctr.id).await.expect("start container");

    // Create a bridge network.
    let net = c
        .network_create(serde_json::json!({"Name":"ewe-it-nv-conntest","Driver":"bridge"}))
        .await
        .expect("create network");

    // Connect the container to the network (no aliases — see the platform's
    // network_integration_tests for the alias-resolution coverage).
    c.network_connect(&net.id, &ctr.id, &[])
        .await
        .expect("connect");

    // Disconnect the container from the network.
    c.network_disconnect(&net.id, &ctr.id, false)
        .await
        .expect("disconnect");

    // Cleanup.
    c.stop_container(&ctr.id, Some(5)).await.expect("stop");
    c.remove_container(&ctr.id, true).await.expect("remove");
    c.network_delete(&net.id).await.expect("delete network");
}

#[valtron_test]
async fn network_prune() {
    let c = client().await;
    c.network_prune(None).await.expect("prune networks");
}

// ── Volume tests ───────────────────────────────────────────────────────

#[valtron_test]
async fn volume_inspect_created() {
    let c = client().await;

    let cfg = serde_json::json!({"Name":"ewe-it-nv-inspect","Driver":"local"});
    let vol = c.volume_create(cfg).await.expect("create volume");

    let inspected = c
        .volume_inspect("ewe-it-nv-inspect")
        .await
        .expect("inspect volume");

    assert_eq!(inspected.name, "ewe-it-nv-inspect");
    assert_eq!(inspected.driver, "local");

    c.volume_delete(&vol.name, Some(true))
        .await
        .expect("delete volume");
}

#[valtron_test]
async fn volume_prune() {
    let c = client().await;
    c.volume_prune(None).await.expect("prune volumes");
}

// ── Exec tests ─────────────────────────────────────────────────────────

#[valtron_test]
async fn exec_inspect() {
    let c = client().await;

    let ctr = c
        .create_container(
            &serde_json::json!({"Image":"alpine:latest","Cmd":["sleep","30"]}),
            Some("ewe-it-nve-execinspect"),
        )
        .await
        .expect("create container");
    c.start_container(&ctr.id).await.expect("start");

    let exec_id = c
        .exec_create(&ctr.id, &["echo", "hi"])
        .await
        .expect("exec create");

    let inspected = c
        .exec_inspect(&exec_id.id)
        .await
        .expect("exec inspect");

    assert!(
        inspected.get("ID").is_some(),
        "exec inspect response should contain an ID field"
    );

    c.stop_container(&ctr.id, Some(5)).await.expect("stop");
    c.remove_container(&ctr.id, true).await.expect("remove");
}

#[valtron_test]
async fn exec_resize() {
    let c = client().await;

    let ctr = c
        .create_container(
            &serde_json::json!({"Image":"alpine:latest","Cmd":["sleep","30"]}),
            Some("ewe-it-nve-execresize"),
        )
        .await
        .expect("create container");
    c.start_container(&ctr.id).await.expect("start");

    let exec_id = c
        .exec_create(&ctr.id, &["sleep", "5"])
        .await
        .expect("exec create");

    // Start exec before resize — Docker returns 500 if exec hasn't been started
    // Detached: the daemon answers at once, with an empty body.
    let (mut stdout, mut stderr) = (String::new(), String::new());
    c.exec_start(&exec_id.id, true)
        .await
        .expect("exec start")
        .read_into(&mut stdout, &mut stderr)
        .await
        .expect("a detached start has no output to fail on");
    c.exec_resize(&exec_id.id, 40, 120)
        .await
        .expect("exec resize");

    c.stop_container(&ctr.id, Some(5)).await.expect("stop");
    c.remove_container(&ctr.id, true).await.expect("remove");
}

// ── System tests ───────────────────────────────────────────────────────

#[valtron_test]
async fn system_events() {
    let c = client().await;
    // `/events` is an infinite stream unless bounded — with no `until` the
    // daemon holds the connection open forever and the stream never ends
    // (collecting it hung the suite). `until=<now>` makes Docker replay events
    // up to this instant and close the stream, so the call terminates.
    let now = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .expect("system clock before UNIX epoch")
        .as_secs()
        .to_string();
    let mut events = c
        .system_events(None, Some(&now), None)
        .await
        .expect("system events");
    // Every replayed event must parse as the generated `EventMessage`.
    while let Some(event) = events.next().await {
        let event = event.expect("a typed event");
        assert!(event.r#type.is_some() || event.action.is_some(), "{event:?}");
    }
}