apcore 0.18.0

Schema-driven module standard for AI-perceivable interfaces
Documentation
//! Tests for Context and Identity.

use apcore::cancel::CancelToken;
use apcore::context::{Context, Identity};
use serde_json::Value;
use std::collections::HashMap;

fn make_identity(id: &str, identity_type: &str, roles: &[&str]) -> Identity {
    Identity::new(
        id.to_string(),
        identity_type.to_string(),
        roles.iter().map(std::string::ToString::to_string).collect(),
        HashMap::new(),
    )
}

// ---------------------------------------------------------------------------
// Identity
// ---------------------------------------------------------------------------

#[test]
fn test_identity_basic_fields() {
    let id = make_identity("user-1", "Alice", &["admin", "user"]);
    assert_eq!(id.id(), "user-1");
    assert_eq!(id.identity_type(), "Alice");
    assert_eq!(id.roles().len(), 2);
    assert!(id.roles().contains(&"admin".to_string()));
}

#[test]
fn test_identity_empty_roles() {
    let id = make_identity("svc-1", "Service", &[]);
    assert!(id.roles().is_empty());
}

#[test]
fn test_identity_with_attrs() {
    let mut attrs = HashMap::new();
    attrs.insert("tier".to_string(), serde_json::json!("premium"));
    let id = Identity::new("user-2".to_string(), "Bob".to_string(), vec![], attrs);
    assert_eq!(id.attrs()["tier"], "premium");
}

#[test]
fn test_identity_serialization() {
    let id = make_identity("user-1", "Alice", &["admin"]);
    let json = serde_json::to_string(&id).unwrap();
    let restored: Identity = serde_json::from_str(&json).unwrap();
    assert_eq!(restored.id(), id.id());
    assert_eq!(restored.identity_type(), id.identity_type());
    assert_eq!(restored.roles(), id.roles());
}

// ---------------------------------------------------------------------------
// Context
// ---------------------------------------------------------------------------

#[test]
fn test_context_new_has_unique_trace_id() {
    let id = make_identity("user-1", "Alice", &[]);
    let ctx1: Context<Value> = Context::new(id.clone());
    let ctx2: Context<Value> = Context::new(id);
    assert_ne!(ctx1.trace_id, ctx2.trace_id);
}

#[test]
fn test_context_initial_call_chain_length_is_zero() {
    let id = make_identity("user-1", "Alice", &[]);
    let ctx: Context<Value> = Context::new(id);
    assert_eq!(ctx.call_chain.len(), 0);
}

#[test]
fn test_context_initial_call_chain_is_empty() {
    let id = make_identity("user-1", "Alice", &[]);
    let ctx: Context<Value> = Context::new(id);
    assert!(ctx.call_chain.is_empty());
}

#[test]
fn test_context_no_global_deadline_by_default() {
    let id = make_identity("user-1", "Alice", &[]);
    let ctx: Context<Value> = Context::new(id);
    assert!(ctx.global_deadline.is_none());
}

#[test]
fn test_context_identity_is_preserved() {
    let id = make_identity("svc-42", "MyService", &["reader"]);
    let ctx: Context<Value> = Context::new(id);
    let identity = ctx.identity.as_ref().expect("identity should be Some");
    assert_eq!(identity.id(), "svc-42");
    assert_eq!(identity.roles(), &["reader"]);
}

#[test]
fn test_context_no_cancel_token_by_default() {
    let id = make_identity("user-1", "Alice", &[]);
    let ctx: Context<Value> = Context::new(id);
    assert!(ctx.cancel_token.is_none());
}

#[test]
fn test_context_with_cancel_token() {
    let id = make_identity("user-1", "Alice", &[]);
    let mut ctx: Context<Value> = Context::new(id);
    let token = CancelToken::new();
    ctx.cancel_token = Some(token);
    assert!(!ctx.cancel_token.as_ref().unwrap().is_cancelled());
}

#[test]
fn test_context_data_starts_empty() {
    let id = make_identity("user-1", "Alice", &[]);
    let ctx: Context<Value> = Context::new(id);
    assert!(ctx.data.read().is_empty());
}

#[test]
fn test_anonymous_context_has_none_identity() {
    let ctx: Context<Value> = Context::anonymous();
    assert!(ctx.identity.is_none());
    assert!(ctx.call_chain.is_empty());
}

#[test]
fn test_shared_data_between_parent_and_child() {
    let id = make_identity("user-1", "Alice", &[]);
    let parent: Context<Value> = Context::new(id);
    let child = parent.child("child_mod");

    // Write to parent's data
    parent
        .data
        .write()
        .insert("key".to_string(), serde_json::json!("from_parent"));

    // Read from child's data — should see parent's write since they share Arc
    let child_data = child.data.read();
    assert_eq!(
        child_data.get("key").unwrap(),
        &serde_json::json!("from_parent")
    );
}

#[test]
fn test_context_serde_roundtrip() {
    let id = make_identity("user-1", "Alice", &["admin"]);
    let ctx: Context<Value> = Context::new(id);
    ctx.data
        .write()
        .insert("foo".to_string(), serde_json::json!(42));

    let json = serde_json::to_value(&ctx).unwrap();
    let restored: Context<Value> = serde_json::from_value(json).unwrap();

    assert_eq!(restored.trace_id, ctx.trace_id);
    assert_eq!(
        restored.identity.as_ref().unwrap().id(),
        ctx.identity.as_ref().unwrap().id()
    );
    assert_eq!(
        restored.data.read().get("foo"),
        Some(&serde_json::json!(42))
    );
}