// SPDX-License-Identifier: GPL-3.0-or-later #include #include #include #include #include #include #include using Catch::Matchers::ContainsSubstring; using namespace yams::metadata; namespace { std::filesystem::path tempDbPath(const char* prefix) { const char* t = std::getenv("YAMS_TEST_TMPDIR"); auto base = (t && *t) ? std::filesystem::path(t) : std::filesystem::temp_directory_path(); std::error_code ec; std::filesystem::create_directories(base, ec); auto ts = std::chrono::steady_clock::now().time_since_epoch().count(); auto p = base / (std::string(prefix) + std::to_string(ts) + ".db"); std::filesystem::remove(p, ec); return p; } struct KGRelationSummaryFixture { KGRelationSummaryFixture() { dbPath = tempDbPath("kg_relation_summary_"); KnowledgeGraphStoreConfig cfg; cfg.enable_wal = false; auto res = makeSqliteKnowledgeGraphStore(dbPath.string(), cfg); REQUIRE(res.has_value()); store = std::move(res.value()); REQUIRE(store != nullptr); } ~KGRelationSummaryFixture() { store.reset(); std::error_code ec; std::filesystem::remove(dbPath, ec); } std::filesystem::path dbPath; std::unique_ptr store; }; } // namespace TEST_CASE("KGRelationSummary normalizes relation names", "[unit][metadata][kg][relations]") { CHECK(normalizeRelationName(" HAS-VERSION ") == "has_version"); CHECK(normalizeRelationName("calls") == "calls"); CHECK(normalizeRelationName("call") == "calls"); CHECK(normalizeRelationName("include") == "includes"); CHECK(normalizeRelationName("inherit") == "inherits"); CHECK(normalizeRelationName("implement") == "implements"); CHECK(normalizeRelationName("reference") == "references"); CHECK(normalizeRelationName("rename_to") == "renamed_to"); CHECK(normalizeRelationName("rename-from") == "renamed_from"); CHECK(normalizeRelationName("move_to") == "moved_to"); CHECK(normalizeRelationName("move-from") == "moved_from"); CHECK(normalizeRelationName("version") == "has_version"); CHECK(normalizeRelationName("blob_version") == "has_version"); CHECK(normalizeRelationName(" ") == ""); } TEST_CASE("KGRelationSummary collects file and hash relations", "[unit][metadata][kg][relations]") { KGRelationSummaryFixture fix; KGNode fileNode; fileNode.nodeKey = "path:file:/repo/main.cpp"; fileNode.type = std::string("file"); fileNode.label = std::string("main.cpp"); auto fileId = fix.store->upsertNode(fileNode); REQUIRE(fileId.has_value()); KGNode docNode; docNode.nodeKey = "doc:abc123"; docNode.type = std::string("document"); docNode.label = std::string("main.cpp"); auto docId = fix.store->upsertNode(docNode); REQUIRE(docId.has_value()); KGNode symbolOne; symbolOne.nodeKey = "symbol:one"; symbolOne.type = std::string("symbol"); auto symOneId = fix.store->upsertNode(symbolOne); REQUIRE(symOneId.has_value()); KGNode symbolTwo; symbolTwo.nodeKey = "symbol:two"; symbolTwo.type = std::string("symbol"); auto symTwoId = fix.store->upsertNode(symbolTwo); REQUIRE(symTwoId.has_value()); KGEdge versionEdge; versionEdge.srcNodeId = fileId.value(); versionEdge.dstNodeId = docId.value(); versionEdge.relation = "has-version"; REQUIRE(fix.store->addEdge(versionEdge).has_value()); KGEdge callsOne; callsOne.srcNodeId = docId.value(); callsOne.dstNodeId = symOneId.value(); callsOne.relation = "calls"; REQUIRE(fix.store->addEdge(callsOne).has_value()); KGEdge callsTwo; callsTwo.srcNodeId = docId.value(); callsTwo.dstNodeId = symTwoId.value(); callsTwo.relation = "calls"; REQUIRE(fix.store->addEdge(callsTwo).has_value()); auto summary = collectFileRelationSummary(fix.store.get(), "/repo/main.cpp", "abc123"); REQUIRE(summary.has_value()); CHECK(summary->totalEdges == 3); REQUIRE_FALSE(summary->topRelations.empty()); CHECK(summary->topRelations.front().first == "calls"); CHECK(summary->topRelations.front().second == 2); auto machineSummary = formatRelationSummary(summary->topRelations, false); CHECK_THAT(machineSummary, ContainsSubstring("calls:2")); auto humanSummary = formatRelationSummary(summary->topRelations, true); CHECK_THAT(humanSummary, ContainsSubstring("calls(2)")); } TEST_CASE("KGRelationSummary resolves legacy file key input", "[unit][metadata][kg][relations]") { KGRelationSummaryFixture fix; KGNode legacyFileNode; legacyFileNode.nodeKey = "file:/repo/legacy.cpp"; legacyFileNode.type = std::string("file"); auto legacyId = fix.store->upsertNode(legacyFileNode); REQUIRE(legacyId.has_value()); KGNode dst; dst.nodeKey = "symbol:legacy"; dst.type = std::string("symbol"); auto dstId = fix.store->upsertNode(dst); REQUIRE(dstId.has_value()); KGEdge edge; edge.srcNodeId = legacyId.value(); edge.dstNodeId = dstId.value(); edge.relation = "includes"; REQUIRE(fix.store->addEdge(edge).has_value()); auto summary = collectFileRelationSummary(fix.store.get(), "file:/repo/legacy.cpp"); REQUIRE(summary.has_value()); REQUIRE_FALSE(summary->topRelations.empty()); CHECK(summary->topRelations.front().first == "includes"); }