yams/tests/unit/cli/repair_command_catch2_test.cpp

216 lines
7.1 KiB
C++

// CLI Repair Command tests
// Catch2 tests for repair command option parsing and behavior
//
// Tests for repair command flag handling, verbose mode defaults,
// and CLI option isolation.
#include <catch2/catch_test_macros.hpp>
#include <CLI/CLI.hpp>
#include <yams/cli/command.h>
#include <yams/cli/yams_cli.h>
#include <filesystem>
#include <iostream>
#include <optional>
#include <sstream>
#include <string>
#include <vector>
// Include test helpers for ScopedEnvVar
#include "../../common/test_helpers_catch2.h"
namespace fs = std::filesystem;
using namespace yams::cli;
namespace yams::cli {
std::unique_ptr<ICommand> createRepairCommand();
}
namespace {
/**
* RAII helper for repair command tests with proper isolation and cleanup.
*/
struct RepairTestHelper {
fs::path tempDir;
std::optional<yams::test::ScopedEnvVar> configEnv;
std::optional<yams::test::ScopedEnvVar> dataEnv;
std::optional<yams::test::ScopedEnvVar> nonInteractiveEnv;
std::optional<yams::test::ScopedEnvVar> disableDaemonEnv;
RepairTestHelper() {
tempDir = yams::test::make_temp_dir("yams_repair_catch2_test_");
fs::create_directories(tempDir);
configEnv.emplace("YAMS_CONFIG", (tempDir / "config.toml").string());
dataEnv.emplace("YAMS_DATA_DIR", (tempDir / "data").string());
nonInteractiveEnv.emplace(std::string("YAMS_NON_INTERACTIVE"),
std::optional<std::string>("1"));
// Disable daemon autostart to prevent cleanup crashes
disableDaemonEnv.emplace(std::string("YAMS_CLI_DISABLE_DAEMON_AUTOSTART"),
std::optional<std::string>("1"));
}
~RepairTestHelper() {
configEnv.reset();
dataEnv.reset();
nonInteractiveEnv.reset();
disableDaemonEnv.reset();
std::error_code ec;
fs::remove_all(tempDir, ec);
}
std::unique_ptr<YamsCLI> makeCli() const { return std::make_unique<YamsCLI>(); }
int runCommand(const std::vector<std::string>& args) {
auto cli = makeCli();
std::vector<char*> argv;
argv.reserve(args.size());
for (const auto& arg : args) {
argv.push_back(const_cast<char*>(arg.c_str()));
}
return cli->run(static_cast<int>(argv.size()), argv.data());
}
};
/**
* RAII helper to capture and restore cout.
*/
class CaptureStdout {
public:
CaptureStdout() : oldCout_(std::cout.rdbuf(buffer_.rdbuf())) {}
~CaptureStdout() { std::cout.rdbuf(oldCout_); }
std::string str() const { return buffer_.str(); }
CaptureStdout(const CaptureStdout&) = delete;
CaptureStdout& operator=(const CaptureStdout&) = delete;
private:
std::ostringstream buffer_;
std::streambuf* oldCout_;
};
void parseRepairCommandWithHelp(const std::vector<const char*>& argv) {
CLI::App app{"yams"};
auto repairCmd = yams::cli::createRepairCommand();
repairCmd->registerCommand(app, /*cli*/ nullptr);
bool gotHelp = false;
bool gotParseError = false;
std::string parseErrorMsg;
try {
app.parse(static_cast<int>(argv.size()), const_cast<char**>(argv.data()));
} catch (const CLI::CallForHelp&) {
gotHelp = true;
} catch (const CLI::ParseError& e) {
gotParseError = true;
parseErrorMsg = e.what();
}
CHECK(gotHelp);
if (gotParseError) {
FAIL("Unexpected parse error: " << parseErrorMsg);
}
}
} // namespace
TEST_CASE("RepairCommand - help displays without errors", "[cli][repair][catch2]") {
RepairTestHelper helper;
CaptureStdout capture;
auto cli = helper.makeCli();
const char* argv[] = {"yams", "repair", "--help"};
// Should not throw - --help exits early
(void)cli->run(3, const_cast<char**>(argv));
std::string output = capture.str();
// Should show repair help text
CHECK(output.find("repair") != std::string::npos);
CHECK(output.find("--orphans") != std::string::npos);
CHECK(output.find("--fts5") != std::string::npos);
CHECK(output.find("--embeddings") != std::string::npos);
CHECK(output.find("--dedupe") != std::string::npos);
}
TEST_CASE("RepairCommand - verbose flag is documented correctly", "[cli][repair][catch2]") {
RepairTestHelper helper;
CaptureStdout capture;
auto cli = helper.makeCli();
const char* argv[] = {"yams", "repair", "--help"};
(void)cli->run(3, const_cast<char**>(argv));
std::string output = capture.str();
// Verbose flag should mention transient errors (the key change from the refactor)
CHECK(output.find("--verbose") != std::string::npos);
CHECK(output.find("-v") != std::string::npos);
}
TEST_CASE("RepairCommand - option flags parse correctly", "[cli][repair][catch2]") {
parseRepairCommandWithHelp({"yams", "repair", "--orphans", "--mime", "--chunks", "--downloads",
"--path-tree", "--stuck", "--dedupe", "--help"});
}
// Note: Tests that actually run repair operations (--fts5, --embeddings, --all, etc.)
// are skipped because they require database access and can hang in unit test environment.
// Integration tests should cover these scenarios with proper daemon/database setup.
TEST_CASE("RepairCommand - fts5 flag parses correctly", "[cli][repair][catch2]") {
parseRepairCommandWithHelp({"yams", "repair", "--fts5", "--help"});
}
TEST_CASE("RepairCommand - embeddings flag with verbose parses correctly",
"[cli][repair][catch2]") {
parseRepairCommandWithHelp(
{"yams", "repair", "--embeddings", "--verbose", "--foreground", "--help"});
}
TEST_CASE("RepairCommand - all flag and refs parse correctly", "[cli][repair][catch2]") {
parseRepairCommandWithHelp(
{"yams", "repair", "--all", "--refs", "--graph", "--dedupe", "--help"});
}
TEST_CASE("RepairCommand - model option accepts value", "[cli][repair][catch2]") {
parseRepairCommandWithHelp(
{"yams", "repair", "--embeddings", "--model", "test-embed-model", "--help"});
}
TEST_CASE("RepairCommand - max retries option accepts value", "[cli][repair][catch2]") {
parseRepairCommandWithHelp({"yams", "repair", "--stuck", "--max-retries", "5", "--help"});
}
TEST_CASE("RepairCommand - complex flag set parses with help", "[cli][repair][catch2]") {
CLI::App app{"yams"};
auto repairCmd = yams::cli::createRepairCommand();
repairCmd->registerCommand(app, /*cli*/ nullptr);
std::vector<const char*> argv = {"yams", "repair",
"--all", "--refs",
"--graph", "--foreground",
"--dry-run", "--force",
"--max-retries", "5",
"--model", "test-embed-model",
"--include-mime", "application/pdf",
"--include-mime", "text/markdown",
"--help"};
parseRepairCommandWithHelp(argv);
}
TEST_CASE("RepairCommand - default invocation reaches callback without throwing",
"[cli][repair][catch2]") {
RepairTestHelper helper;
REQUIRE_NOTHROW((void)helper.runCommand({"yams", "repair", "--help"}));
}