tandem-server 0.6.5

HTTP server for Tandem engine APIs
#[test]
fn automation_v2_run_store_backfills_definition_metadata_from_snapshot() {
    let automation = AutomationSpecBuilder::new("automation-definition-backfill")
        .metadata(json!({ "definition_version": "release-backfill" }))
        .build();
    let mut run =
        AutomationRunBuilder::new("run-definition-backfill", "automation-definition-backfill")
            .build();
    run.automation_snapshot = Some(automation.clone());
    let mut run_value = serde_json::to_value(&run).expect("serialize run");
    let run_object = run_value.as_object_mut().expect("run object");
    run_object.remove("workflow_definition_version");
    run_object.remove("workflow_definition_snapshot_hash");

    let raw = json!({
        "schema_version": AUTOMATION_V2_RUNS_SCHEMA_VERSION,
        "runs": {
            "run-definition-backfill": run_value
        }
    })
    .to_string();
    let (runs, upgraded) = parse_automation_v2_runs_file(&raw).expect("parse backfill run");
    assert!(upgraded);
    let parsed = runs
        .get("run-definition-backfill")
        .expect("backfilled run");
    let expected_hash = crate::stateful_runtime::automation_definition_snapshot_hash(&automation);

    assert_eq!(
        parsed.workflow_definition_version.as_deref(),
        Some("release-backfill")
    );
    assert_eq!(
        parsed.workflow_definition_snapshot_hash.as_deref(),
        Some(expected_hash.as_str())
    );
}

#[test]
fn automation_v2_run_store_backfills_enterprise_scope_metadata_from_snapshot() {
    let automation = AutomationSpecBuilder::new("automation-enterprise-backfill")
        .metadata(json!({
            "resource_access": {
                "owning_org_unit_id": " Finance "
            }
        }))
        .build();
    let mut run =
        AutomationRunBuilder::new("run-enterprise-backfill", "automation-enterprise-backfill")
            .build();
    run.automation_snapshot = Some(automation);
    let raw = json!({
        "schema_version": AUTOMATION_V2_RUNS_SCHEMA_VERSION,
        "runs": {
            "run-enterprise-backfill": run
        }
    })
    .to_string();

    let (runs, upgraded) = parse_automation_v2_runs_file(&raw).expect("parse backfill run");
    assert!(upgraded);
    let parsed = runs
        .get("run-enterprise-backfill")
        .and_then(|run| run.automation_snapshot.as_ref())
        .expect("backfilled run snapshot");
    let scope = parsed.enterprise_scope().expect("enterprise scope");

    assert_eq!(scope.owning_org_unit_id.as_deref(), Some("finance"));
    assert!(parsed
        .metadata
        .as_ref()
        .and_then(|metadata| metadata.get("enterprise_scope"))
        .is_some());
}

#[tokio::test]
async fn create_automation_v2_run_records_definition_metadata() {
    use crate::automation_v2::execution_profile::ExecutionProfile;

    let root = std::env::temp_dir().join(format!(
        "tandem-definition-metadata-run-{}",
        uuid::Uuid::new_v4()
    ));
    std::fs::create_dir_all(&root).expect("state root");
    let mut state = test_state_with_path(root.join("shared.json"));
    state.automations_v2_path = root.join("automations_v2.json");
    state.automation_v2_runs_path = root.join("automation_v2_runs.json");
    state.automation_governance_path = root.join("automation_governance.json");
    let automation = AutomationSpecBuilder::new("automation-definition-metadata")
        .name("Definition metadata")
        .execution_profile(ExecutionProfile::Strict)
        .build();
    state
        .put_automation_v2(automation.clone())
        .await
        .expect("persist automation");
    let run = state
        .create_automation_v2_run_with_profile(&automation, "manual", Some(ExecutionProfile::Yolo))
        .await
        .expect("create run");
    let snapshot = run.automation_snapshot.as_ref().expect("snapshot set on run");
    let expected_hash = crate::stateful_runtime::automation_definition_snapshot_hash(snapshot);
    let expected_version =
        crate::stateful_runtime::automation_definition_version(snapshot, &expected_hash);

    assert_eq!(
        snapshot.execution.profile,
        Some(ExecutionProfile::Yolo),
        "snapshot must carry the run-level effective profile"
    );
    assert_eq!(
        run.workflow_definition_version.as_deref(),
        Some(expected_version.as_str())
    );
    assert_eq!(
        run.workflow_definition_snapshot_hash.as_deref(),
        Some(expected_hash.as_str())
    );
}

#[tokio::test]
async fn automation_v2_run_snapshot_replacement_restamps_definition_metadata() {
    let root = std::env::temp_dir().join(format!(
        "tandem-definition-metadata-restamp-{}",
        uuid::Uuid::new_v4()
    ));
    std::fs::create_dir_all(&root).expect("state root");
    let mut state = ready_test_state().await;
    state.shared_resources_path = root.join("shared.json");
    state.automations_v2_path = root.join("automations_v2.json");
    state.automation_v2_runs_path = root.join("automation_v2_runs.json");
    state.automation_governance_path = root.join("automation_governance.json");
    let automation = AutomationSpecBuilder::new("automation-definition-restamp")
        .metadata(json!({ "definition_version": "initial-definition" }))
        .build();
    state
        .put_automation_v2(automation.clone())
        .await
        .expect("persist automation");
    let run = state
        .create_automation_v2_run(&automation, "manual")
        .await
        .expect("create run");
    let original_hash = run.workflow_definition_snapshot_hash.clone();
    let mut replacement_snapshot = run
        .automation_snapshot
        .clone()
        .expect("snapshot set on run");
    replacement_snapshot.metadata = Some(json!({
        "definition_version": "manual-trigger-definition",
        "plan_package": {
            "manual_trigger_record": {
                "run_id": run.run_id,
                "trigger_source": "manual"
            }
        }
    }));
    let expected_hash =
        crate::stateful_runtime::automation_definition_snapshot_hash(&replacement_snapshot);
    let expected_version =
        crate::stateful_runtime::automation_definition_version(&replacement_snapshot, &expected_hash);

    let updated = state
        .update_automation_v2_run(&run.run_id, |row| {
            row.automation_snapshot = Some(replacement_snapshot.clone());
            crate::stateful_runtime::stamp_automation_run_definition_metadata(row);
        })
        .await
        .expect("updated run");

    assert_ne!(original_hash.as_deref(), Some(expected_hash.as_str()));
    assert_eq!(
        updated.workflow_definition_version.as_deref(),
        Some(expected_version.as_str())
    );
    assert_eq!(
        updated.workflow_definition_snapshot_hash.as_deref(),
        Some(expected_hash.as_str())
    );
}