chipzen-sdk 0.3.0

CLI for the Chipzen poker bot SDK — scaffold and validate bot projects
Documentation
use chipzen_sdk::{scaffold_bot, validate_bot, ScaffoldOptions, Severity, ValidateOptions};
use std::fs;
use std::path::Path;
use tempfile::tempdir;

fn scaffolded(name: &str, parent: &Path) -> std::path::PathBuf {
    let opts = ScaffoldOptions {
        parent_dir: Some(parent.to_path_buf()),
    };
    scaffold_bot(name, &opts).unwrap()
}

fn write(path: &Path, contents: &str) {
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent).unwrap();
    }
    fs::write(path, contents).unwrap();
}

fn names_with_severity(results: &[chipzen_sdk::ValidationResult], sev: Severity) -> Vec<&str> {
    results
        .iter()
        .filter(|r| r.severity == sev)
        .map(|r| r.name.as_str())
        .collect()
}

#[test]
fn scaffolded_bot_passes_every_static_check() {
    let parent = tempdir().unwrap();
    let dir = scaffolded("scaffold_for_validate", parent.path());
    let results = validate_bot(&dir, &ValidateOptions::default()).unwrap();

    let passes = names_with_severity(&results, Severity::Pass);
    let fails = names_with_severity(&results, Severity::Fail);
    assert!(fails.is_empty(), "expected no fails, got: {fails:?}");
    assert!(passes.contains(&"size"));
    assert!(passes.contains(&"file_structure"));
    assert!(passes.contains(&"cargo_metadata"));
    assert!(passes.contains(&"imports"));
    assert!(passes.contains(&"bot_impl"));
    assert!(passes.contains(&"decide_method"));
}

#[test]
fn validate_reports_missing_path() {
    let parent = tempdir().unwrap();
    let results = validate_bot(
        &parent.path().join("does-not-exist"),
        &ValidateOptions::default(),
    )
    .unwrap();
    assert_eq!(results[0].severity, Severity::Fail);
    assert_eq!(results[0].name, "file_structure");
}

#[test]
fn validate_reports_missing_cargo_toml() {
    let parent = tempdir().unwrap();
    let dir = parent.path().join("empty_dir");
    fs::create_dir(&dir).unwrap();
    let results = validate_bot(&dir, &ValidateOptions::default()).unwrap();
    let fails = names_with_severity(&results, Severity::Fail);
    assert!(fails.contains(&"file_structure"));
}

#[test]
fn validate_reports_missing_chipzen_bot_dependency() {
    let parent = tempdir().unwrap();
    let dir = parent.path().join("no_dep_bot");
    fs::create_dir(&dir).unwrap();
    write(
        &dir.join("Cargo.toml"),
        r#"[package]
name = "no_dep_bot"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = "1"
"#,
    );
    write(
        &dir.join("src").join("main.rs"),
        "fn main() {} impl Bot for X {} fn decide() {}",
    );
    let results = validate_bot(&dir, &ValidateOptions::default()).unwrap();
    let imports_fail = results
        .iter()
        .find(|r| r.name == "imports" && r.severity == Severity::Fail)
        .expect("expected imports fail when chipzen-bot is missing");
    assert!(imports_fail.message.contains("chipzen-bot"));
}

#[test]
fn validate_reports_blocked_dependencies() {
    let parent = tempdir().unwrap();
    let dir = parent.path().join("blocked_deps");
    fs::create_dir(&dir).unwrap();
    write(
        &dir.join("Cargo.toml"),
        r#"[package]
name = "blocked_deps"
version = "0.1.0"
edition = "2021"

[dependencies]
chipzen-bot = "0.2"
pnet = "0.34"
"#,
    );
    write(
        &dir.join("src").join("main.rs"),
        "use chipzen_bot::*; impl Bot for X {} fn decide() {}",
    );
    let results = validate_bot(&dir, &ValidateOptions::default()).unwrap();
    let imports = results.iter().find(|r| r.name == "imports").unwrap();
    assert_eq!(imports.severity, Severity::Fail);
    assert!(imports.message.contains("pnet"));
}

#[test]
fn validate_warns_on_fs_friendly_dependencies_without_failing_run() {
    let parent = tempdir().unwrap();
    let dir = parent.path().join("warn_deps");
    fs::create_dir(&dir).unwrap();
    write(
        &dir.join("Cargo.toml"),
        r#"[package]
name = "warn_deps"
version = "0.1.0"
edition = "2021"

[dependencies]
chipzen-bot = "0.2"
tempfile = "3"
"#,
    );
    write(
        &dir.join("src").join("main.rs"),
        "impl Bot for X {} fn decide() {}",
    );
    let results = validate_bot(&dir, &ValidateOptions::default()).unwrap();
    let warns = names_with_severity(&results, Severity::Warn);
    assert!(warns.contains(&"imports"));
    assert!(names_with_severity(&results, Severity::Fail).is_empty());
}

#[test]
fn validate_reports_missing_bot_impl() {
    let parent = tempdir().unwrap();
    let dir = parent.path().join("no_bot_impl");
    fs::create_dir(&dir).unwrap();
    write(
        &dir.join("Cargo.toml"),
        r#"[package]
name = "no_bot_impl"
version = "0.1.0"
edition = "2021"

[dependencies]
chipzen-bot = "0.2"
"#,
    );
    write(
        &dir.join("src").join("main.rs"),
        "// Just a comment about Bot for ... but no actual impl.\nfn main() {}",
    );
    let results = validate_bot(&dir, &ValidateOptions::default()).unwrap();
    let bot_impl = results.iter().find(|r| r.name == "bot_impl").unwrap();
    assert_eq!(bot_impl.severity, Severity::Fail);
}

#[test]
fn validate_reports_missing_decide_method() {
    let parent = tempdir().unwrap();
    let dir = parent.path().join("no_decide");
    fs::create_dir(&dir).unwrap();
    write(
        &dir.join("Cargo.toml"),
        r#"[package]
name = "no_decide"
version = "0.1.0"
edition = "2021"

[dependencies]
chipzen-bot = "0.2"
"#,
    );
    write(
        &dir.join("src").join("main.rs"),
        "impl Bot for X { /* fn decide -- in a comment, not real */ }",
    );
    let results = validate_bot(&dir, &ValidateOptions::default()).unwrap();
    let decide = results.iter().find(|r| r.name == "decide_method").unwrap();
    assert_eq!(decide.severity, Severity::Fail);
}

#[test]
fn validate_handles_unparseable_cargo_toml() {
    let parent = tempdir().unwrap();
    let dir = parent.path().join("bad_toml");
    fs::create_dir(&dir).unwrap();
    write(&dir.join("Cargo.toml"), "this is not valid TOML {{{");
    write(&dir.join("src").join("main.rs"), "fn main() {}");
    let results = validate_bot(&dir, &ValidateOptions::default()).unwrap();
    let fails = names_with_severity(&results, Severity::Fail);
    assert!(fails.contains(&"cargo_metadata"));
}