magellan-cli 0.6.0

Deterministic presentation engine for AI-generated technical walkthroughs
use std::fs;
use std::path::{Path, PathBuf};

use assert_cmd::Command;

struct FixtureCase {
    path: &'static str,
    title: &'static str,
    terminal_markers: &'static [&'static str],
    markdown_markers: &'static [&'static str],
    html_markers: &'static [&'static str],
}

fn fixture_path(relative: &str) -> PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR")).join(relative)
}

fn fixture_cases() -> [FixtureCase; 7] {
    [
        FixtureCase {
            path: "examples/session-walkthrough.json",
            title: "Session walkthrough: checkout validation moved in front of order submission",
            terminal_markers: &[
                "How the request flow changed",
                "Sequence",
                "Flow",
                "Component graph",
                "Verification",
            ],
            markdown_markers: &[
                "## How the request flow changed",
                "sequenceDiagram",
                "flowchart LR",
                "## Verification",
            ],
            html_markers: &[
                "Magellan walkthrough",
                "class=\"sidebar\"",
                "class=\"toc-link",
                "Sequence diagram",
                "Flow diagram",
                "Component diagram",
                "ASCII fallback",
            ],
        },
        FixtureCase {
            path: "examples/branch-handoff-timeline.json",
            title: "Handoff: search results hydration cleanup across the branch",
            terminal_markers: &[
                "Timeline of the change",
                "Timeline",
                "Steady-state flow after the cleanup",
                "Flow",
            ],
            markdown_markers: &[
                "## Timeline of the change",
                "timeline",
                "title Timeline",
                "flowchart LR",
            ],
            html_markers: &[
                "Magellan walkthrough",
                "class=\"sidebar\"",
                "class=\"toc-link",
                "Timeline",
                "Flow diagram",
                "Verification",
                "ASCII fallback",
            ],
        },
        FixtureCase {
            path: "examples/followup-validation-question.json",
            title: "Follow-up: why the retry guard moved into the background worker",
            terminal_markers: &["Why the worker owns retries now", "Flow", "Before", "After"],
            markdown_markers: &[
                "## Why the worker owns retries now",
                "flowchart LR",
                "subgraph Before",
                "subgraph After",
            ],
            html_markers: &[
                "Magellan walkthrough",
                "class=\"sidebar\"",
                "class=\"toc-link",
                "Flow diagram",
                "Before / after",
                "ASCII fallback",
            ],
        },
        FixtureCase {
            path: "examples/state-machine-order-lifecycle.json",
            title: "State machine: order lifecycle after the refund refactor",
            terminal_markers: &[
                "Order lifecycle states",
                "State machine",
                "Pending --place order--> Paid",
            ],
            markdown_markers: &[
                "## Order lifecycle states",
                "stateDiagram-v2",
                "Pending --> Paid: place order",
            ],
            html_markers: &[
                "Magellan walkthrough",
                "class=\"sidebar\"",
                "State machine",
                "ASCII fallback",
            ],
        },
        FixtureCase {
            path: "examples/layer-stack-request-pipeline.json",
            title: "Layer stack: inbound request pipeline",
            terminal_markers: &["Layer stack", "[Edge CDN]", "[Load Balancer]"],
            markdown_markers: &["## Pipeline layers", "block-beta"],
            html_markers: &[
                "Magellan walkthrough",
                "class=\"sidebar\"",
                "Layer stack",
                "ASCII fallback",
            ],
        },
        FixtureCase {
            path: "examples/table-permission-matrix.json",
            title: "Table: permission matrix after the role rewrite",
            terminal_markers: &["Role", "admin", "viewer"],
            markdown_markers: &[
                "## Permission matrix",
                "| Role | Create | Delete | Invite |",
                "| --- | --- | --- | --- |",
                "| admin | yes | yes | yes |",
            ],
            html_markers: &[
                "Magellan walkthrough",
                "class=\"sidebar\"",
                "Table",
                "ASCII fallback",
            ],
        },
        FixtureCase {
            path: "examples/dependency-tree-service-modules.json",
            title: "Dependency tree: payment service module layout",
            terminal_markers: &["payment_service", "├──", "└──"],
            markdown_markers: &[
                "## Service module layout",
                "flowchart TD",
                "ROOT[\"payment_service\"]",
            ],
            html_markers: &[
                "Magellan walkthrough",
                "class=\"sidebar\"",
                "Dependency tree",
                "ASCII fallback",
            ],
        },
    ]
}

#[test]
fn checked_in_fixture_payloads_validate_with_real_binary() {
    for case in fixture_cases() {
        Command::cargo_bin("magellan")
            .expect("binary should build")
            .args(["validate", "--input"])
            .arg(fixture_path(case.path))
            .assert()
            .success();
    }
}

#[test]
fn checked_in_fixtures_render_expected_terminal_and_markdown_output() {
    for case in fixture_cases() {
        let terminal_output = Command::cargo_bin("magellan")
            .expect("binary should build")
            .args(["render", "--input"])
            .arg(fixture_path(case.path))
            .args(["--format", "terminal"])
            .assert()
            .success()
            .get_output()
            .stdout
            .clone();
        let terminal = String::from_utf8(terminal_output).expect("terminal output should be utf-8");

        assert!(
            terminal.contains(case.title),
            "terminal render for {} should contain the title",
            case.path
        );
        for marker in case.terminal_markers {
            assert!(
                terminal.contains(marker),
                "terminal render for {} should contain marker {:?}",
                case.path,
                marker
            );
        }

        let markdown_output = Command::cargo_bin("magellan")
            .expect("binary should build")
            .args(["render", "--input"])
            .arg(fixture_path(case.path))
            .args(["--format", "markdown"])
            .assert()
            .success()
            .get_output()
            .stdout
            .clone();
        let markdown = String::from_utf8(markdown_output).expect("markdown output should be utf-8");

        assert!(
            markdown.contains(&format!("# {}", case.title)),
            "markdown render for {} should contain the title",
            case.path
        );
        for marker in case.markdown_markers {
            assert!(
                markdown.contains(marker),
                "markdown render for {} should contain marker {:?}",
                case.path,
                marker
            );
        }
    }
}

#[test]
fn checked_in_fixtures_render_expected_html_output() {
    let temp_dir = tempfile::tempdir().expect("temp dir should be created");

    for (index, case) in fixture_cases().into_iter().enumerate() {
        let output_path = temp_dir.path().join(format!("fixture-{index}.html"));

        Command::cargo_bin("magellan")
            .expect("binary should build")
            .args(["render", "--input"])
            .arg(fixture_path(case.path))
            .args(["--format", "html", "--out"])
            .arg(&output_path)
            .assert()
            .success();

        let html = fs::read_to_string(&output_path).expect("html output should be readable");
        assert!(
            html.contains(&format!("<title>{}</title>", case.title)),
            "html render for {} should contain the title element",
            case.path
        );
        assert!(
            !html.contains("https://") && !html.contains("http://"),
            "html render for {} should stay self-contained",
            case.path
        );
        for marker in case.html_markers {
            assert!(
                html.contains(marker),
                "html render for {} should contain marker {:?}",
                case.path,
                marker
            );
        }
        assert!(
            html.contains("class=\"sidebar\""),
            "html render for {} should include the sidebar",
            case.path
        );
        assert!(
            html.contains("class=\"toc-link"),
            "html render for {} should include toc links",
            case.path
        );
        assert!(
            html.contains("data-theme-toggle"),
            "html render for {} should include the theme toggle",
            case.path
        );
    }
}

#[test]
fn session_fixture_html_includes_expected_scroll_structure() {
    let temp_dir = tempfile::tempdir().expect("temp dir should be created");
    let output_path = temp_dir.path().join("session-scroll.html");

    Command::cargo_bin("magellan")
        .expect("binary should build")
        .args(["render", "--input"])
        .arg(fixture_path("examples/session-walkthrough.json"))
        .args(["--format", "html", "--out"])
        .arg(&output_path)
        .assert()
        .success();

    let html = fs::read_to_string(&output_path).expect("html output should be readable");
    let section_count = html.matches("class=\"section\"").count();
    let verification_count = html.matches("class=\"section verification\"").count();
    let toc_link_count = html.matches("class=\"toc-link").count();
    let diagram_count = html.matches("class=\"diagram\"").count();

    assert_eq!(section_count, 3, "3 regular sections");
    assert_eq!(verification_count, 1, "1 verification section");
    assert_eq!(
        toc_link_count, 5,
        "summary + 3 sections + verification toc links"
    );
    assert_eq!(
        diagram_count, 3,
        "each section with a diagram should render one inline diagram"
    );
    assert!(html.contains("class=\"sidebar\""));
    assert!(html.contains("data-theme-toggle"));
    assert!(html.contains("[data-theme=\"light\"]"));
    assert!(!html.contains("data-book-track"));
    assert!(!html.contains("Book View"));
}