semora-cli 1.1.0

Semora CLI: publish Markdown and HTML, and hear back from the page
Documentation
//! A pushed folder holding a name with a colon: the name is left out with a warning and the rest
//! is pushed. The server refuses a colon in any bundle path, so one such name would otherwise
//! fail the whole push.
//!
//! Windows cannot hold such a name (`a:b.md` there is a stream of the file `a`), so this runs on
//! Unix. `bundle.rs` has a test of the same rule on names alone, which runs everywhere.
#![cfg(unix)]

mod common;

use common::*;

#[tokio::test(flavor = "multi_thread")]
async fn a_colon_in_a_name_is_skipped_and_the_rest_is_pushed() {
    let server = Server::start().await;
    let cli = Cli::new(&server);
    cli.write("site/index.md", "# Site");
    cli.write("site/a:b.md", "# Colon file");
    cli.write("site/v1:final/one.md", "# Colon folder");
    cli.write("site/v1:final/deeper/two.md", "# Inside the colon folder");

    let out = cli.run(&["push", "site", "--json"]).await;
    assert_eq!(out.code, 0, "the push fails: {}", out.stderr);
    assert!(
        out.stderr.contains("skipped a:b.md"),
        "the file is named in a warning: {}",
        out.stderr
    );
    assert_eq!(
        out.stderr.matches("v1:final").count(),
        1,
        "the folder is named once, and not file by file: {}",
        out.stderr
    );
    assert!(
        !out.stderr.contains("one.md") && !out.stderr.contains("two.md"),
        "the files inside the folder are not named: {}",
        out.stderr
    );

    let v = out.json();
    assert_eq!(v["files"], 1, "{v}");
    let id = v["id"].as_str().unwrap();
    let detail: serde_json::Value = reqwest::Client::new()
        .get(format!("{}/api/v1/docs/{id}", server.url))
        .bearer_auth(&server.key)
        .send()
        .await
        .unwrap()
        .json()
        .await
        .unwrap();
    let paths: Vec<&str> = detail["files"]
        .as_array()
        .unwrap()
        .iter()
        .map(|f| f["path"].as_str().unwrap())
        .collect();
    assert_eq!(paths, vec!["index.md"], "{detail}");
}

#[tokio::test(flavor = "multi_thread")]
async fn an_entry_that_was_skipped_says_why() {
    let server = Server::start().await;
    let cli = Cli::new(&server);
    cli.write("site/index.md", "# Site");
    cli.write("site/a:b.md", "# Colon file");
    cli.write("site/v1:final/one.md", "# Colon folder");

    for (entry, skipped) in [("a:b.md", "a:b.md"), ("v1:final/one.md", "v1:final")] {
        let out = cli.run(&["push", "site", "--entry", entry]).await;
        assert_eq!(out.code, 1, "{}", out.stderr);
        assert!(
            out.stderr.contains(&format!(
                "--entry {entry} is not pushed: {skipped} was skipped (a colon in the"
            )),
            "{}",
            out.stderr
        );
    }
    let out = cli
        .run(&["push", "site", "--entry", "index.md", "--json"])
        .await;
    assert_eq!(out.code, 0, "{}", out.stderr);
}