mirror of
https://github.com/logos-co/logos-cpp-sdk.git
synced 2026-08-31 09:41:06 +00:00
341 lines
11 KiB
C++
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"
|