// Characterization tests for the `yams daemon status` renderers. // // The renderers are pure StatusResponse -> text, so a synthetic response drives every branch // the CLI used to reach only through a live daemon RPC. #include #include #include #include #include #include #include #include #include #include namespace { using yams::cli::DaemonStatusRenderContext; using yams::daemon::MemorySyncResponse; using yams::daemon::StatusResponse; struct PlainColors { yams::cli::ui::ColorMode saved{yams::cli::ui::color_mode()}; PlainColors() { yams::cli::ui::set_colors_enabled_override(false); } ~PlainColors() { yams::cli::ui::set_color_mode(saved); } }; StatusResponse readyDaemon() { StatusResponse s{}; s.running = true; s.ready = true; s.version = "9.9.9"; s.uptimeSeconds = 125; s.requestsProcessed = 42; s.lifecycleState = "ready"; s.memoryUsageMb = 512; s.embeddingAvailable = true; s.embeddingModel = "all-MiniLM-L6-v2"; s.contentStoreRoot = "/srv/yams/storage"; s.vectorDbInitAttempted = true; s.vectorDbReady = true; s.readinessStates[std::string(yams::daemon::readiness::kDatabase)] = true; s.requestCounts["repair_running"] = 1; return s; } std::string renderBrief(const StatusResponse& s, const DaemonStatusRenderContext& ctx) { std::ostringstream os; yams::cli::renderDaemonStatusBrief(s, ctx, os); return os.str(); } std::string renderDetailed(const StatusResponse& s, const DaemonStatusRenderContext& ctx) { std::ostringstream os; yams::cli::renderDaemonStatusDetailed(s, ctx, os); return os.str(); } std::string lineContaining(const std::string& text, std::string_view needle) { std::istringstream lines(text); for (std::string line; std::getline(lines, line);) { if (line.find(needle) != std::string::npos) { return line; } } return {}; } } // namespace TEST_CASE("primary status is flushed before optional collection", "[cli][status-order]") { PlainColors plain; for (bool detailed : {false, true}) { struct Buffer : std::stringbuf { bool flushed = false; int sync() override { flushed = true; return std::stringbuf::sync(); } } buffer; std::ostream os(&buffer); bool called = false; yams::cli::renderDaemonStatusWithOptionalSection(readyDaemon(), {}, detailed, os, [&](std::ostream& out) { called = true; CHECK_FALSE(buffer.str().empty()); CHECK(buffer.flushed); out << "optional-status"; }); CHECK(called); CHECK(buffer.str().find("optional-status") != std::string::npos); } } TEST_CASE("daemon status brief renders the overview from a synthetic response", "[cli][daemon][status][catch2]") { PlainColors plain; DaemonStatusRenderContext ctx; ctx.configuredDataDir = "/srv/yams"; const std::string out = renderBrief(readyDaemon(), ctx); CHECK(out.find("YAMS Daemon") != std::string::npos); CHECK(out.find("Ready") != std::string::npos); CHECK(out.find("v9.9.9") != std::string::npos); CHECK(out.find("42 requests") != std::string::npos); CHECK(out.find("all-MiniLM-L6-v2") != std::string::npos); CHECK(out.find("Running") != std::string::npos); CHECK(out.find("Data dir warning") == std::string::npos); CHECK(out.find("Waiting on:") == std::string::npos); CHECK(out.find("Error:") == std::string::npos); CHECK(out.find("Memory Sync") == std::string::npos); CHECK(out.find("yams daemon status -d") != std::string::npos); } TEST_CASE("daemon status names a running integrity check instead of 'Opening'", "[cli][daemon][status][catch2]") { PlainColors plain; StatusResponse s = readyDaemon(); s.ready = false; s.lifecycleState = "initializing"; s.readinessStates[std::string(yams::daemon::readiness::kDatabase)] = false; s.databasePhase = std::string(yams::daemon::dbphase::kCheckingIntegrity); s.databasePhaseElapsedMs = 671000; s.metadataDbPath = "/srv/yams/yams.db"; const std::string out = renderBrief(s, {}); INFO(out); CHECK(out.find("Checking integrity") != std::string::npos); CHECK(out.find("11m") != std::string::npos); CHECK(out.find("full scan after unclean stop") != std::string::npos); CHECK(out.find("Opening") == std::string::npos); } TEST_CASE("daemon status shows the vector index as pending while vectors initialize", "[cli][daemon][status][catch2]") { PlainColors plain; StatusResponse s = readyDaemon(); s.ready = false; s.lifecycleState = "initializing"; s.vectorDbInitAttempted = false; s.vectorDbReady = false; s.readinessStates[std::string(yams::daemon::readiness::kDatabase)] = false; s.databasePhase = std::string(yams::daemon::dbphase::kCheckingIntegrity); s.databasePhaseElapsedMs = 90000; const std::string brief = renderBrief(s, {}); INFO(brief); CHECK(lineContaining(brief, "Vector Index").find("pending") != std::string::npos); CHECK(brief.find("by configuration") == std::string::npos); const std::string detailed = renderDetailed(s, {}); INFO(detailed); CHECK(lineContaining(detailed, "Vector DB").find("Pending") != std::string::npos); CHECK(detailed.find("by configuration") == std::string::npos); } TEST_CASE("daemon status says 'disabled by configuration' only when the daemon reports it", "[cli][daemon][status][catch2]") { PlainColors plain; StatusResponse s = readyDaemon(); s.vectorDbInitAttempted = false; s.vectorDbReady = false; s.readinessStates[std::string(yams::daemon::readiness::kVectorDbDisabled)] = true; const std::string brief = renderBrief(s, {}); INFO(brief); const auto line = lineContaining(brief, "Vector Index"); CHECK(line.find("disabled") != std::string::npos); CHECK(line.find("by configuration") != std::string::npos); CHECK(line.find("pending") == std::string::npos); const std::string detailed = renderDetailed(s, {}); INFO(detailed); CHECK(lineContaining(detailed, "Vector DB").find("by configuration") != std::string::npos); } TEST_CASE("lexical containment rejects incomparable roots", "[cli][daemon][status][catch2]") { using yams::common::isLexicallyContained; CHECK(isLexicallyContained("/tmp", "/tmp")); CHECK(isLexicallyContained("/tmp/child", "/tmp")); CHECK_FALSE(isLexicallyContained("relative", "/tmp")); CHECK_FALSE(isLexicallyContained("/tmp", "relative")); CHECK_FALSE(isLexicallyContained("/tmp-other", "/tmp")); CHECK_FALSE(isLexicallyContained("/tmp/../outside", "/tmp")); CHECK_FALSE(isLexicallyContained({}, "/tmp")); CHECK_FALSE(isLexicallyContained("/tmp", {})); #ifdef _WIN32 CHECK_FALSE(isLexicallyContained("D:/data", "C:/temp")); CHECK_FALSE(isLexicallyContained("C:relative", "C:/temp")); CHECK_FALSE(isLexicallyContained("C:/temp", "C:relative")); CHECK(isLexicallyContained("C:/temp/child", "C:/temp")); #endif } TEST_CASE("daemon status brief surfaces blockers, errors, and data-dir drift", "[cli][daemon][status][catch2]") { PlainColors plain; StatusResponse s = readyDaemon(); s.ready = false; s.lifecycleState = "initializing"; s.lastError = "vector index rebuild failed"; s.readinessStates["content_store"] = false; s.initProgress["content_store"] = 40; DaemonStatusRenderContext ctx; ctx.configuredDataDir = "/elsewhere/yams"; const std::string out = renderBrief(s, ctx); CHECK(out.find("Initializing") != std::string::npos); CHECK(out.find("Waiting on: Content Store (40%)") != std::string::npos); CHECK(out.find("Error: vector index rebuild failed") != std::string::npos); CHECK(out.find("Daemon data dir differs from current CLI/config") != std::string::npos); SECTION("an explicit --socket makes the data-dir difference expected") { ctx.explicitCustomSocket = true; const std::string quiet = renderBrief(s, ctx); CHECK(quiet.find("Daemon data dir differs") == std::string::npos); } } TEST_CASE("daemon status brief warns when the daemon serves a temporary data directory", "[cli][daemon][status][catch2]") { PlainColors plain; StatusResponse s = readyDaemon(); s.contentStoreRoot = (std::filesystem::temp_directory_path() / "yams-ephemeral" / "storage").string(); DaemonStatusRenderContext ctx; ctx.configuredDataDir = std::filesystem::temp_directory_path() / "yams-ephemeral"; const std::string out = renderBrief(s, ctx); CHECK(out.find("Daemon is serving a temporary data directory") != std::string::npos); CHECK(out.find("differs from current CLI/config") == std::string::npos); } TEST_CASE("daemon status names the active repair operation from the shared code table", "[cli][daemon][status][catch2]") { PlainColors plain; StatusResponse s = readyDaemon(); s.requestCounts[std::string(yams::daemon::metrics::kRepairCurrentOperationCode)] = yams::daemon::metrics::repairOperationCodeForName("block_refs"); s.requestCounts[std::string(yams::daemon::metrics::kRepairCurrentOperationElapsedMs)] = 65000; DaemonStatusRenderContext ctx; const std::string brief = renderBrief(s, ctx); CHECK(brief.find("Running · block refs") != std::string::npos); const std::string detailed = renderDetailed(s, ctx); CHECK(detailed.find("block refs") != std::string::npos); } TEST_CASE("daemon status detailed renders every section from a synthetic response", "[cli][daemon][status][catch2]") { PlainColors plain; StatusResponse s = readyDaemon(); StatusResponse::ProviderInfo provider; provider.name = "onnx"; provider.ready = true; provider.modelsLoaded = 2; provider.isProvider = true; s.providers.push_back(provider); StatusResponse::ModelInfo model; model.name = "all-MiniLM-L6-v2"; model.type = "onnx"; model.memoryMb = 128; model.requestCount = 4; s.models.push_back(model); StatusResponse::PluginSkipInfo skipped; skipped.path = "/plugins/legacy_plugin.so"; skipped.reason = "abi mismatch"; s.skippedPlugins.push_back(skipped); DaemonStatusRenderContext ctx; ctx.configuredDataDir = "/srv/yams"; const std::string out = renderDetailed(s, ctx); for (const char* header : {"YAMS Daemon — Detailed", "Resources", "Transport", "Search", "Post-Ingest Pipeline", "Internal Processing", "Repair Service", "Storage & Embeddings", "Providers", "Skipped Plugins", "Models"}) { INFO(header); CHECK(out.find(header) != std::string::npos); } CHECK(out.find("/plugins/legacy_plugin.so") != std::string::npos); CHECK(out.find("abi mismatch") != std::string::npos); CHECK(out.find("128 MB · 4 req") != std::string::npos); CHECK(out.find("2 models · active") != std::string::npos); CHECK(out.find("Components Not Ready") == std::string::npos); CHECK(out.find("Data Directory Warnings") == std::string::npos); } TEST_CASE("daemon status shows host pressure and deferred background work", "[cli][daemon][status][host_pressure][catch2]") { PlainColors plain; SECTION("busy host names what is being deferred") { StatusResponse s = readyDaemon(); s.hostPressureSource = "psi"; s.hostPressureElevated = true; s.hostCpuPressurePct = 72.5; s.hostIoPressurePct = 3.0; s.hostMemoryPressurePct = 0.0; s.hostLoadPerCpu = 1.9; s.deferredBackgroundWork = "topology,repair"; for (const auto& out : {renderBrief(s, {}), renderDetailed(s, {})}) { INFO(out); CHECK(out.find("Host Load") != std::string::npos); CHECK(out.find("Busy") != std::string::npos); CHECK(out.find("cpu 72%") != std::string::npos); CHECK(out.find("io 3%") != std::string::npos); CHECK(out.find("deferring topology, repair") != std::string::npos); } } SECTION("calm load-average host shows the per-CPU load") { StatusResponse s = readyDaemon(); s.hostPressureSource = "loadavg"; s.hostLoadPerCpu = 0.42; const std::string out = renderBrief(s, {}); INFO(out); CHECK(out.find("Host Load") != std::string::npos); CHECK(out.find("Calm") != std::string::npos); CHECK(out.find("load 0.42/cpu") != std::string::npos); CHECK(out.find("deferring") == std::string::npos); } SECTION("no host signal hides the row") { const std::string out = renderBrief(readyDaemon(), {}); CHECK(out.find("Host Load") == std::string::npos); } } TEST_CASE("memory sync section renders only when the loop has started", "[cli][daemon][status][catch2]") { PlainColors plain; SECTION("no response") { std::ostringstream os; yams::cli::renderMemorySyncSection(nullptr, os); CHECK(os.str().empty()); } MemorySyncResponse m; m.backend = "direct"; m.mode = "writer"; m.corpusId = "corpus-1"; m.corpusEpoch = 3; m.nodeId = "node-a"; m.trustMode = "authenticated-writers"; m.peerCount = 2; m.records = 10; m.quarantinedRecords = 1; m.successfulCycles = 5; m.failedCycles = 1; m.lastSuccessAgeMs = 1000; SECTION("not started") { m.started = false; std::ostringstream os; yams::cli::renderMemorySyncSection(&m, os); CHECK(os.str().empty()); } SECTION("started") { m.started = true; std::ostringstream os; yams::cli::renderMemorySyncSection(&m, os); const std::string out = os.str(); CHECK(out.find("Memory Sync") != std::string::npos); CHECK(out.find("corpus-1 (epoch 3)") != std::string::npos); CHECK(out.find("authenticated-writers") != std::string::npos); CHECK(out.find("yams p2p peers for details") != std::string::npos); CHECK(out.find("check daemon log for reasons") != std::string::npos); CHECK(out.find("5 ok · 1 failed") != std::string::npos); CHECK(out.find("0 ok · 0 with stage failures") != std::string::npos); CHECK(out.find("0 records") != std::string::npos); CHECK(out.find("Last apply error") == std::string::npos); CHECK(out.find("Publish") == std::string::npos); } SECTION("started with a stuck apply path") { m.started = true; m.applyCycles = 1268; m.applyFailedCycles = 1268; m.deferredTopology = 2; m.oldestDeferralAgeMs = 11 * 60 * 1000; m.applyFailuresTopology = 1268; m.lastApplyFailureStage = "topology"; m.lastApplyFailure = "edge identity mismatch"; m.publishSkippedCycles = 3; std::ostringstream os; yams::cli::renderMemorySyncSection(&m, os); const std::string out = os.str(); CHECK(out.find("0 ok · 1268 with stage failures") != std::string::npos); CHECK(out.find("2 records (oldest") != std::string::npos); CHECK(out.find("prerequisite not replicated") != std::string::npos); CHECK(out.find("topology: edge identity mismatch") != std::string::npos); CHECK(out.find("3 skipped · 0 failed") != std::string::npos); } SECTION("started with failing outbound sessions and a quarantined writer") { m.started = true; m.quarantinedWriters = 1; m.outboundSessions = 12; m.outboundFailures = 3; m.lastOutboundFailure = "node-b: connection refused"; m.lastOutboundFailureAgeMs = 90'000; std::ostringstream os; yams::cli::renderMemorySyncSection(&m, os); const std::string out = os.str(); CHECK(out.find("Quarantined writers") != std::string::npos); CHECK(out.find("durable; check daemon log for the reason") != std::string::npos); CHECK(out.find("Outbound sessions") != std::string::npos); CHECK(out.find("12 started · 3 failed") != std::string::npos); CHECK(out.find("Last outbound failure") != std::string::npos); CHECK(out.find("node-b: connection refused") != std::string::npos); CHECK(out.find("1m 30s ago") != std::string::npos); } SECTION("started with healthy outbound sessions") { m.started = true; m.outboundSessions = 4; std::ostringstream os; yams::cli::renderMemorySyncSection(&m, os); const std::string out = os.str(); CHECK(out.find("4 started · 0 failed") != std::string::npos); CHECK(out.find("Last outbound failure") == std::string::npos); } }