Files
Flora/tests/integration_test.cpp
T
grendervill 95c03040c7 Prod: полный аудит и стабилизация — исправлены критические баги, расширено покрытие тестами, подготовлен бандл
- FileStore: защита от выхода за пределы хранилища (isInsideVault), валидация имён (запрет скрытых, зарезервированных, управляющих символов), проверка destDir, лимит watcher, dailyNote валидация, openVault проверка isDir
- DocumentManager: лимит 10МБ, кламп автосейва 0.5-120с, защита от OOM, бинарный детект, проверка путей
- VaultIndex: исправлен парсинг тегов (заголовки, инлайн-код), case-insensitive поиск, дедупликация рёбер, защита от traversal, лимит глубины, проверка vaultExists
- MdParser: блокировка всех on* событий, javascript:/data:, esc кавычек, MIME fallback, normalizeLang расширен (c/cpp/py/js и кириллица), wiki глобальный поиск, image лимит 3МБ, защита clipboard
- GitManager: commitAll с GIT_INDEX_ADD_DEFAULT и поддержкой пустых коммитов/удалений, fileGitStatus с cleanPath и проверкой префикса, repoExists/hasCommits с cleanPath, initRepo валидация
- SettingsStore: encodeKey/encodeRel для QSettings Ini, flat хранение viewMode, sync после записи, кламп ширин панелей, миграция старых ключей
- SyncManager: счётчик busy, обработка httpStatus при network error, валидация токена/провайдера/URL, GitHub Enterprise URL, таймаут
- VaultManager: валидация путей (корень, файл vs папка, права), проверка вложенности
- WindowController: per-window FileStore/DocumentManager/VaultIndex, cleanPath, закрытие окна через QQuickWindow::closing, обработка ошибок компонента
- QML: TableEditor пустая строка, WelcomeWizard QUrl парсинг, FileTree фильтр, GraphView лимит 600 узлов, RightPanel debounce rebuild, Theme singleton, MonthCalendar и др.
- Тесты: добавлены flora_settings_test, flora_sync_test, flora_extended_test, flora_integration_test — покрытие всех модулей, 20/20 зелёных
- Сборка: обновлён Flora.app (4.6М бинарь -> 173М бандл, ad-hoc подпись, проверен offscreen запуск, версия 0.0.1 + cmark 0.29 + libgit2 1.9)
- .gitignore: добавлены Testing/, coverage, gcda/gcov, __pycache__, .tmp, test-vault и др.
2026-09-01 19:25:07 +03:00

137 lines
5.6 KiB
C++

// Интеграционный тест: цепочка VaultManager + FileStore + DocumentManager + VaultIndex + GitManager + MdParser
#include <QCoreApplication>
#include <QTemporaryDir>
#include <QDir>
#include <QFile>
#include <QSettings>
#include <cstdio>
#include "app/VaultManager.h"
#include "core/SettingsStore.h"
#include "core/FileStore.h"
#include "core/DocumentManager.h"
#include "core/VaultIndex.h"
#include "git/GitManager.h"
#include "markdown/MdParser.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);
app.setOrganizationName("FloraInteg");
app.setApplicationName("integ_test");
QTemporaryDir tmp;
if(!tmp.isValid()) return fail("tmp");
QSettings::setPath(QSettings::IniFormat, QSettings::UserScope, tmp.path());
QSettings::setDefaultFormat(QSettings::IniFormat);
SettingsStore settings;
GitManager git;
VaultManager vaultMgr(&settings, &git);
QString vaultPath = tmp.path()+"/myvault";
QString err = vaultMgr.createVault(vaultPath);
if(!err.isEmpty()) return fail("createVault",err);
if(!settings.hasVault(vaultPath)) return fail("hasVault");
if(!git.hasCommits(vaultPath) && false) {} // may be no commits yet
// FileStore operations
FileStore fs;
err = fs.openVault(vaultPath);
if(!err.isEmpty()) return fail("openVault",err);
err = fs.createFile(vaultPath, "note1");
if(!err.isEmpty()) return fail("createFile note1",err);
err = fs.createFolder(vaultPath, "folder1");
if(!err.isEmpty()) return fail("createFolder",err);
QString note1 = vaultPath+"/note1.md";
QString folder1 = vaultPath+"/folder1";
// create inside folder
err = fs.createFile(folder1, "inner.md");
if(!err.isEmpty()) return fail("inner",err);
QString inner = folder1+"/inner.md";
// DocumentManager: open and edit
DocumentManager dm;
dm.setAutosaveIntervalMs(500);
int idx = dm.openDocument(note1);
if(idx!=0) return fail("open note1");
dm.updateContent(idx, "# Header #tagA\n\nLink to [[inner]] and #tagB\n");
dm.saveDocument(idx);
if(dm.isDirty(idx)) return fail("still dirty");
int idx2 = dm.openDocument(inner);
if(idx2!=1) return fail("open inner");
dm.updateContent(idx2, "# Inner\n\nBack to [[note1]] #tagA\n");
dm.saveDocument(idx2);
// Git commit all
if(!git.commitAll(vaultPath, "initial content")) return fail("commitAll", git.lastError());
if(!git.hasCommits(vaultPath)) return fail("hasCommits after");
// VaultIndex should find tags and links
VaultIndex vi;
vi.rebuild(vaultPath);
if(vi.fileCount()!=2) return fail("fileCount 2", QString::number(vi.fileCount()));
QStringList tags = vi.tagsOfFile(note1);
if(!tags.contains("tagA") || !tags.contains("tagB")) return fail("tags note1", tags.join(","));
auto links = vi.linksOfFile(note1);
bool foundInner=false;
for(auto l: links) if(l.toMap().value("path").toString()==inner) foundInner=true;
if(!foundInner) return fail("link note1->inner");
if(vi.filesWithTag("tagA").size()!=2) return fail("tagA files 2");
// MdParser render
MdParser parser;
QString content = dm.contentAt(idx);
QString html = parser.render(content, vaultPath, QFileInfo(note1).absolutePath());
if(!html.contains("tagA") && !html.contains("tag")) {} // tags are not rendered as html, but links should be
if(!html.contains("flora://open")) return fail("md render link", html);
if(html.contains("onload")) return fail("xss");
// Daily note
QString dn = fs.dailyNotePath("2026-09-01");
if(dn.isEmpty()) return fail("dailyNote");
if(!QFileInfo::exists(dn)) return fail("dailyNote not exists");
int dnIdx = dm.openDocument(dn);
if(dnIdx<0) return fail("open daily");
dm.updateContent(dnIdx, "# 2026-09-01\n\nDaily #daily\n");
dm.saveDocument(dnIdx);
vi.rebuild(vaultPath);
if(vi.fileCount()!=3) return fail("fileCount 3 after daily");
if(!vi.tagsOfFile(dn).contains("daily")) return fail("daily tag");
// Rename and move
err = fs.renameItem(note1, "renamed.md");
if(!err.isEmpty()) return fail("rename",err);
QString renamed = vaultPath+"/renamed.md";
if(!QFileInfo::exists(renamed)) return fail("renamed not exists");
// DocumentManager path still old, but file moved - should handle? reopen
dm.closeTab(idx); // close old (should be at index? after previous ops indices shifted - find renamed)
// open renamed
int idxR = dm.openDocument(renamed);
if(idxR<0) return fail("open renamed");
// Move inner to root
err = fs.moveItem(inner, vaultPath);
if(!err.isEmpty()) return fail("move inner to root",err);
if(!QFileInfo::exists(vaultPath+"/inner.md")) return fail("moved inner not at root");
// Remove folder (now empty)
err = fs.removeItem(folder1);
if(!err.isEmpty()) return fail("remove folder",err);
if(QDir(folder1).exists()) return fail("folder still exists");
// Git status checks
QString st = git.fileGitStatus(vaultPath, renamed);
if(st.isEmpty()) return fail("git status empty");
// commit again
if(!git.commitAll(vaultPath, "after moves")) return fail("commit after moves", git.lastError());
// SettingsStore viewMode persistence across vaults
settings.setViewModeForFile(vaultPath, renamed, "preview");
if(settings.viewModeForFile(vaultPath, renamed)!="preview") return fail("viewMode", settings.viewModeForFile(vaultPath, renamed));
std::printf("INTEGRATION TEST OK\n");
return 0;
}