// Тест VaultIndex (Фаза 7): теги, [[ссылки]], md-ссылки, граф, списки файлов по тегу. #include #include #include #include #include #include "core/VaultIndex.h" static int fail(const char *msg, const QString &detail = QString()) { std::fprintf(stderr, "FAIL: %s | %s\n", msg, qPrintable(detail)); return 1; } int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); QTemporaryDir dir; if (!dir.isValid()) return fail("temp dir"); QDir(dir.path()).mkpath("sub"); auto put = [&](const QString &rel, const QString &body) { QFile f(dir.path() + "/" + rel); f.open(QIODevice::WriteOnly); f.write(body.toUtf8()); f.close(); }; put("a.md", "# Заголовок #тег1\n\nСсылка на [[b]] и [[b]].\nТег ещё #тег2 и #тег1.\n"); put("sub/b.md", "# B\n\nпростой текст #тег2\n"); put("c.md", "[Перейти на b](sub/b.md)\n\n```\n# не тег\nint x;\n```\n"); VaultIndex idx; idx.rebuild(dir.path()); if (idx.fileCount() != 3) return fail("fileCount", QString::number(idx.fileCount())); const QString a = dir.path() + "/a.md"; const QString b = dir.path() + "/sub/b.md"; const QString c = dir.path() + "/c.md"; // Теги файла a: тег1, тег2 (заголовок "# Заголовок" не тег) QStringList tagsA = idx.tagsOfFile(a); if (!tagsA.contains("тег1") || !tagsA.contains("тег2") || tagsA.size() != 2) return fail("tagsOfFile(a)", tagsA.join(",")); // Ссылки a → b (вики) auto linksA = idx.linksOfFile(a); if (linksA.isEmpty()) return fail("linksOfFile(a) пусто"); bool foundB = false; for (const auto &l : linksA) { if (l.toMap().value("path").toString() == b) foundB = true; } if (!foundB) return fail("вики-ссылка на b не разрешена"); // c → b по md-ссылке auto linksC = idx.linksOfFile(c); foundB = false; for (const auto &l : linksC) if (l.toMap().value("path").toString() == b) foundB = true; if (!foundB) return fail("md-ссылка на b не разрешена", QString()); // Граф: узлы-файлы и рёбра; тег как узел bool haveTagNode = false; for (const auto &n : idx.nodes()) { if (n.toMap().value("type").toString() == "tag") haveTagNode = true; } if (!haveTagNode) return fail("нет узла-тега в графе"); if (idx.edges().isEmpty()) return fail("нет рёбер в графе"); // filesWithTag QStringList files1 = idx.filesWithTag("тег1"); if (files1.size() != 1 || files1.first() != a) return fail("filesWithTag(тег1)", files1.join(",")); QStringList files2 = idx.filesWithTag("тег2"); if (files2.size() != 2) return fail("filesWithTag(тег2)", QString::number(files2.size())); // --- инкремент: updateFile меняет только один файл (убираем теги из a.md) --- put("a.md", "# Заголовок\n\nСсылка на [[b]].\n"); idx.updateFile(a); if (idx.fileCount() != 3) return fail("updateFile fileCount", QString::number(idx.fileCount())); // a.md теперь без тегов — тег1 остался только... нигде: filesWithTag пуст if (!idx.filesWithTag("тег1").isEmpty()) return fail("updateFile снял тег1", idx.filesWithTag("тег1").join(",")); if (idx.tagsOfFile(a).size() != 0) return fail("updateFile tagsOfFile", idx.tagsOfFile(a).join(",")); // b.md не трогали — его теги на месте if (!idx.tagsOfFile(b).contains("тег2")) return fail("updateFile не тронул b"); // --- инкремент: новый файл чинит висячую ссылку (relink) --- put("d.md", "Ссылка на [[future]].\n"); const QString d = dir.path() + "/d.md"; idx.updateFile(d); if (idx.fileCount() != 4) return fail("updateFile new fileCount", QString::number(idx.fileCount())); if (!idx.linksOfFile(d).isEmpty()) return fail("висячая ссылка должна быть пустой"); put("sub/future.md", "# Future\n"); const QString future = dir.path() + "/sub/future.md"; idx.updateFile(future); { bool foundFuture = false; for (const auto &l : idx.linksOfFile(d)) if (l.toMap().value("path").toString() == future) foundFuture = true; if (!foundFuture) return fail("relink не починил [[future]]"); } // --- инкремент: removeFile чистит теги/ссылки/узел --- // (в реальности вызывается потому что файл удалён — удаляем и с диска) QFile::remove(future); idx.removeFile(future); if (idx.fileCount() != 4) return fail("removeFile fileCount", QString::number(idx.fileCount())); { bool foundFuture = false; for (const auto &l : idx.linksOfFile(d)) if (l.toMap().value("path").toString() == future) foundFuture = true; if (foundFuture) return fail("removeFile не убрал входящую ссылку"); } // removeFile несуществующего — тихо, без падения idx.removeFile(dir.path() + "/нет-такого.md"); idx.updateFile(dir.path() + "/нет-такого.md"); // --- инкремент: syncIncremental догоняет внешние изменения --- put("ext.md", "Внешний #внешний\n"); const QString ext = dir.path() + "/ext.md"; if (idx.fileCount() != 4) return fail("до sync файл не виден", QString::number(idx.fileCount())); idx.syncIncremental(); if (idx.fileCount() != 5) return fail("sync подхватил файл", QString::number(idx.fileCount())); if (!idx.tagsOfFile(ext).contains("внешний")) return fail("sync теги"); QFile::remove(ext); idx.syncIncremental(); if (idx.fileCount() != 4) return fail("sync убрал файл", QString::number(idx.fileCount())); if (idx.tagsOfFile(ext).size() != 0) return fail("sync теги удалены"); std::printf("VAULTINDEX TEST OK\n"); return 0; }