Files
logos-cpp-sdk/tests/sdk/test_event_system.cpp

341 lines
11 KiB
C++

#include <gtest/gtest.h>
#include <QCoreApplication>
#include <QtTest/QSignalSpy>
#include "logos_mock.h"
#include "logos_api.h"
#include "logos_api_client.h"
#include "logos_api_provider.h"
#include "logos_api_consumer.h"
#include "logos_object.h"
#include "logos_provider_object.h"
#include "module_proxy.h"
#include "plugin_registry.h"
#include "local_transport.h"
// Provider that emits events on demand
class EventTestProvider : public LogosProviderBase {
public:
QString providerName() const override { return "event_test"; }
QString providerVersion() const override { return "1.0.0"; }
QVariant callMethod(const QString& methodName, const QVariantList& args) override
{
Q_UNUSED(methodName) Q_UNUSED(args)
return QVariant(true);
}
QJsonArray getMethods() override { return QJsonArray(); }
void testEmitEvent(const QString& name, const QVariantList& data) { emitEvent(name, data); }
};
// ── LogosAPIClient::onEventResponse(LogosObject*) ─────────────────────────
class ClientEventResponseTest : public ::testing::Test {
protected:
void SetUp() override
{
m_mock = new LogosMockSetup();
m_api = new LogosAPI("origin");
m_client = m_api->getClient("target");
}
void TearDown() override
{
delete m_api;
delete m_mock;
}
LogosMockSetup* m_mock = nullptr;
LogosAPI* m_api = nullptr;
LogosAPIClient* m_client = nullptr;
};
TEST_F(ClientEventResponseTest, OnEventResponseNullObjectIsNoOp)
{
// Should not crash
m_client->onEventResponse(static_cast<LogosObject*>(nullptr), "evt", {QVariant(1)});
}
TEST_F(ClientEventResponseTest, OnEventResponseEmptyEventNameIsNoOp)
{
// Get a mock object to test with
m_mock->when("target", "fn").thenReturn(QVariant(1));
LogosObject* obj = m_client->requestObject("target");
ASSERT_NE(obj, nullptr);
// Should not crash, empty event name is rejected
m_client->onEventResponse(obj, "", {QVariant(1)});
obj->release();
}
TEST_F(ClientEventResponseTest, OnEventResponseCallsEmitEventOnObject)
{
// MockLogosObject::emitEvent is a no-op, so we just verify no crash
m_mock->when("target", "fn").thenReturn(QVariant(1));
LogosObject* obj = m_client->requestObject("target");
ASSERT_NE(obj, nullptr);
m_client->onEventResponse(obj, "my_event", {QVariant("data")});
obj->release();
}
// ── LogosAPIClient::onEventResponse(QObject*) backward compat ──────────────
class LegacyQObjectEventTest : public QObject {
Q_OBJECT
public:
QString lastEventName;
QVariantList lastEventData;
public slots:
void eventResponse(const QString& eventName, const QVariantList& data)
{
lastEventName = eventName;
lastEventData = data;
}
};
TEST_F(ClientEventResponseTest, OnEventResponseQObjectCallsSlot)
{
LegacyQObjectEventTest receiver;
m_client->onEventResponse(&receiver, "test_event", {QVariant(42)});
EXPECT_EQ(receiver.lastEventName, "test_event");
ASSERT_EQ(receiver.lastEventData.size(), 1);
EXPECT_EQ(receiver.lastEventData[0].toInt(), 42);
}
TEST_F(ClientEventResponseTest, OnEventResponseQObjectNullIsNoOp)
{
m_client->onEventResponse(static_cast<QObject*>(nullptr), "evt", {QVariant(1)});
}
TEST_F(ClientEventResponseTest, OnEventResponseQObjectEmptyNameIsNoOp)
{
LegacyQObjectEventTest receiver;
m_client->onEventResponse(&receiver, "", {QVariant(1)});
EXPECT_TRUE(receiver.lastEventName.isEmpty());
}
// ── LogosAPIClient::onEvent (LogosObject callback registration) ────────────
TEST_F(ClientEventResponseTest, OnEventNullObjectIsNoOp)
{
// Should not crash
m_client->onEvent(nullptr, "evt", [](const QString&, const QVariantList&) {});
}
// ── LogosAPIClient::informModuleToken ──────────────────────────────────────
TEST_F(ClientEventResponseTest, InformModuleTokenDelegatesToConsumer)
{
m_mock->when("capability_module", "fn").thenReturn(QVariant(true));
// informModuleToken routes through consumer -> capability_module -> informModuleToken
bool result = m_client->informModuleToken("auth_tok", "target_mod", "secret");
// MockLogosObject::informModuleToken always returns true
EXPECT_TRUE(result);
}
// ── LogosAPIClient::informModuleToken_module ───────────────────────────────
TEST_F(ClientEventResponseTest, InformModuleTokenModuleDelegatesToConsumer)
{
m_mock->when("origin_mod", "fn").thenReturn(QVariant(true));
bool result = m_client->informModuleToken_module("auth_tok", "origin_mod", "target_mod", "secret");
EXPECT_TRUE(result);
}
// ── LogosAPIProvider::onEventResponse ──────────────────────────────────────
class ProviderEventResponseTest : public ::testing::Test {
protected:
void SetUp() override
{
m_mock = new LogosMockSetup();
}
void TearDown() override
{
delete m_mock;
}
LogosMockSetup* m_mock = nullptr;
};
TEST_F(ProviderEventResponseTest, OnEventResponseNullObjectIsNoOp)
{
LogosAPI api("mod");
LogosAPIProvider* prov = api.getProvider();
// Should not crash
prov->onEventResponse(nullptr, "evt", {QVariant(1)});
}
TEST_F(ProviderEventResponseTest, OnEventResponseEmptyNameIsNoOp)
{
LogosAPI api("mod");
LogosAPIProvider* prov = api.getProvider();
m_mock->when("mod", "fn").thenReturn(QVariant(1));
LogosAPIClient* client = api.getClient("mod");
LogosObject* obj = client->requestObject("mod");
ASSERT_NE(obj, nullptr);
// Empty event name should be rejected
prov->onEventResponse(obj, "", {QVariant(1)});
obj->release();
}
TEST_F(ProviderEventResponseTest, OnEventResponseCallsEmitEvent)
{
LogosAPI api("mod");
LogosAPIProvider* prov = api.getProvider();
m_mock->when("mod", "fn").thenReturn(QVariant(1));
LogosAPIClient* client = api.getClient("mod");
LogosObject* obj = client->requestObject("mod");
ASSERT_NE(obj, nullptr);
// MockLogosObject::emitEvent is no-op, just verify no crash
prov->onEventResponse(obj, "test_event", {QVariant("data")});
obj->release();
}
// ── Full local event pipeline: Provider -> ModuleProxy -> LocalLogosObject -> Client ──
class LocalEventPipelineTest : public ::testing::Test {
protected:
void SetUp() override
{
m_savedMode = LogosModeConfig::getMode();
LogosModeConfig::setMode(LogosMode::Local);
TokenManager::instance().clearAllTokens();
m_provider = new EventTestProvider();
m_proxy = new ModuleProxy(m_provider);
}
void TearDown() override
{
PluginRegistry::unregisterPlugin("pipeline_mod");
delete m_proxy;
delete m_provider;
LogosModeConfig::setMode(m_savedMode);
}
LogosMode m_savedMode;
EventTestProvider* m_provider = nullptr;
ModuleProxy* m_proxy = nullptr;
};
TEST_F(LocalEventPipelineTest, FullEventPipelineProviderToConsumerObject)
{
LocalTransportHost host;
host.publishObject("pipeline_mod", m_proxy);
LocalTransportConnection conn;
LogosObject* obj = conn.requestObject("pipeline_mod", 5000);
ASSERT_NE(obj, nullptr);
// Register event listener on the LogosObject
QString receivedEvent;
QVariantList receivedData;
obj->onEvent("pipeline_evt", [&](const QString& name, const QVariantList& data) {
receivedEvent = name;
receivedData = data;
});
// Provider emits -> LogosProviderBase.emitEvent -> callback -> ModuleProxy.emit eventResponse
// -> EventHelper.onEventResponse -> registered callback
m_provider->testEmitEvent("pipeline_evt", {QVariant("payload"), QVariant(99)});
EXPECT_EQ(receivedEvent, "pipeline_evt");
ASSERT_EQ(receivedData.size(), 2);
EXPECT_EQ(receivedData[0].toString(), "payload");
EXPECT_EQ(receivedData[1].toInt(), 99);
obj->release();
}
TEST_F(LocalEventPipelineTest, EventNotDeliveredAfterDisconnect)
{
LocalTransportHost host;
host.publishObject("pipeline_mod", m_proxy);
LocalTransportConnection conn;
LogosObject* obj = conn.requestObject("pipeline_mod", 5000);
ASSERT_NE(obj, nullptr);
int count = 0;
obj->onEvent("evt", [&](const QString&, const QVariantList&) { count++; });
m_provider->testEmitEvent("evt", {});
EXPECT_EQ(count, 1);
obj->disconnectEvents();
m_provider->testEmitEvent("evt", {});
EXPECT_EQ(count, 1);
// Re-register after disconnect
obj->onEvent("evt", [&](const QString&, const QVariantList&) { count++; });
m_provider->testEmitEvent("evt", {});
EXPECT_EQ(count, 2);
obj->release();
}
TEST_F(LocalEventPipelineTest, EmitEventOnLocalLogosObjectTriggersSignal)
{
LocalTransportHost host;
host.publishObject("pipeline_mod", m_proxy);
LocalTransportConnection conn;
LogosObject* obj = conn.requestObject("pipeline_mod", 5000);
ASSERT_NE(obj, nullptr);
// Use QSignalSpy on the ModuleProxy to verify emitEvent triggers the signal
QSignalSpy spy(m_proxy, &ModuleProxy::eventResponse);
obj->emitEvent("ext_event", {QVariant("from_outside")});
// emitEvent uses QueuedConnection, so process events
QCoreApplication::processEvents();
EXPECT_EQ(spy.count(), 1);
EXPECT_EQ(spy.at(0).at(0).toString(), "ext_event");
QVariantList data = spy.at(0).at(1).value<QVariantList>();
ASSERT_EQ(data.size(), 1);
EXPECT_EQ(data[0].toString(), "from_outside");
obj->release();
}
// ── Consumer onEvent integration ───────────────────────────────────────────
TEST_F(LocalEventPipelineTest, ConsumerOnEventRegistersCallback)
{
LocalTransportHost host;
host.publishObject("pipeline_mod", m_proxy);
// Seed a token so client doesn't try capability_module
TokenManager::instance().saveToken("pipeline_mod", "tok");
LogosAPI api("test_origin");
LogosAPIClient* client = api.getClient("pipeline_mod");
// Get object handle
LogosObject* obj = client->requestObject("pipeline_mod");
ASSERT_NE(obj, nullptr);
// Register via client.onEvent (delegates to consumer.onEvent -> obj.onEvent)
QString receivedName;
QVariantList receivedData;
client->onEvent(obj, "client_evt", [&](const QString& name, const QVariantList& data) {
receivedName = name;
receivedData = data;
});
// Provider emits event
m_provider->testEmitEvent("client_evt", {QVariant("via_client")});
EXPECT_EQ(receivedName, "client_evt");
ASSERT_EQ(receivedData.size(), 1);
EXPECT_EQ(receivedData[0].toString(), "via_client");
obj->release();
}
#include "test_event_system.moc"