harn-hostlib 0.10.140

Opt-in code-intelligence and deterministic-tool host builtins for the Harn VM
use super::*;

fn resource(domain: &str) -> HostLeaseResourceKey {
    HostLeaseResourceKey {
        machine: "mac-local".to_string(),
        resource_class: HostLeaseResourceClass::RustHeavy,
        domain: domain.to_string(),
    }
}

#[test]
fn overview_observes_known_idle_resources_instead_of_an_empty_census() {
    let temp = TempDir::new().unwrap();
    let store = store(&temp);
    let overview = store.status_overview("mac-local", None).unwrap();
    assert_eq!(
        overview.resources.len(),
        HOST_LEASE_RESOURCE_DEFINITIONS.len()
    );
    for state in overview.resources {
        assert_eq!(state.observed_at_ms, overview.observed_at_ms);
        assert_eq!(state.domain, DEFAULT_HOST_LEASE_DOMAIN);
        assert!(state.active.is_none());
        assert!(state.pending.is_empty());
    }
}

#[test]
fn status_and_admission_share_priority_order_and_resource_scope() {
    let temp = TempDir::new().unwrap();
    let store = store(&temp);
    let resource = resource("verification");
    let low = waiter("low", 1);
    let high = waiter("high", 2);
    store
        .enqueue_waiter(
            &resource,
            HostLeasePriorityClass::Deferrable,
            &low,
            i64::MAX,
            None,
        )
        .unwrap();
    store
        .enqueue_waiter(
            &resource,
            HostLeasePriorityClass::Interactive,
            &high,
            i64::MAX,
            None,
        )
        .unwrap();
    let mut other = resource.clone();
    other.domain = "other-domain".to_string();
    store
        .enqueue_waiter(
            &other,
            HostLeasePriorityClass::Measurement,
            &waiter("other", 3),
            i64::MAX,
            None,
        )
        .unwrap();
    other.machine = "other-host".to_string();
    store
        .enqueue_waiter(
            &other,
            HostLeasePriorityClass::Measurement,
            &waiter("other-host", 4),
            i64::MAX,
            None,
        )
        .unwrap();

    let scoped = store
        .status_for_domain(&resource.machine, resource.resource_class, &resource.domain)
        .unwrap();
    assert_eq!(
        scoped
            .pending
            .iter()
            .map(|entry| entry.waiter_id.as_str())
            .collect::<Vec<_>>(),
        ["high", "low"]
    );
    let overview = store.status_overview(&resource.machine, None).unwrap();
    assert_eq!(
        overview.resources.len(),
        HOST_LEASE_RESOURCE_DEFINITIONS.len() + 2
    );
    assert_eq!(
        overview
            .resources
            .iter()
            .map(|state| state.pending.len())
            .sum::<usize>(),
        3
    );
    let filtered = store
        .status_overview(&resource.machine, Some(&resource.domain))
        .unwrap();
    assert_eq!(
        filtered.resources.len(),
        HOST_LEASE_RESOURCE_DEFINITIONS.len()
    );
    assert!(filtered
        .resources
        .iter()
        .all(|state| state.domain == resource.domain));
    assert_eq!(
        filtered
            .resources
            .iter()
            .map(|state| state.pending.len())
            .sum::<usize>(),
        2
    );
    assert!(store.status(&resource.machine).unwrap().pending.is_empty());

    let mut request = request("high-owner");
    request.resource_class = resource.resource_class;
    request.domain = resource.domain.clone();
    request.priority_class = HostLeasePriorityClass::Interactive;
    let acquired = store
        .try_acquire_once(request, None, Some(i64::MAX), &high)
        .unwrap();
    assert_eq!(acquired.status, HostLeaseAcquireStatus::Acquired);
    assert_eq!(acquired.queue.unwrap().position, 1);
    let scoped = store
        .status_for_domain(&resource.machine, resource.resource_class, &resource.domain)
        .unwrap();
    assert_eq!(scoped.active.unwrap().owner, "high-owner");
    assert_eq!(scoped.pending.len(), 1);
    assert_eq!(scoped.pending[0].waiter_id, "low");
}

#[test]
fn status_preserves_unknown_waiters_and_removes_expired_or_dead_waiters() {
    let temp = TempDir::new().unwrap();
    let inspector = Arc::new(ScriptedProcessInspector::alive(42));
    let store = HostLeaseStore::for_root_with_inspector(
        temp.path(),
        Arc::clone(&inspector) as Arc<dyn ProcessInspector>,
    )
    .unwrap();
    let resource = resource(DEFAULT_HOST_LEASE_DOMAIN);
    store
        .enqueue_waiter(
            &resource,
            HostLeasePriorityClass::Measurement,
            &waiter("live", 1),
            i64::MAX,
            Some(123),
        )
        .unwrap();
    let read = || {
        store
            .status_for_resource(&resource.machine, resource.resource_class)
            .unwrap()
    };
    assert_eq!(read().pending.len(), 1);
    inspector.set(ProcessObservation::Unknown);
    assert_eq!(read().pending.len(), 1, "unobservable is not dead");
    inspector.set(ProcessObservation::Dead);
    assert!(read().pending.is_empty());

    inspector.set(ProcessObservation::Alive { identity: 42 });
    store
        .enqueue_waiter(
            &resource,
            HostLeasePriorityClass::Measurement,
            &waiter("reused", 2),
            i64::MAX,
            Some(123),
        )
        .unwrap();
    inspector.set(ProcessObservation::Alive { identity: 43 });
    assert!(
        read().pending.is_empty(),
        "PID reuse must not preserve a dead waiter"
    );
    store
        .enqueue_waiter(
            &resource,
            HostLeasePriorityClass::Measurement,
            &waiter("expires", 3),
            i64::MAX,
            None,
        )
        .unwrap();
    assert_eq!(read().pending.len(), 1);
    let expired = store
        .status_at(&resource.machine, resource.resource_class, i64::MAX)
        .unwrap();
    assert!(expired.pending.is_empty());
}

#[test]
fn pending_evidence_is_required_when_decoding_a_status_receipt() {
    let temp = TempDir::new().unwrap();
    let state = store(&temp).status("mac-local").unwrap();
    let mut encoded = serde_json::to_value(state).unwrap();
    encoded.as_object_mut().unwrap().remove("pending");
    assert!(serde_json::from_value::<HostLeaseState>(encoded).is_err());
}