rustoku-cli 0.14.0

Command-line interface for the Rustoku library
use assert_cmd::prelude::*;
use rustoku_lib::RustokuError;
use std::process::Command;

/// Helper function to get the path to our compiled binary
fn get_rustoku_bin() -> Command {
    // This assumes our binary is named 'rustoku' and is in target/debug or target/release
    // We'll use cargo's `env!("CARGO_BIN_EXE_rustoku-cli")` for robustness
    Command::new(env!("CARGO_BIN_EXE_rustoku-cli"))
}

#[test]
fn test_version_command() {
    get_rustoku_bin()
        .arg("-V")
        .assert()
        .success() // Assert that the command exited successfully
        .stdout(predicates::str::starts_with("rustoku-cli ")); // Assert stdout starts with "rustoku "
}

#[test]
fn test_generate_default_clues() {
    let output = get_rustoku_bin()
        .arg("generate")
        .assert()
        .success()
        .get_output()
        .stdout
        .clone();

    let output_str = String::from_utf8(output).unwrap();
    let puzzle = output_str
        .lines()
        .find(|line| line.starts_with("Line format:"))
        .expect("Line representation is missing")
        .trim_start_matches("Line format: ");
    assert_eq!(puzzle.len(), 81);
}

#[test]
fn test_generate_custom_clues() {
    get_rustoku_bin()
        .arg("generate")
        .arg("--clues")
        .arg("25")
        .assert()
        .success(); // This should have the same output as the default
}

#[test]
fn test_solve_any_some_solution() {
    get_rustoku_bin()
        .arg("solve")
        .arg("any")
        .arg("530070000600195000098000060800060003400803001700020006060000280000419005000080079")
        .assert()
        .success()
        .stdout(predicates::str::contains(
            "534678912672195348198342567859761423426853791713924856961537284287419635345286179",
        ));
}

#[test]
fn test_solve_any_no_solution() {
    get_rustoku_bin()
        .arg("solve")
        .arg("any")
        .arg("078002609030008020002000083000000040043090000007300090200001036001840902050003007")
        .assert()
        .success()
        .stdout(predicates::str::contains("No solution found"));
}

#[test]
fn test_solve_any_invalid_puzzle() {
    get_rustoku_bin()
        .arg("solve")
        .arg("any")
        .arg("short") // Invalid length
        .assert()
        .failure() // Expect the command to fail
        .stderr(predicates::str::contains(
            RustokuError::InvalidInputLength.to_string(),
        ));
}

#[test]
fn test_solve_all_two_solutions() {
    get_rustoku_bin()
        .arg("solve")
        .arg("all")
        .arg("295743861431865900876192543387459216612387495549216738763504189928671354154930000")
        .assert()
        .success()
        .stdout(predicates::str::contains("Found 2 solutions"));
}

#[test]
fn test_check_correct_solution() {
    get_rustoku_bin()
        .arg("check")
        .arg("295743861431865927876192543387459216612387495549216738763524189154938672928671354")
        .assert()
        .success()
        .stdout(predicates::str::contains("Puzzle is solved correctly"));
}

#[test]
fn test_check_incorrect_solution() {
    get_rustoku_bin()
        .arg("check")
        .arg("295743861431865927876192543387459216612387495549216738763524189154938672928671350") // Last digit changed to 0
        .assert()
        .success()
        .stdout(predicates::str::contains("Puzzle is not solved correctly"));
}

#[test]
fn test_show_puzzle() {
    get_rustoku_bin()
        .arg("show")
        .arg("900507400007080000830401006402000300090000065000050080200908000080074000700210803")
        .assert()
        .success()
        .stdout(predicates::str::contains("9005")) // Board start
        .stdout(predicates::str::contains("0803")); // Board end
}

#[test]
fn test_solve_all_with_until_flag_limits_results() {
    // Unique puzzle -> until=1 should return exactly one solution
    get_rustoku_bin()
        .arg("solve")
        .arg("all")
        .arg("530070000600195000098000060800060003400803001700020006060000280000419005000080079")
        .arg("--until")
        .arg("1")
        .assert()
        .success()
        .stdout(predicates::str::contains("Found 1 unique solution"));

    // Two-solution puzzle -> until=2 should return at most two solutions
    get_rustoku_bin()
        .arg("solve")
        .arg("all")
        .arg("295743861431865900876192543387459216612387495549216738763504189928671354154938600")
        .arg("--until")
        .arg("2")
        .assert()
        .success()
        .stdout(predicates::str::contains("Found 2 solutions"));

    // 63-solution puzzle -> until=5 should return at most 5 solutions
    get_rustoku_bin()
        .arg("solve")
        .arg("all")
        .arg("000000500080760092001005470056309000009001004320500010000200700700090030000000000")
        .arg("--until")
        .arg("5")
        .assert()
        .success()
        .stdout(predicates::str::contains("Found 5 solutions"));
}

const SUDOKU_CSV_SAMPLE: &str = "\
quizzes,solutions
004300209005009001070060043006002087190007400050083000600000105003508690042910300,864371259325849761971265843436192587198657432257483916689734125713528694542916378
040100050107003960520008000000000017000906800803050620090060543600080700250097100,346179258187523964529648371965832417472916835813754629798261543631485792254397186
600120384008459072000006005000264030070080006940003000310000050089700000502000190,695127384138459672724836915851264739273981546946573821317692458489715263562348197
497200000100400005000016098620300040300900000001072600002005870000600004530097061,497258316186439725253716498629381547375964182841572639962145873718623954534897261
005910308009403060027500100030000201000820007006007004000080000640150700890000420,465912378189473562327568149738645291954821637216397854573284916642159783891736425
100005007380900000600000480820001075040760020069002001005039004000020100000046352,194685237382974516657213489823491675541768923769352841215839764436527198978146352
009065430007000800600108020003090002501403960804000100030509007056080000070240090,289765431317924856645138729763891542521473968894652173432519687956387214178246395
000000657702400100350006000500020009210300500047109008008760090900502030030018206,894231657762495183351876942583624719219387564647159328128763495976542831435918276
503070190000006750047190600400038000950200300000010072000804001300001860086720005,563472198219386754847195623472638519951247386638519472795864231324951867186723945
060720908084003001700100065900008000071060000002010034000200706030049800215000090,163725948584693271729184365946358127371462589852917634498231756637549812215876493
004083002051004300000096710120800006040000500830607900060309040007000205090050803,974183652651274389283596714129835476746912538835647921568329147317468295492751863
000060280709001000860320074900040510007190340003006002002970000300800905500000021,431567289729481653865329174986243517257198346143756892612975438374812965598634721
004300000890200670700900050500008140070032060600001308001750900005040012980006005,254367891893215674716984253532698147178432569649571328421753986365849712987126435
008070100120090054000003020604010089530780010009062300080040607007506000400800002,958274163123698754746153928674315289532789416819462375285941637397526841461837592
065370002000001370000640800097004028080090001100020940040006700070018050230900060,865379412924581376713642895397164528482795631156823947541236789679418253238957164
005710329000362800004000000100000980083900250006003100300106000409800007070029500,865714329917362845234598761142657983783941256596283174358176492429835617671429538
200005300000073850000108904070009001651000040040200080300050000580760100410030096,268495317194673852735128964872549631651387249943216785326951478589764123417832596
040800500080760092001005470056309000009001004320500010000200700700090030005008026,947812563583764192261935478156349287879621354324587619698253741712496835435178926
050083017000100400304005608000030009090824500006000070009000050007290086103607204,652483917978162435314975628825736149791824563436519872269348751547291386183657294
700084005300701020080260401624109038803600010000000002900000000001005790035400006,712984365346751829589263471624179538853642917197538642978316254461825793235497186\
";

#[test]
fn test_solve_csv_dataset() {
    let temp_dir = std::env::temp_dir();
    let temp_file = temp_dir.join("sudoku_sample.csv");
    std::fs::write(&temp_file, SUDOKU_CSV_SAMPLE).unwrap();

    get_rustoku_bin()
        .arg("solve")
        .arg("csv")
        .arg(temp_file.to_str().unwrap())
        .arg("--stats-only")
        .arg("--human")
        .assert()
        .success()
        .stdout(predicates::str::contains("Statistics:"))
        .stdout(predicates::str::contains("Total puzzles: 20"))
        .stdout(predicates::str::contains("Solved: 20"));

    std::fs::remove_file(temp_file).ok();
}