diff options
-rw-r--r-- | src/event.cpp | 15 | ||||
-rw-r--r-- | src/filters.cpp | 2 | ||||
-rw-r--r-- | src/nostr_service.cpp | 74 | ||||
-rw-r--r-- | test/nostr_service_test.cpp | 141 |
4 files changed, 183 insertions, 49 deletions
diff --git a/src/event.cpp b/src/event.cpp index 6510ac6..532ba81 100644 --- a/src/event.cpp +++ b/src/event.cpp @@ -43,25 +43,14 @@ string Event::serialize() j["id"] = this->generateId(j.dump()); - json jarr = json::array({ "EVENT", j }); - - return jarr.dump(); + return j.dump(); }; Event Event::fromString(string jstr) { json j = json::parse(jstr); - Event event; - event.id = j["id"]; - event.pubkey = j["pubkey"]; - event.createdAt = j["created_at"]; - event.kind = j["kind"]; - event.tags = j["tags"]; - event.content = j["content"]; - event.sig = j["sig"]; - - return event; + return Event::fromJson(j); }; Event Event::fromJson(json j) diff --git a/src/filters.cpp b/src/filters.cpp index 3179c2f..83756f9 100644 --- a/src/filters.cpp +++ b/src/filters.cpp @@ -44,7 +44,7 @@ string Filters::serialize(string subscriptionId) json jarr = json::array({ "REQ", subscriptionId, j }); - return j.dump(); + return jarr.dump(); }; void Filters::validate() diff --git a/src/nostr_service.cpp b/src/nostr_service.cpp index 73ce95e..d1744e3 100644 --- a/src/nostr_service.cpp +++ b/src/nostr_service.cpp @@ -130,16 +130,21 @@ tuple<RelayList, RelayList> NostrService::publishEvent(shared_ptr<Event> event) PLOG_INFO << "Attempting to publish event to Nostr relays."; - string serializedEvent; + json message; try { this->_signer->sign(event); - serializedEvent = event->serialize(); + message = json::array({ "EVENT", event->serialize() }); } - catch (const std::invalid_argument& error) + catch (const std::invalid_argument& e) { - PLOG_ERROR << "Failed to sign event: " << error.what(); - throw error; + PLOG_ERROR << "Failed to sign event: " << e.what(); + throw e; + } + catch (const json::exception& je) + { + PLOG_ERROR << "Failed to serialize event: " << je.what(); + throw je; } lock_guard<mutex> lock(this->_propertyMutex); @@ -147,8 +152,9 @@ tuple<RelayList, RelayList> NostrService::publishEvent(shared_ptr<Event> event) for (const string& relay : this->_activeRelays) { PLOG_INFO << "Entering lambda."; - future<tuple<string, bool>> publishFuture = async([this, relay, serializedEvent]() { - return this->_client->send(serializedEvent, relay); + future<tuple<string, bool>> publishFuture = async([this, relay, message]() + { + return this->_client->send(message.dump(), relay); }); publishFutures.push_back(move(publishFuture)); } @@ -176,7 +182,6 @@ tuple<RelayList, RelayList> NostrService::publishEvent(shared_ptr<Event> event) string NostrService::queryRelays(shared_ptr<Filters> filters) { return this->queryRelays(filters, [this](string subscriptionId, shared_ptr<Event> event) { - lock_guard<mutex> lock(this->_propertyMutex); this->_lastRead[subscriptionId] = event->id; this->onEvent(subscriptionId, event); }); @@ -190,21 +195,26 @@ string NostrService::queryRelays( RelayList failedRelays; string subscriptionId = this->generateSubscriptionId(); + string request = filters->serialize(subscriptionId); vector<future<tuple<string, bool>>> requestFutures; + vector<future<void>> receiveFutures; for (const string relay : this->_activeRelays) { - lock_guard<mutex> lock(this->_propertyMutex); this->_subscriptions[relay].push_back(subscriptionId); - string request = filters->serialize(subscriptionId); - - future<tuple<string, bool>> requestFuture = async([this, &relay, &request]() { + + future<tuple<string, bool>> requestFuture = async([this, &relay, &request]() + { return this->_client->send(request, relay); }); requestFutures.push_back(move(requestFuture)); - this->_client->receive(relay, [this, responseHandler](string payload) { - this->onMessage(payload, responseHandler); + auto receiveFuture = async([this, &relay, &responseHandler]() + { + this->_client->receive(relay, [this, responseHandler](string payload) { + this->onMessage(payload, responseHandler); + }); }); + receiveFutures.push_back(move(receiveFuture)); } for (auto& publishFuture : requestFutures) @@ -220,6 +230,11 @@ string NostrService::queryRelays( } } + for (auto& receiveFuture : receiveFutures) + { + receiveFuture.get(); + } + size_t targetCount = this->_activeRelays.size(); size_t successfulCount = successfulRelays.size(); PLOG_INFO << "Sent query to " << successfulCount << "/" << targetCount << " open relay connections."; @@ -287,7 +302,8 @@ tuple<RelayList, RelayList> NostrService::closeSubscription(string subscriptionI } string request = this->generateCloseRequest(subscriptionId); - future<tuple<string, bool>> closeFuture = async([this, &relay, &request]() { + future<tuple<string, bool>> closeFuture = async([this, &relay, &request]() + { return this->_client->send(request, relay); }); closeFutures.push_back(move(closeFuture)); @@ -326,7 +342,8 @@ tuple<RelayList, RelayList> NostrService::closeSubscriptions(RelayList relays) vector<future<tuple<RelayList, RelayList>>> closeFutures; for (const string relay : relays) { - future<tuple<RelayList, RelayList>> closeFuture = async([this, &relay]() { + future<tuple<RelayList, RelayList>> closeFuture = async([this, &relay]() + { RelayList successfulRelays; RelayList failedRelays; @@ -487,19 +504,24 @@ void NostrService::onMessage( string message, function<void(const string&, shared_ptr<Event>)> eventHandler) { - json jarr = json::array(); - jarr = json::parse(message); - - string messageType = jarr[0]; + try + { + json jMessage = json::parse(message); + string messageType = jMessage[0]; + if (messageType == "EVENT") + { + string subscriptionId = jMessage[1]; + Event event = Event::fromString(jMessage[2]); + eventHandler(subscriptionId, make_shared<Event>(event)); + } - if (messageType == "EVENT") + // Support other message types here, if necessary. + } + catch (const json::exception& je) { - string subscriptionId = jarr[1]; - Event event = Event::fromJson(jarr[2]); - eventHandler(subscriptionId, make_shared<Event>(event)); + PLOG_ERROR << "JSON handling exception: " << je.what(); + throw je; } - - // Support other message types here, if necessary. }; void NostrService::onEvent(string subscriptionId, shared_ptr<Event> event) diff --git a/test/nostr_service_test.cpp b/test/nostr_service_test.cpp index 64c14e8..6b68221 100644 --- a/test/nostr_service_test.cpp +++ b/test/nostr_service_test.cpp @@ -1,12 +1,15 @@ #include <gmock/gmock.h> #include <gtest/gtest.h> +#include <nlohmann/json.hpp> #include <plog/Appenders/ConsoleAppender.h> #include <plog/Formatters/TxtFormatter.h> +#include <iostream> #include <websocketpp/client.hpp> #include <client/web_socket_client.hpp> #include <nostr.hpp> +using nlohmann::json; using std::function; using std::lock_guard; using std::make_shared; @@ -67,6 +70,35 @@ public: return event; }; + static const string getTestEventMessage(string subscriptionId) + { + auto event = make_shared<nostr::Event>(getTestEvent()); + + auto signer = make_unique<FakeSigner>(); + signer->sign(event); + + json jarr = json::array(); + jarr.push_back("EVENT"); + jarr.push_back(subscriptionId); + jarr.push_back(event->serialize()); + + return jarr.dump(); + } + + static const nostr::Filters getTestFilters() + { + nostr::Filters filters; + filters.authors = { + "13tn5ccv2guflxgffq4aj0hw5x39pz70zcdrfd6vym887gry38zys28dask", + "1l9d9jh67rkwayalrxcy686aujyz5pper5kzjv8jvg8pu9v9ns4ls0xvq42", + "187ujhtmnv82ftg03h4heetwk3dd9mlfkf8th3fvmrk20nxk9mansuzuyla" + }; + filters.kinds = { 0, 1 }; + filters.limit = 10; + + return filters; + } + protected: shared_ptr<plog::ConsoleAppender<plog::TxtFormatter>> testAppender; shared_ptr<MockWebSocketClient> mockClient; @@ -99,7 +131,11 @@ TEST_F(NostrServiceTest, Constructor_InitializesService_WithNoDefaultRelays) TEST_F(NostrServiceTest, Constructor_InitializesService_WithProvidedDefaultRelays) { - auto nostrService = make_unique<nostr::NostrService>(testAppender, mockClient, fakeSigner, defaultTestRelays); + auto nostrService = make_unique<nostr::NostrService>( + testAppender, + mockClient, + fakeSigner, + defaultTestRelays); auto defaultRelays = nostrService->defaultRelays(); auto activeRelays = nostrService->activeRelays(); @@ -140,7 +176,11 @@ TEST_F(NostrServiceTest, OpenRelayConnections_OpensConnections_ToDefaultRelays) return status; })); - auto nostrService = make_unique<nostr::NostrService>(testAppender, mockClient, fakeSigner, defaultTestRelays); + auto nostrService = make_unique<nostr::NostrService>( + testAppender, + mockClient, + fakeSigner, + defaultTestRelays); nostrService->openRelayConnections(); auto activeRelays = nostrService->activeRelays(); @@ -175,7 +215,11 @@ TEST_F(NostrServiceTest, OpenRelayConnections_OpensConnections_ToProvidedRelays) return status; })); - auto nostrService = make_unique<nostr::NostrService>(testAppender, mockClient, fakeSigner, defaultTestRelays); + auto nostrService = make_unique<nostr::NostrService>( + testAppender, + mockClient, + fakeSigner, + defaultTestRelays); nostrService->openRelayConnections(testRelays); auto activeRelays = nostrService->activeRelays(); @@ -212,7 +256,11 @@ TEST_F(NostrServiceTest, OpenRelayConnections_AddsOpenConnections_ToActiveRelays return status; })); - auto nostrService = make_unique<nostr::NostrService>(testAppender, mockClient, fakeSigner, defaultTestRelays); + auto nostrService = make_unique<nostr::NostrService>( + testAppender, + mockClient, + fakeSigner, + defaultTestRelays); nostrService->openRelayConnections(); auto activeRelays = nostrService->activeRelays(); @@ -255,7 +303,11 @@ TEST_F(NostrServiceTest, CloseRelayConnections_ClosesConnections_ToActiveRelays) return status; })); - auto nostrService = make_unique<nostr::NostrService>(testAppender, mockClient, fakeSigner, defaultTestRelays); + auto nostrService = make_unique<nostr::NostrService>( + testAppender, + mockClient, + fakeSigner, + defaultTestRelays); nostrService->openRelayConnections(); EXPECT_CALL(*mockClient, closeConnection(defaultTestRelays[0])).Times(1); @@ -290,7 +342,11 @@ TEST_F(NostrServiceTest, CloseRelayConnections_RemovesClosedConnections_FromActi return status; })); - auto nostrService = make_unique<nostr::NostrService>(testAppender, mockClient, fakeSigner, allTestRelays); + auto nostrService = make_unique<nostr::NostrService>( + testAppender, + mockClient, + fakeSigner, + allTestRelays); nostrService->openRelayConnections(); EXPECT_CALL(*mockClient, closeConnection(testRelays[0])).Times(1); @@ -328,7 +384,11 @@ TEST_F(NostrServiceTest, PublishEvent_CorrectlyIndicates_AllSuccesses) return status; })); - auto nostrService = make_unique<nostr::NostrService>(testAppender, mockClient, fakeSigner, defaultTestRelays); + auto nostrService = make_unique<nostr::NostrService>( + testAppender, + mockClient, + fakeSigner, + defaultTestRelays); nostrService->openRelayConnections(); EXPECT_CALL(*mockClient, send(_, _)) @@ -369,7 +429,11 @@ TEST_F(NostrServiceTest, PublishEvent_CorrectlyIndicates_AllFailures) return status; })); - auto nostrService = make_unique<nostr::NostrService>(testAppender, mockClient, fakeSigner, defaultTestRelays); + auto nostrService = make_unique<nostr::NostrService>( + testAppender, + mockClient, + fakeSigner, + defaultTestRelays); nostrService->openRelayConnections(); EXPECT_CALL(*mockClient, send(_, _)) @@ -410,7 +474,11 @@ TEST_F(NostrServiceTest, PublishEvent_CorrectlyIndicates_MixedSuccessesAndFailur return status; })); - auto nostrService = make_unique<nostr::NostrService>(testAppender, mockClient, fakeSigner, defaultTestRelays); + auto nostrService = make_unique<nostr::NostrService>( + testAppender, + mockClient, + fakeSigner, + defaultTestRelays); nostrService->openRelayConnections(); EXPECT_CALL(*mockClient, send(_, defaultTestRelays[0])) @@ -435,4 +503,59 @@ TEST_F(NostrServiceTest, PublishEvent_CorrectlyIndicates_MixedSuccessesAndFailur ASSERT_EQ(failures.size(), 1); ASSERT_EQ(failures[0], defaultTestRelays[1]); }; + +TEST_F(NostrServiceTest, QueryRelays_UsesDefaultHandler_AndReturnsSubscriptionId) +{ + mutex connectionStatusMutex; + auto connectionStatus = make_shared<unordered_map<string, bool>>(); + connectionStatus->insert({ defaultTestRelays[0], false }); + connectionStatus->insert({ defaultTestRelays[1], false }); + + EXPECT_CALL(*mockClient, isConnected(_)) + .WillRepeatedly(Invoke([connectionStatus, &connectionStatusMutex](string uri) + { + lock_guard<mutex> lock(connectionStatusMutex); + bool status = connectionStatus->at(uri); + if (status == false) + { + connectionStatus->at(uri) = true; + } + return status; + })); + + auto nostrService = make_unique<nostr::NostrService>( + testAppender, + mockClient, + fakeSigner, + defaultTestRelays); + nostrService->openRelayConnections(); + + auto sentSubscriptionId = make_shared<string>(); + EXPECT_CALL(*mockClient, send(_, _)) + .Times(2) + .WillRepeatedly(Invoke([sentSubscriptionId](string message, string uri) + { + json jarr = json::array(); + jarr = json::parse(message); + + string temp = jarr[1].dump(); + if (!temp.empty() && temp[0] == '\"' && temp[temp.size() - 1] == '\"') + { + *sentSubscriptionId = temp.substr(1, temp.size() - 2); + } + + return make_tuple(uri, true); + })); + EXPECT_CALL(*mockClient, receive(_, _)) + .Times(2) + .WillRepeatedly(Invoke([sentSubscriptionId](string _, function<void(const string&)> messageHandler) + { + messageHandler(getTestEventMessage(*sentSubscriptionId)); + })); + + auto filters = make_shared<nostr::Filters>(getTestFilters()); + auto receivedSubscriptionId = nostrService->queryRelays(filters); + + EXPECT_STREQ(receivedSubscriptionId.c_str(), sentSubscriptionId->c_str()); +}; } // namespace nostr_test |