tests: add snapshot round-trip test using Orchestrator::saveSnapshot/loadSnapshot; Orchestrator reuses a DAL handle so stub mode persists within instance

This commit is contained in:
Χγφτ Kompanion 2025-10-19 10:49:12 +02:00
parent c36ef7e46b
commit d5226fe7d6
4 changed files with 54 additions and 12 deletions

View File

@ -71,6 +71,19 @@ Orchestrator::Orchestrator(QObject *parent)
connect(&timer_, &QTimer::timeout, this, &Orchestrator::processPendingTasks); connect(&timer_, &QTimer::timeout, this, &Orchestrator::processPendingTasks);
} }
Orchestrator::~Orchestrator() {
delete dal_;
}
ki::PgDal& Orchestrator::dal() {
if (!dal_) {
dal_ = new ki::PgDal();
const QByteArray dsn = qgetenv("PG_DSN");
if (!dsn.isEmpty()) dal_->connect(dsn.toStdString()); else dal_->connect("stub://memory");
}
return *dal_;
}
void Orchestrator::start(int intervalMs) { void Orchestrator::start(int intervalMs) {
ensureResolvedDirs(); ensureResolvedDirs();
continuityHandshakeOnce(); continuityHandshakeOnce();
@ -178,11 +191,7 @@ bool Orchestrator::saveSnapshot(const QString &nameSpace,
const QJsonObject &content, const QJsonObject &content,
const QStringList &tags) const QStringList &tags)
{ {
ki::PgDal dal; auto nsRow = dal().ensureNamespace(nameSpace.toStdString());
const QByteArray dsn = qgetenv("PG_DSN");
if (!dsn.isEmpty()) dal.connect(dsn.toStdString()); else dal.connect("stub://memory");
auto nsRow = dal.ensureNamespace(nameSpace.toStdString());
if (!nsRow) return false; if (!nsRow) return false;
ki::ItemRow row; ki::ItemRow row;
@ -194,22 +203,18 @@ bool Orchestrator::saveSnapshot(const QString &nameSpace,
row.metadata_json = "{}"; row.metadata_json = "{}";
row.created_at = std::chrono::system_clock::now(); row.created_at = std::chrono::system_clock::now();
const std::string id = dal.upsertItem(row); const std::string id = dal().upsertItem(row);
return !id.empty(); return !id.empty();
} }
std::optional<QJsonObject> Orchestrator::loadSnapshot(const QString &nameSpace, std::optional<QJsonObject> Orchestrator::loadSnapshot(const QString &nameSpace,
const QString &key) const QString &key)
{ {
ki::PgDal dal; auto nsRow = dal().findNamespace(nameSpace.toStdString());
const QByteArray dsn = qgetenv("PG_DSN");
if (!dsn.isEmpty()) dal.connect(dsn.toStdString()); else dal.connect("stub://memory");
auto nsRow = dal.findNamespace(nameSpace.toStdString());
if (!nsRow) return std::nullopt; if (!nsRow) return std::nullopt;
std::vector<std::string> tags; tags.emplace_back("snapshot"); std::vector<std::string> tags; tags.emplace_back("snapshot");
auto rows = dal.fetchContext(nsRow->id, std::optional<std::string>(key.toStdString()), tags, std::nullopt, 1); auto rows = dal().fetchContext(nsRow->id, std::optional<std::string>(key.toStdString()), tags, std::nullopt, 1);
if (rows.empty()) return std::nullopt; if (rows.empty()) return std::nullopt;
const auto &row = rows.front(); const auto &row = rows.front();
if (row.content_json.empty()) return std::nullopt; if (row.content_json.empty()) return std::nullopt;

View File

@ -8,6 +8,8 @@
#include "kompanion_mw_export.h" #include "kompanion_mw_export.h"
namespace ki { class PgDal; }
// Minimal model provider interface so tests can stub generation. // Minimal model provider interface so tests can stub generation.
class IModelProvider { class IModelProvider {
public: public:
@ -49,6 +51,7 @@ class KOMPANION_MW_EXPORT Orchestrator : public QObject {
Q_OBJECT Q_OBJECT
public: public:
explicit Orchestrator(QObject *parent=nullptr); explicit Orchestrator(QObject *parent=nullptr);
~Orchestrator();
// Injectable model provider (Ollama by default). Ownership left to caller. // Injectable model provider (Ollama by default). Ownership left to caller.
void setModelProvider(IModelProvider *prov) { model_ = prov; } void setModelProvider(IModelProvider *prov) { model_ = prov; }
@ -104,4 +107,7 @@ private:
QTimer timer_; QTimer timer_;
bool continuityDone_ = false; bool continuityDone_ = false;
IModelProvider *model_ = nullptr; // not owned IModelProvider *model_ = nullptr; // not owned
// Reused DB handle so in-memory stub persists across calls in tests.
ki::PgDal* dal_ = nullptr;
ki::PgDal& dal();
}; };

View File

@ -11,3 +11,9 @@ qt_add_executable(test_orchestrator
) )
target_link_libraries(test_orchestrator PRIVATE Qt6::Core Qt6::Network Qt6::Test kompanion_mw) target_link_libraries(test_orchestrator PRIVATE Qt6::Core Qt6::Network Qt6::Test kompanion_mw)
add_test(NAME test_orchestrator COMMAND test_orchestrator) add_test(NAME test_orchestrator COMMAND test_orchestrator)
qt_add_executable(test_snapshot
test_snapshot.cpp
)
target_link_libraries(test_snapshot PRIVATE Qt6::Core Qt6::Network Qt6::Test kompanion_mw)
add_test(NAME test_snapshot COMMAND test_snapshot)

25
tests/test_snapshot.cpp Normal file
View File

@ -0,0 +1,25 @@
#include <QtTest>
#include "../src/middleware/orchestrator.h"
class SnapshotTest : public QObject {
Q_OBJECT
private slots:
void round_trip() {
// Ensure no PG_DSN is required; Orchestrator maintains a shared in-memory DAL per instance
Orchestrator orch;
const QString ns = QStringLiteral("tests");
const QString key = QStringLiteral("session:last");
QJsonObject payload{{"a", 1}, {"b", QStringLiteral("x")}};
QVERIFY2(orch.saveSnapshot(ns, key, payload), "saveSnapshot should succeed");
auto loaded = orch.loadSnapshot(ns, key);
QVERIFY2(loaded.has_value(), "loadSnapshot should return value");
QCOMPARE(loaded->value("a").toInt(), 1);
QCOMPARE(loaded->value("b").toString(), QStringLiteral("x"));
}
};
QTEST_MAIN(SnapshotTest)
#include "test_snapshot.moc"