csaf-crud 1.4.11

CSAF 2.0 / 2.1 advisory CRUD server with HATEOAS JSON API and HTML UI (TLS 1.3, HTTP/1.1 + HTTP/2 + HTTP/3)
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2026 Pierre Gronau, ndaal in Cologne

//! `embedded_docs/` must not drift from the documents it copies.
//!
//! `crates/csaf-crud/src/routes/info.rs` serves its documentation with
//! `include_str!("../../embedded_docs/…")`. That indirection exists for a
//! good reason — `cargo publish` packages only files under the crate
//! directory, so embedding the repo-root copies directly would produce a
//! crate that fails to build for anyone who downloads it from crates.io.
//!
//! The cost of that indirection is a **silent** failure mode: the copy is
//! kept in step by hand, nothing enforces it, and a stale copy is invisible
//! locally. Everything compiles, every test passes, and the running server
//! serves last release's changelog. This suite makes that drift a test
//! failure instead of a support ticket.
//!
//! Deliberately skipped rather than failed when the repo-root file is
//! absent: that is exactly the published-crate case the indirection exists
//! for, where there is nothing to compare against and a hard failure would
//! break a legitimate downstream build.

use std::path::{Path, PathBuf};

/// Repo root, derived from this crate's manifest directory.
fn repo_root() -> PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR")).join("..").join("..")
}

/// The embedded copy and its source of truth, as `info.rs` embeds them.
const PAIRS: &[(&str, &str)] = &[
    ("CHANGELOG.md", "CHANGELOG.md"),
    ("README.md", "README.md"),
    (
        "Administrator_Guide.md",
        "documentation/Administrator_Guide.md",
    ),
    ("User_Guide.md", "documentation/User_Guide.md"),
];

#[test]
fn every_embedded_doc_matches_its_source() {
    let root = repo_root();
    let embedded_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("embedded_docs");
    let mut compared = 0_usize;

    for (embedded_name, source_rel) in PAIRS {
        let source = root.join(source_rel);
        if !source.is_file() {
            // Published-crate case: no repo root to compare against.
            continue;
        }
        let embedded_path = embedded_dir.join(embedded_name);
        let embedded = std::fs::read_to_string(&embedded_path).unwrap_or_else(|e| {
            panic!("{embedded_name} is embedded by info.rs but unreadable: {e}")
        });
        let expected = std::fs::read_to_string(&source)
            .unwrap_or_else(|e| panic!("{source_rel} unreadable: {e}"));

        assert_eq!(
            embedded, expected,
            "\n\
             embedded_docs/{embedded_name} has drifted from {source_rel}.\n\
             The server would serve the STALE copy — the mismatch is invisible\n\
             at runtime because both files compile and all other tests pass.\n\
             Re-sync with:\n    \
             cp -f -p -v {source_rel} crates/csaf-crud/embedded_docs/{embedded_name}\n"
        );
        compared += 1;
    }

    // A silently-empty loop would make this test pass forever without
    // comparing anything — the exact false green it exists to prevent.
    if root.join("CHANGELOG.md").is_file() {
        assert_eq!(
            compared,
            PAIRS.len(),
            "expected to compare all {} embedded documents, compared {compared}",
            PAIRS.len(),
        );
    }
}

#[test]
fn every_embedded_file_is_covered_by_this_test() {
    // A new `include_str!` in info.rs with no entry in PAIRS would drift
    // unnoticed, so the directory listing is the authority, not PAIRS.
    let embedded_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("embedded_docs");
    let entries = std::fs::read_dir(&embedded_dir).expect("embedded_docs must exist");

    for entry in entries {
        let entry = entry.expect("readable dir entry");
        let name = entry.file_name().to_string_lossy().into_owned();
        // Case-insensitive: macOS filesystems are case-preserving but
        // case-insensitive, so a `README.MD` would be served by info.rs
        // yet skipped by a case-sensitive check — unguarded drift.
        let is_markdown = Path::new(&name)
            .extension()
            .is_some_and(|ext| ext.eq_ignore_ascii_case("md"));
        if !is_markdown {
            continue;
        }
        assert!(
            PAIRS.iter().any(|(embedded, _)| *embedded == name),
            "embedded_docs/{name} is shipped but has no entry in PAIRS, so nothing \
             checks it for drift — add it to PAIRS in this file",
        );
    }
}