agent-team-mail-core 1.3.0

Core library for local agent team mail workflows.
Documentation
use std::fs;

use tempfile::tempdir;

use super::tests::{RecordingPostSendEmitter, TestRuntime, install_home_env, send_request};
use crate::delivery_policy::DeliveryHarnessPath;
use crate::error_codes::AtmErrorCode;
use crate::observability::NullObservability;
use crate::protocol::NotificationKind;
use crate::send::SendCommandOutcome;
use crate::test_support::TEST_SENDER;
use crate::types::TeamName;

#[test]
fn load_send_alert_state_parse_errors_are_config_errors() {
    let tempdir = tempdir().expect("tempdir");
    let path = super::alert_state::state_path(tempdir.path());
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent).expect("state dir");
    }
    fs::write(&path, "{not-json").expect("state file");

    let error = super::alert_state::load(&path).expect_err("malformed state");
    assert!(error.is_config());
}

#[test]
#[serial_test::serial(env)]
fn send_non_claude_success_delivers_original_via_outbound_boundary() {
    let runtime = TestRuntime::new(None, DeliveryHarnessPath::NonClaude);
    let tempdir = tempdir().expect("tempdir");
    let home_dir = tempdir.path().join("home");
    let _env = install_home_env(&home_dir);
    let post_send_emitter = RecordingPostSendEmitter::succeed();

    let outcome = super::send_mail_with_runtime_impl(
        send_request(tempdir.path()),
        &NullObservability,
        &runtime,
        Some(&post_send_emitter),
    )
    .expect("send outcome");

    assert_eq!(outcome.outcome, SendCommandOutcome::Sent);
    assert!(outcome.warnings.is_empty());
    assert!(
        runtime
            .appended_messages
            .lock()
            .expect("append lock")
            .is_empty()
    );
    let deliveries = runtime
        .non_claude_deliveries
        .lock()
        .expect("non-claude deliveries lock");
    assert_eq!(deliveries.len(), 1);
    assert_eq!(deliveries[0].messages.len(), 1);
    assert_eq!(deliveries[0].messages[0].from.as_str(), TEST_SENDER);
    drop(deliveries);
    let events = super::tests::read_notification_events(&home_dir);
    assert_eq!(events.len(), 1);
    assert_eq!(events[0].kind, NotificationKind::Delivery);
    assert_eq!(
        super::tests::notification_detail(&events[0])
            .get("sender")
            .and_then(serde_json::Value::as_str),
        Some(TEST_SENDER)
    );
    assert_eq!(
        events[0].team.as_ref().map(TeamName::as_str),
        Some("test-team")
    );
    let emitted = post_send_emitter.emitted();
    assert_eq!(emitted.len(), 1);
    assert_eq!(emitted[0].event.sender.as_str(), TEST_SENDER);
    assert_eq!(emitted[0].event.description, "hello");
}

#[test]
#[serial_test::serial(env)]
fn send_non_claude_warns_when_graft_post_send_delivery_fails() {
    let runtime = TestRuntime::new(None, DeliveryHarnessPath::NonClaude);
    let tempdir = tempdir().expect("tempdir");
    let home_dir = tempdir.path().join("home");
    let _env = install_home_env(&home_dir);
    let post_send_emitter = RecordingPostSendEmitter::fail(AtmErrorCode::PostSendGraftUnavailable);

    let outcome = super::send_mail_with_runtime_impl(
        send_request(tempdir.path()),
        &NullObservability,
        &runtime,
        Some(&post_send_emitter),
    )
    .expect("send outcome");

    assert_eq!(outcome.outcome, SendCommandOutcome::Sent);
    assert_eq!(outcome.warnings.len(), 1);
    assert!(
        outcome.warnings[0]
            .message
            .contains("warning: post-send emission failed")
    );
    let notification_path =
        crate::home::host_runtime_dir_from_home(&home_dir).join("notifications.jsonl");
    assert!(
        !notification_path.exists(),
        "notification log should not append when graft post-send emission fails"
    );
}