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 и др.
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
// Интеграционный тест: цепочка 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;
|
||||
}
|
||||
Reference in New Issue
Block a user