dpm-engine 0.1.0

Command/query execution engine for DPM
Documentation
use super::*;
use crate::{Command, NextWorkQuery, apply_command, is_ready, next_work};
use chrono::Utc;
use dpm_model::{ActorId, ArtifactId, ArtifactKind, DecisionStatus, WorkItemId};
use std::collections::BTreeMap;

#[test]
fn contextual_decisions_supply_sources_without_gating_or_leaking_to_other_work() {
    let mut plan: Plan = serde_json::from_str(include_str!(
        "../../../../tests/support/execution-plan.json"
    ))
    .expect("fixture");
    let task = plan.find_work_by_key("TEST-A").expect("task").id;
    let other = plan.find_work_by_key("TEST-B").expect("other").id;
    let source = Artifact {
        id: ArtifactId::new(),
        kind: ArtifactKind::File,
        uri: "discussion:design-choice".into(),
        label: "Design source, not completion evidence".into(),
        metadata: BTreeMap::new(),
        created_by: ActorId::human("planner"),
        created_at: Utc::now(),
    };
    plan.artifacts.insert(source.id, source.clone());
    let decision = plan.decisions.values_mut().next().expect("decision");
    decision.blocks.clear();
    decision.related_work.insert(task);
    decision.rationale = Some("Keep work independent of checkout paths.".into());
    decision.artifact_ids.insert(source.id);
    let id = decision.id;
    plan.validate().expect("valid associations");

    let before = plan.clone();
    let context = execution_context(&plan, &plan.work_items[&task]).expect("context");
    assert_eq!(context.decisions, vec![plan.decisions[&id].clone()]);
    assert!(context.artifacts.contains(&source));
    assert_eq!(context.decisions[0].status, DecisionStatus::Open);
    assert!(
        execution_context(&plan, &plan.work_items[&other])
            .expect("context")
            .decisions
            .is_empty()
    );
    assert!(
        !execution_context(&plan, &plan.work_items[&other])
            .expect("context")
            .artifacts
            .contains(&source)
    );
    assert!(is_ready(&plan, &plan.work_items[&task], chrono::Utc::now()));
    assert!(
        next_work(&plan, &NextWorkQuery::default(), chrono::Utc::now())
            .expect("next")
            .iter()
            .any(|candidate| candidate.work.id == task)
    );
    assert_eq!(plan, before);
    assert!(
        !plan.work_items[&task]
            .execution
            .artifact_ids
            .contains(&source.id)
    );
    apply_command(
        &mut plan,
        ActorId::agent("worker"),
        Command::Claim { work: task },
        Utc::now(),
        dpm_model::OperationId::new(),
    )
    .expect("contextual question does not prevent a claim");

    let mut gated = before;
    gated
        .decisions
        .get_mut(&id)
        .expect("decision")
        .blocks
        .insert(task);
    assert!(!is_ready(
        &gated,
        &gated.work_items[&task],
        chrono::Utc::now()
    ));
}

#[test]
fn context_associations_apply_to_descendants_without_affecting_readiness() {
    let mut plan: Plan = serde_json::from_str(include_str!(
        "../../../../tests/support/execution-plan.json"
    ))
    .expect("fixture");
    let task = plan.find_work_by_key("TEST-A").expect("task").id;
    let mut parent = plan.work_items[&task].clone();
    parent.id = WorkItemId::new();
    parent.key = dpm_model::Key::new("PACKAGE");
    parent.kind = dpm_model::WorkKind::WorkPackage;
    parent.schedule.estimate = None;
    let parent_id = parent.id;
    plan.work_items.insert(parent_id, parent);
    plan.work_items.get_mut(&task).expect("task").parent = Some(parent_id);
    let decision = plan.decisions.values_mut().next().expect("decision");
    decision.blocks.clear();
    decision.related_work.insert(parent_id);
    let id = decision.id;
    plan.validate().expect("valid hierarchy");
    assert_eq!(
        execution_context(&plan, &plan.work_items[&task])
            .expect("context")
            .decisions[0]
            .id,
        id
    );
    assert!(is_ready(&plan, &plan.work_items[&task], chrono::Utc::now()));
}