* test(graph): cover document lookup and --scope-cwd listing (#280) Red tests for the two graph CLI bugs in #280: - GetResponse graph fields and file name must survive the proto wire. - DocumentService graph lookup by hash and by name must return the same documents as traversing the doc:<hash> node. - The document graph JSON must name the document when only name is set. - KG path-range scoping must match document nodes by label and snapshot path nodes by $.path. - listByType --scope-cwd must keep document and path nodes under the client cwd (including via a symlinked cwd) outside src/ and include/. The existing code-roots dispatcher case now places its out-of-scope file outside the scope root, since the scope is the whole cwd. Signed-off-by: trvon <me@trevon.dev> * fix(graph): fill document lookup and scope --scope-cwd to the cwd (#280) yams graph <hash> and --name returned graphEnabled=false with no related documents while --node-key doc:<hash> listed neighbours: - The GetResponse proto binding dropped graphEnabled, related and fileName, so socket clients never saw graph data. - DocumentService started traversal at blob:<hash>, which has no outgoing edges, and filtered out semantic_neighbor. Start at the doc:<hash> node and allow semantic_neighbor. - GraphQueryService did not hydrate documentHash for doc: nodes, so reached documents were skipped. - The presenter read only fileName for "document"; fall back to name. --list-type <t> --scope-cwd returned nothing: - The daemon scoped to <cwd>/src and <cwd>/include only. Scope to the whole cwd, lexical and symlink-resolved. - The KG range predicate ignored document labels and $.path on snapshot path nodes. Match both. Signed-off-by: trvon <me@trevon.dev> * docs(skills): correct graph document lookup examples --format dot is only rendered for node traversal, so show it with --node-key doc:<hash>. Describe --scope-cwd as cwd-scoped. Signed-off-by: trvon <me@trevon.dev> --------- Signed-off-by: trvon <me@trevon.dev>
114 lines
4.6 KiB
C++
114 lines
4.6 KiB
C++
// Copyright (c) 2025 YAMS Contributors
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
#include <nlohmann/json.hpp>
|
|
#include <catch2/catch_test_macros.hpp>
|
|
|
|
#include <yams/cli/graph_query_presenter.h>
|
|
|
|
#include <sstream>
|
|
|
|
TEST_CASE("Graph query presenter renders graph responses across formats", "[cli][graph]") {
|
|
yams::daemon::GraphQueryResponse response;
|
|
response.kgAvailable = true;
|
|
response.originNode.nodeId = 1;
|
|
response.originNode.nodeKey = "function:demo::caller@/repo/src/caller.cpp";
|
|
response.originNode.label = "/repo/src/caller.cpp";
|
|
response.originNode.type = "function";
|
|
response.totalNodesFound = 2;
|
|
response.totalEdgesTraversed = 1;
|
|
|
|
yams::daemon::GraphNode node;
|
|
node.nodeId = 2;
|
|
node.nodeKey = "function:demo::callee@/repo/src/callee.cpp";
|
|
node.label = "callee";
|
|
node.type = "function";
|
|
node.distance = 1;
|
|
node.documentHash = "abcdef1234567890";
|
|
node.documentPath = "/repo/src/callee.cpp";
|
|
node.properties = R"({"language":"cpp"})";
|
|
response.connectedNodes.push_back(node);
|
|
|
|
yams::daemon::GraphEdge edge;
|
|
edge.edgeId = 42;
|
|
edge.srcNodeId = 1;
|
|
edge.dstNodeId = 2;
|
|
edge.relation = "calls";
|
|
edge.weight = 1.0f;
|
|
response.edges.push_back(edge);
|
|
|
|
std::ostringstream jsonOut;
|
|
auto jsonResult = yams::cli::renderGraphQueryResponse(
|
|
jsonOut, response, yams::cli::GraphQueryRenderOptions{.jsonOutput = true, .cwd = "/repo"});
|
|
REQUIRE(jsonResult.has_value());
|
|
CHECK(jsonOut.str().find("\"connectedNodes\"") != std::string::npos);
|
|
CHECK(jsonOut.str().find("\"via\": \"calls(1)\"") != std::string::npos);
|
|
|
|
std::ostringstream tableOut;
|
|
auto tableResult = yams::cli::renderGraphQueryResponse(
|
|
tableOut, response, yams::cli::GraphQueryRenderOptions{.verbose = true, .cwd = "/repo"});
|
|
REQUIRE(tableResult.has_value());
|
|
CHECK(tableOut.str().find("Knowledge Graph Query") != std::string::npos);
|
|
CHECK(tableOut.str().find("src/callee.cpp") != std::string::npos);
|
|
CHECK(tableOut.str().find("Node Properties") != std::string::npos);
|
|
|
|
std::ostringstream dotOut;
|
|
auto dotResult = yams::cli::renderGraphQueryResponse(
|
|
dotOut, response,
|
|
yams::cli::GraphQueryRenderOptions{.outputFormat = "dot", .cwd = "/repo"});
|
|
REQUIRE(dotResult.has_value());
|
|
CHECK(dotOut.str().find("digraph G") != std::string::npos);
|
|
CHECK(dotOut.str().find("calls") != std::string::npos);
|
|
}
|
|
|
|
TEST_CASE("Graph query presenter renders document graph fallbacks and related docs",
|
|
"[cli][graph]") {
|
|
yams::daemon::GetResponse disabled;
|
|
disabled.fileName = "src/new_file.cpp";
|
|
disabled.path = "/repo/src/new_file.cpp";
|
|
disabled.hash = "1234567890abcdef";
|
|
disabled.graphEnabled = false;
|
|
|
|
std::ostringstream disabledOut;
|
|
auto disabledResult = yams::cli::renderDocumentGraphResponse(
|
|
disabledOut, disabled, yams::cli::DocumentGraphRenderOptions{.depth = 2, .cwd = "/repo"});
|
|
REQUIRE(disabledResult.has_value());
|
|
CHECK(disabledOut.str().find("yams add \"src/new_file.cpp\" --sync") != std::string::npos);
|
|
CHECK(disabledOut.str().find("yams graph --search \"*new_file*\"") != std::string::npos);
|
|
|
|
yams::daemon::GetResponse related;
|
|
related.fileName = "src/main.cpp";
|
|
related.path = "/repo/src/main.cpp";
|
|
related.hash = "fedcba0987654321";
|
|
related.graphEnabled = true;
|
|
yams::daemon::RelatedDocumentEntry rel;
|
|
rel.hash = "abcd1234efgh5678";
|
|
rel.name = "src/helper.cpp";
|
|
rel.relationship = "calls";
|
|
rel.distance = 1;
|
|
related.related.push_back(rel);
|
|
|
|
std::ostringstream relatedOut;
|
|
auto relatedResult = yams::cli::renderDocumentGraphResponse(
|
|
relatedOut, related, yams::cli::DocumentGraphRenderOptions{.depth = 2, .cwd = "/repo"});
|
|
REQUIRE(relatedResult.has_value());
|
|
CHECK(relatedOut.str().find("Related Documents (depth 2)") != std::string::npos);
|
|
CHECK(relatedOut.str().find("src/helper.cpp") != std::string::npos);
|
|
}
|
|
|
|
TEST_CASE("Document graph JSON names the document when only name is set", "[cli][graph]") {
|
|
// Issue #280: the daemon fills GetResponse.name, not fileName, so "document" came back "".
|
|
yams::daemon::GetResponse resp;
|
|
resp.name = "bm25.hpp";
|
|
resp.path = "/corpus/simeon/include/simeon/bm25.hpp";
|
|
resp.hash = "12be4dc5f2d4a975";
|
|
resp.graphEnabled = true;
|
|
|
|
std::ostringstream out;
|
|
auto result = yams::cli::renderDocumentGraphResponse(
|
|
out, resp, yams::cli::DocumentGraphRenderOptions{.jsonOutput = true, .cwd = "/corpus"});
|
|
REQUIRE(result.has_value());
|
|
const auto payload = nlohmann::json::parse(out.str());
|
|
CHECK(payload.at("document") == "bm25.hpp");
|
|
CHECK(payload.at("graphEnabled") == true);
|
|
}
|