scientific-workflow 0.7.2

Configuration-driven scientific tasks, typed state, and durable recordings
Documentation
use std::fs;
use std::path::PathBuf;
use std::sync::atomic::{AtomicU64, Ordering};

use scientific_workflow::prelude::basics::*;

static TEMP_SEQUENCE: AtomicU64 = AtomicU64::new(0);

struct TempDirectory(PathBuf);

impl TempDirectory {
    fn new() -> Self {
        let sequence = TEMP_SEQUENCE.fetch_add(1, Ordering::Relaxed);
        let path = std::env::temp_dir().join(format!(
            "scientific-workflow-artifact-{}-{sequence}",
            std::process::id()
        ));
        fs::create_dir(&path).unwrap();
        Self(path)
    }
}

impl Drop for TempDirectory {
    fn drop(&mut self) {
        let _ = fs::remove_dir_all(&self.0);
    }
}

#[test]
fn exact_bytes_are_atomically_reused_and_verified() {
    let root = TempDirectory::new();
    let scope = ExecutionScope::create_named(&root.0, "execution").unwrap();
    let first = persist_artifact(&scope, "initial-space", "json", b"exact bytes").unwrap();
    let second = persist_artifact(&scope, "initial-space", "json", b"exact bytes").unwrap();

    assert_eq!(first.disposition(), ArtifactDisposition::Created);
    assert_eq!(second.disposition(), ArtifactDisposition::Reused);
    assert_eq!(first.descriptor(), second.descriptor());
    assert_eq!(
        load_verified_artifact(scope.directory(), first.descriptor())
            .unwrap()
            .bytes(),
        b"exact bytes"
    );
}

#[test]
fn altered_bytes_fail_digest_verification() {
    let root = TempDirectory::new();
    let scope = ExecutionScope::create_named(&root.0, "execution").unwrap();
    let persisted = persist_artifact(&scope, "input", "bin", b"original").unwrap();
    fs::write(
        scope.directory().join(persisted.descriptor().path()),
        b"altered",
    )
    .unwrap();

    assert!(matches!(
        load_verified_artifact(scope.directory(), persisted.descriptor()),
        Err(ArtifactLoadError::DigestMismatch { .. })
    ));
}

#[test]
fn unsafe_names_and_descriptor_paths_are_rejected() {
    let root = TempDirectory::new();
    let scope = ExecutionScope::create_named(&root.0, "execution").unwrap();
    assert!(matches!(
        persist_artifact(&scope, "../escape", "json", b"bytes"),
        Err(ArtifactError::InvalidFragment { .. })
    ));

    let descriptor: ArtifactDescriptor = serde_json::from_value(serde_json::json!({
        "sha256": "0".repeat(64),
        "path": "../outside"
    }))
    .unwrap();
    assert!(matches!(
        load_verified_artifact(scope.directory(), &descriptor),
        Err(ArtifactLoadError::InvalidDescriptor { .. })
    ));
}