numi-core 0.2.1

Core parsing, normalization, rendering, and output orchestration for Numi.
Documentation
use super::super::{check, generate};
use super::{make_temp_dir, seed_cached_parse, with_temp_dir_override};
use crate::parse_cache::{CacheKind, CachedParseData};
use camino::Utf8PathBuf;
use numi_ir::{EntryKind, Metadata, RawEntry};
use serde_json::json;
use std::{fs, path::Path};

fn write_files_job_config(config_path: &Path) {
    fs::write(
        config_path,
        r#"
version = 1

[jobs.files]
output = "Generated/Files.swift"

[[jobs.files.inputs]]
type = "files"
path = "Resources/Fixtures"

[jobs.files.template]
[jobs.files.template.builtin]
language = "swift"
name = "files"
"#,
    )
    .expect("config should be written");
}

#[test]
fn check_uses_cached_files_parse_and_still_reports_stale_outputs() {
    let temp_dir = make_temp_dir("pipeline-files-check-cache-hit");
    let config_path = temp_dir.join("numi.toml");
    let files_root = temp_dir.join("Resources/Fixtures");

    fs::create_dir_all(files_root.join("Onboarding")).expect("files directory should exist");
    fs::write(files_root.join("faq.pdf"), "faq").expect("faq file should be written");
    fs::write(files_root.join("Onboarding/welcome-video.mp4"), "video")
        .expect("video file should be written");
    fs::create_dir_all(temp_dir.join("Generated")).expect("generated dir should exist");
    write_files_job_config(&config_path);

    generate(&config_path, None).expect("initial generation should succeed");
    let generated_path = temp_dir.join("Generated/Files.swift");
    let baseline = fs::read_to_string(&generated_path).expect("generated output should exist");
    assert!(baseline.contains("welcomeVideoMp4"));

    seed_cached_parse(
        CacheKind::Files,
        &files_root,
        CachedParseData::Files(vec![RawEntry {
            path: "cached-guide.pdf".to_string(),
            source_path: Utf8PathBuf::from_path_buf(files_root.join("cached-guide.pdf"))
                .expect("cached source path should be utf8"),
            kind: EntryKind::Data,
            properties: Metadata::from([
                ("relativePath".to_string(), json!("cached-guide.pdf")),
                ("fileName".to_string(), json!("cached-guide.pdf")),
            ]),
        }]),
    )
    .expect("files cache should be seeded");

    let report = check(&config_path, None).expect("check should succeed");

    assert_eq!(
        report.stale_paths,
        vec![Utf8PathBuf::from_path_buf(generated_path).expect("utf8 output path")]
    );

    fs::remove_dir_all(temp_dir).expect("temp dir should be removed");
}

#[test]
fn check_degrades_when_cache_root_is_unusable() {
    let temp_dir = make_temp_dir("pipeline-cache-degrade-check");
    let config_path = temp_dir.join("numi.toml");
    let files_root = temp_dir.join("Resources/Fixtures");
    let generated_path = temp_dir.join("Generated/Files.swift");
    let bad_tmp = temp_dir.join("not-a-directory");

    fs::create_dir_all(files_root.join("Onboarding")).expect("files directory should exist");
    fs::write(files_root.join("faq.pdf"), "faq").expect("faq file should be written");
    fs::write(files_root.join("Onboarding/welcome-video.mp4"), "video")
        .expect("video file should be written");
    fs::create_dir_all(temp_dir.join("Generated")).expect("generated dir should exist");
    fs::write(&bad_tmp, "cache root blocker").expect("bad tmp file should exist");
    write_files_job_config(&config_path);

    generate(&config_path, None).expect("initial generation should succeed");
    fs::write(&generated_path, "stale output").expect("generated output should be mutated");

    let report = with_temp_dir_override(&bad_tmp, || check(&config_path, None))
        .expect("check should succeed without cache access");

    assert_eq!(
        report.stale_paths,
        vec![Utf8PathBuf::from_path_buf(generated_path).expect("utf8 output path")]
    );

    fs::remove_dir_all(temp_dir).expect("temp dir should be removed");
}