aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorLibravatar Michael J <37635304+buttercat1791@users.noreply.github.com>2024-05-09 08:28:50 -0500
committerLibravatar GitHub <noreply@github.com>2024-05-09 08:28:50 -0500
commit663fb4e7199e1b4318a5bc107096f6a529823e02 (patch)
tree7d92570de852f97550f84140bd794fa5d64c567b /test
parent0d87b4053983ec8edaff5b73491b717866876586 (diff)
parentd6faf6c815611450d1b61045b53525d7f25ac5c9 (diff)
Merge pull request #3 from buttercat1791/relay-readv0.0.2
Full Relay Read/Write Support
Diffstat (limited to 'test')
-rw-r--r--test/nostr_service_test.cpp811
1 files changed, 746 insertions, 65 deletions
diff --git a/test/nostr_service_test.cpp b/test/nostr_service_test.cpp
index 83de3be..b3b9b28 100644
--- a/test/nostr_service_test.cpp
+++ b/test/nostr_service_test.cpp
@@ -1,21 +1,19 @@
+#include <chrono>
#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 std::lock_guard;
-using std::make_shared;
-using std::mutex;
-using std::shared_ptr;
-using std::string;
-using std::unordered_map;
-using ::testing::_;
-using ::testing::Invoke;
-using ::testing::Return;
+using namespace std;
+using namespace ::testing;
+
+using nlohmann::json;
namespace nostr_test
{
@@ -23,42 +21,172 @@ class MockWebSocketClient : public client::IWebSocketClient {
public:
MOCK_METHOD(void, start, (), (override));
MOCK_METHOD(void, stop, (), (override));
- MOCK_METHOD(void, openConnection, (std::string uri), (override));
- MOCK_METHOD(bool, isConnected, (std::string uri), (override));
- MOCK_METHOD((std::tuple<std::string, bool>), send, (std::string message, std::string uri), (override));
- MOCK_METHOD(void, closeConnection, (std::string uri), (override));
+ MOCK_METHOD(void, openConnection, (string uri), (override));
+ MOCK_METHOD(bool, isConnected, (string uri), (override));
+ MOCK_METHOD((tuple<string, bool>), send, (string message, string uri), (override));
+ MOCK_METHOD((tuple<string, bool>), send, (string message, string uri, function<void(const string&)> messageHandler), (override));
+ MOCK_METHOD(void, receive, (string uri, function<void(const string&)> messageHandler), (override));
+ MOCK_METHOD(void, closeConnection, (string uri), (override));
+};
+
+class FakeSigner : public nostr::ISigner
+{
+public:
+ void sign(shared_ptr<nostr::Event> event) override
+ {
+ event->sig = "fake_signature";
+ }
};
class NostrServiceTest : public testing::Test
{
public:
- inline static const nostr::RelayList defaultTestRelays =
+ inline static const vector<string> defaultTestRelays =
{
"wss://relay.damus.io",
"wss://nostr.thesamecat.io"
};
+ static const nostr::Event getTextNoteTestEvent()
+ {
+ nostr::Event event;
+ event.pubkey = "13tn5ccv2guflxgffq4aj0hw5x39pz70zcdrfd6vym887gry38zys28dask";
+ event.kind = 1;
+ event.tags =
+ {
+ { "e", "5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36", "wss://nostr.example.com" },
+ { "p", "f7234bd4c1394dda46d09f35bd384dd30cc552ad5541990f98844fb06676e9ca" },
+ { "a", "30023:f7234bd4c1394dda46d09f35bd384dd30cc552ad5541990f98844fb06676e9ca:abcd", "wss://nostr.example.com" }
+ };
+ event.content = "Hello, World!";
+
+ return event;
+ };
+
+ static const vector<nostr::Event> getMultipleTextNoteTestEvents()
+ {
+ auto now = std::chrono::system_clock::now();
+ std::time_t currentTime = std::chrono::system_clock::to_time_t(now);
+
+ nostr::Event event1;
+ event1.pubkey = "13tn5ccv2guflxgffq4aj0hw5x39pz70zcdrfd6vym887gry38zys28dask";
+ event1.kind = 1;
+ event1.tags =
+ {
+ { "e", "5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36", "wss://nostr.example.com" },
+ { "p", "f7234bd4c1394dda46d09f35bd384dd30cc552ad5541990f98844fb06676e9ca" },
+ { "a", "30023:f7234bd4c1394dda46d09f35bd384dd30cc552ad5541990f98844fb06676e9ca:abcd", "wss://nostr.example.com" }
+ };
+ event1.content = "Hello, World!";
+ event1.createdAt = currentTime;
+
+ nostr::Event event2;
+ event2.pubkey = "1l9d9jh67rkwayalrxcy686aujyz5pper5kzjv8jvg8pu9v9ns4ls0xvq42";
+ event2.kind = 1;
+ event2.tags =
+ {
+ { "e", "5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36", "wss://nostr.example.com" },
+ { "p", "f7234bd4c1394dda46d09f35bd384dd30cc552ad5541990f98844fb06676e9ca" },
+ { "a", "30023:f7234bd4c1394dda46d09f35bd384dd30cc552ad5541990f98844fb06676e9ca:abcd", "wss://nostr.example.com" }
+ };
+ event2.content = "Welcome to Nostr!";
+ event2.createdAt = currentTime;
+
+ nostr::Event event3;
+ event3.pubkey = "187ujhtmnv82ftg03h4heetwk3dd9mlfkf8th3fvmrk20nxk9mansuzuyla";
+ event3.kind = 1;
+ event3.tags =
+ {
+ { "e", "5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36", "wss://nostr.example.com" },
+ { "p", "f7234bd4c1394dda46d09f35bd384dd30cc552ad5541990f98844fb06676e9ca" },
+ { "a", "30023:f7234bd4c1394dda46d09f35bd384dd30cc552ad5541990f98844fb06676e9ca:abcd", "wss://nostr.example.com" }
+ };
+ event3.content = "Time for some introductions!";
+ event3.createdAt = currentTime;
+
+ return { event1, event2, event3 };
+ };
+
+ static const nostr::Event getLongFormTestEvent()
+ {
+ nostr::Event event;
+ event.pubkey = "13tn5ccv2guflxgffq4aj0hw5x39pz70zcdrfd6vym887gry38zys28dask";
+ event.kind = 30023;
+ event.tags =
+ {
+ { "event", "5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36", "wss://nostr.example.com" },
+ { "pubkey", "f7234bd4c1394dda46d09f35bd384dd30cc552ad5541990f98844fb06676e9ca" },
+ { "author", "30023:f7234bd4c1394dda46d09f35bd384dd30cc552ad5541990f98844fb06676e9ca:abcd", "wss://nostr.example.com" }
+ };
+ event.content = "Hello, World!";
+
+ return event;
+ }
+
+ static const string getTestEventMessage(shared_ptr<nostr::Event> event, string subscriptionId)
+ {
+ 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 getKind0And1TestFilters()
+ {
+ nostr::Filters filters;
+ filters.authors = {
+ "13tn5ccv2guflxgffq4aj0hw5x39pz70zcdrfd6vym887gry38zys28dask",
+ "1l9d9jh67rkwayalrxcy686aujyz5pper5kzjv8jvg8pu9v9ns4ls0xvq42",
+ "187ujhtmnv82ftg03h4heetwk3dd9mlfkf8th3fvmrk20nxk9mansuzuyla"
+ };
+ filters.kinds = { 0, 1 };
+ filters.limit = 10;
+
+ return filters;
+ }
+
+ static const nostr::Filters getKind30023TestFilters()
+ {
+ nostr::Filters filters;
+ filters.authors = {
+ "13tn5ccv2guflxgffq4aj0hw5x39pz70zcdrfd6vym887gry38zys28dask",
+ "1l9d9jh67rkwayalrxcy686aujyz5pper5kzjv8jvg8pu9v9ns4ls0xvq42",
+ "187ujhtmnv82ftg03h4heetwk3dd9mlfkf8th3fvmrk20nxk9mansuzuyla"
+ };
+ filters.kinds = { 30023 };
+ filters.limit = 5;
+
+ return filters;
+ }
+
protected:
shared_ptr<plog::ConsoleAppender<plog::TxtFormatter>> testAppender;
- shared_ptr<MockWebSocketClient> testClient;
+ shared_ptr<MockWebSocketClient> mockClient;
+ shared_ptr<FakeSigner> fakeSigner;
void SetUp() override
{
testAppender = make_shared<plog::ConsoleAppender<plog::TxtFormatter>>();
- testClient = make_shared<MockWebSocketClient>();
+ mockClient = make_shared<MockWebSocketClient>();
+ fakeSigner = make_shared<FakeSigner>();
};
};
TEST_F(NostrServiceTest, Constructor_StartsClient)
{
- EXPECT_CALL(*testClient, start()).Times(1);
+ EXPECT_CALL(*mockClient, start()).Times(1);
- auto nostrService = new nostr::NostrService(testAppender.get(), testClient.get());
+ auto nostrService = make_unique<nostr::NostrService>(testAppender, mockClient, fakeSigner);
};
TEST_F(NostrServiceTest, Constructor_InitializesService_WithNoDefaultRelays)
{
- auto nostrService = new nostr::NostrService(testAppender.get(), testClient.get());
+ auto nostrService = make_unique<nostr::NostrService>(testAppender, mockClient, fakeSigner);
auto defaultRelays = nostrService->defaultRelays();
auto activeRelays = nostrService->activeRelays();
@@ -68,7 +196,11 @@ TEST_F(NostrServiceTest, Constructor_InitializesService_WithNoDefaultRelays)
TEST_F(NostrServiceTest, Constructor_InitializesService_WithProvidedDefaultRelays)
{
- auto nostrService = new nostr::NostrService(testAppender.get(), testClient.get(), defaultTestRelays);
+ auto nostrService = make_unique<nostr::NostrService>(
+ testAppender,
+ mockClient,
+ fakeSigner,
+ defaultTestRelays);
auto defaultRelays = nostrService->defaultRelays();
auto activeRelays = nostrService->activeRelays();
@@ -82,9 +214,9 @@ TEST_F(NostrServiceTest, Constructor_InitializesService_WithProvidedDefaultRelay
TEST_F(NostrServiceTest, Destructor_StopsClient)
{
- EXPECT_CALL(*testClient, start()).Times(1);
+ EXPECT_CALL(*mockClient, start()).Times(1);
- auto nostrService = new nostr::NostrService(testAppender.get(), testClient.get());
+ auto nostrService = make_unique<nostr::NostrService>(testAppender, mockClient, fakeSigner);
};
TEST_F(NostrServiceTest, OpenRelayConnections_OpensConnections_ToDefaultRelays)
@@ -94,10 +226,10 @@ TEST_F(NostrServiceTest, OpenRelayConnections_OpensConnections_ToDefaultRelays)
connectionStatus->insert({ defaultTestRelays[0], false });
connectionStatus->insert({ defaultTestRelays[1], false });
- EXPECT_CALL(*testClient, openConnection(defaultTestRelays[0])).Times(1);
- EXPECT_CALL(*testClient, openConnection(defaultTestRelays[1])).Times(1);
+ EXPECT_CALL(*mockClient, openConnection(defaultTestRelays[0])).Times(1);
+ EXPECT_CALL(*mockClient, openConnection(defaultTestRelays[1])).Times(1);
- EXPECT_CALL(*testClient, isConnected(_))
+ EXPECT_CALL(*mockClient, isConnected(_))
.WillRepeatedly(Invoke([connectionStatus, &connectionStatusMutex](string uri)
{
lock_guard<mutex> lock(connectionStatusMutex);
@@ -109,7 +241,11 @@ TEST_F(NostrServiceTest, OpenRelayConnections_OpensConnections_ToDefaultRelays)
return status;
}));
- auto nostrService = new nostr::NostrService(testAppender.get(), testClient.get(), defaultTestRelays);
+ auto nostrService = make_unique<nostr::NostrService>(
+ testAppender,
+ mockClient,
+ fakeSigner,
+ defaultTestRelays);
nostrService->openRelayConnections();
auto activeRelays = nostrService->activeRelays();
@@ -122,17 +258,17 @@ TEST_F(NostrServiceTest, OpenRelayConnections_OpensConnections_ToDefaultRelays)
TEST_F(NostrServiceTest, OpenRelayConnections_OpensConnections_ToProvidedRelays)
{
- nostr::RelayList testRelays = { "wss://nos.lol" };
+ vector<string> testRelays = { "wss://nos.lol" };
mutex connectionStatusMutex;
auto connectionStatus = make_shared<unordered_map<string, bool>>();
connectionStatus -> insert({ testRelays[0], false });
- EXPECT_CALL(*testClient, openConnection(testRelays[0])).Times(1);
- EXPECT_CALL(*testClient, openConnection(defaultTestRelays[0])).Times(0);
- EXPECT_CALL(*testClient, openConnection(defaultTestRelays[1])).Times(0);
+ EXPECT_CALL(*mockClient, openConnection(testRelays[0])).Times(1);
+ EXPECT_CALL(*mockClient, openConnection(defaultTestRelays[0])).Times(0);
+ EXPECT_CALL(*mockClient, openConnection(defaultTestRelays[1])).Times(0);
- EXPECT_CALL(*testClient, isConnected(_))
+ EXPECT_CALL(*mockClient, isConnected(_))
.WillRepeatedly(Invoke([connectionStatus, &connectionStatusMutex](string uri)
{
lock_guard<mutex> lock(connectionStatusMutex);
@@ -144,7 +280,11 @@ TEST_F(NostrServiceTest, OpenRelayConnections_OpensConnections_ToProvidedRelays)
return status;
}));
- auto nostrService = new nostr::NostrService(testAppender.get(), testClient.get(), defaultTestRelays);
+ auto nostrService = make_unique<nostr::NostrService>(
+ testAppender,
+ mockClient,
+ fakeSigner,
+ defaultTestRelays);
nostrService->openRelayConnections(testRelays);
auto activeRelays = nostrService->activeRelays();
@@ -157,7 +297,7 @@ TEST_F(NostrServiceTest, OpenRelayConnections_OpensConnections_ToProvidedRelays)
TEST_F(NostrServiceTest, OpenRelayConnections_AddsOpenConnections_ToActiveRelays)
{
- nostr::RelayList testRelays = { "wss://nos.lol" };
+ vector<string> testRelays = { "wss://nos.lol" };
mutex connectionStatusMutex;
auto connectionStatus = make_shared<unordered_map<string, bool>>();
@@ -165,11 +305,11 @@ TEST_F(NostrServiceTest, OpenRelayConnections_AddsOpenConnections_ToActiveRelays
connectionStatus->insert({ defaultTestRelays[1], false });
connectionStatus->insert({ testRelays[0], false });
- EXPECT_CALL(*testClient, openConnection(defaultTestRelays[0])).Times(1);
- EXPECT_CALL(*testClient, openConnection(defaultTestRelays[1])).Times(1);
- EXPECT_CALL(*testClient, openConnection(testRelays[0])).Times(1);
+ EXPECT_CALL(*mockClient, openConnection(defaultTestRelays[0])).Times(1);
+ EXPECT_CALL(*mockClient, openConnection(defaultTestRelays[1])).Times(1);
+ EXPECT_CALL(*mockClient, openConnection(testRelays[0])).Times(1);
- EXPECT_CALL(*testClient, isConnected(_))
+ EXPECT_CALL(*mockClient, isConnected(_))
.WillRepeatedly(Invoke([connectionStatus, &connectionStatusMutex](string uri)
{
lock_guard<mutex> lock(connectionStatusMutex);
@@ -181,7 +321,11 @@ TEST_F(NostrServiceTest, OpenRelayConnections_AddsOpenConnections_ToActiveRelays
return status;
}));
- auto nostrService = new nostr::NostrService(testAppender.get(), testClient.get(), defaultTestRelays);
+ auto nostrService = make_unique<nostr::NostrService>(
+ testAppender,
+ mockClient,
+ fakeSigner,
+ defaultTestRelays);
nostrService->openRelayConnections();
auto activeRelays = nostrService->activeRelays();
@@ -212,7 +356,7 @@ TEST_F(NostrServiceTest, CloseRelayConnections_ClosesConnections_ToActiveRelays)
connectionStatus->insert({ defaultTestRelays[0], false });
connectionStatus->insert({ defaultTestRelays[1], false });
- EXPECT_CALL(*testClient, isConnected(_))
+ EXPECT_CALL(*mockClient, isConnected(_))
.WillRepeatedly(Invoke([connectionStatus, &connectionStatusMutex](string uri)
{
lock_guard<mutex> lock(connectionStatusMutex);
@@ -224,11 +368,15 @@ TEST_F(NostrServiceTest, CloseRelayConnections_ClosesConnections_ToActiveRelays)
return status;
}));
- auto nostrService = new nostr::NostrService(testAppender.get(), testClient.get(), defaultTestRelays);
+ auto nostrService = make_unique<nostr::NostrService>(
+ testAppender,
+ mockClient,
+ fakeSigner,
+ defaultTestRelays);
nostrService->openRelayConnections();
- EXPECT_CALL(*testClient, closeConnection(defaultTestRelays[0])).Times(1);
- EXPECT_CALL(*testClient, closeConnection(defaultTestRelays[1])).Times(1);
+ EXPECT_CALL(*mockClient, closeConnection(defaultTestRelays[0])).Times(1);
+ EXPECT_CALL(*mockClient, closeConnection(defaultTestRelays[1])).Times(1);
nostrService->closeRelayConnections();
@@ -238,8 +386,8 @@ TEST_F(NostrServiceTest, CloseRelayConnections_ClosesConnections_ToActiveRelays)
TEST_F(NostrServiceTest, CloseRelayConnections_RemovesClosedConnections_FromActiveRelays)
{
- nostr::RelayList testRelays = { "wss://nos.lol" };
- nostr::RelayList allTestRelays = { defaultTestRelays[0], defaultTestRelays[1], testRelays[0] };
+ vector<string> testRelays = { "wss://nos.lol" };
+ vector<string> allTestRelays = { defaultTestRelays[0], defaultTestRelays[1], testRelays[0] };
mutex connectionStatusMutex;
auto connectionStatus = make_shared<unordered_map<string, bool>>();
@@ -247,7 +395,7 @@ TEST_F(NostrServiceTest, CloseRelayConnections_RemovesClosedConnections_FromActi
connectionStatus->insert({ defaultTestRelays[1], false });
connectionStatus->insert({ testRelays[0], false });
- EXPECT_CALL(*testClient, isConnected(_))
+ EXPECT_CALL(*mockClient, isConnected(_))
.WillRepeatedly(Invoke([connectionStatus, &connectionStatusMutex](string uri)
{
lock_guard<mutex> lock(connectionStatusMutex);
@@ -259,10 +407,14 @@ TEST_F(NostrServiceTest, CloseRelayConnections_RemovesClosedConnections_FromActi
return status;
}));
- auto nostrService = new nostr::NostrService(testAppender.get(), testClient.get(), allTestRelays);
+ auto nostrService = make_unique<nostr::NostrService>(
+ testAppender,
+ mockClient,
+ fakeSigner,
+ allTestRelays);
nostrService->openRelayConnections();
- EXPECT_CALL(*testClient, closeConnection(testRelays[0])).Times(1);
+ EXPECT_CALL(*mockClient, closeConnection(testRelays[0])).Times(1);
nostrService->closeRelayConnections(testRelays);
@@ -285,7 +437,7 @@ TEST_F(NostrServiceTest, PublishEvent_CorrectlyIndicates_AllSuccesses)
connectionStatus->insert({ defaultTestRelays[0], false });
connectionStatus->insert({ defaultTestRelays[1], false });
- EXPECT_CALL(*testClient, isConnected(_))
+ EXPECT_CALL(*mockClient, isConnected(_))
.WillRepeatedly(Invoke([connectionStatus, &connectionStatusMutex](string uri)
{
lock_guard<mutex> lock(connectionStatusMutex);
@@ -297,17 +449,28 @@ TEST_F(NostrServiceTest, PublishEvent_CorrectlyIndicates_AllSuccesses)
return status;
}));
- auto nostrService = new nostr::NostrService(testAppender.get(), testClient.get(), defaultTestRelays);
+ auto nostrService = make_unique<nostr::NostrService>(
+ testAppender,
+ mockClient,
+ fakeSigner,
+ defaultTestRelays);
nostrService->openRelayConnections();
- EXPECT_CALL(*testClient, send(_, _))
+ EXPECT_CALL(*mockClient, send(_, _, _))
.Times(2)
- .WillRepeatedly(Invoke([](string message, string uri)
+ .WillRepeatedly(Invoke([](string message, string uri, function<void(const string&)> messageHandler)
{
+ json messageArr = json::parse(message);
+ auto event = nostr::Event::fromString(messageArr[1]);
+
+ json jarr = json::array({ "OK", event.id, true, "Event accepted" });
+ messageHandler(jarr.dump());
+
return make_tuple(uri, true);
}));
- auto [successes, failures] = nostrService->publishEvent(nostr::Event());
+ auto testEvent = make_shared<nostr::Event>(getTextNoteTestEvent());
+ auto [successes, failures] = nostrService->publishEvent(testEvent);
ASSERT_EQ(successes.size(), defaultTestRelays.size());
for (auto relay : successes)
@@ -325,7 +488,7 @@ TEST_F(NostrServiceTest, PublishEvent_CorrectlyIndicates_AllFailures)
connectionStatus->insert({ defaultTestRelays[0], false });
connectionStatus->insert({ defaultTestRelays[1], false });
- EXPECT_CALL(*testClient, isConnected(_))
+ EXPECT_CALL(*mockClient, isConnected(_))
.WillRepeatedly(Invoke([connectionStatus, &connectionStatusMutex](string uri)
{
lock_guard<mutex> lock(connectionStatusMutex);
@@ -337,17 +500,23 @@ TEST_F(NostrServiceTest, PublishEvent_CorrectlyIndicates_AllFailures)
return status;
}));
- auto nostrService = new nostr::NostrService(testAppender.get(), testClient.get(), defaultTestRelays);
+ auto nostrService = make_unique<nostr::NostrService>(
+ testAppender,
+ mockClient,
+ fakeSigner,
+ defaultTestRelays);
nostrService->openRelayConnections();
- EXPECT_CALL(*testClient, send(_, _))
+ // Simulate a case where the message failed to send to all relays.
+ EXPECT_CALL(*mockClient, send(_, _, _))
.Times(2)
- .WillRepeatedly(Invoke([](string message, string uri)
+ .WillRepeatedly(Invoke([](string message, string uri, function<void(const string&)> messageHandler)
{
return make_tuple(uri, false);
}));
- auto [successes, failures] = nostrService->publishEvent(nostr::Event());
+ auto testEvent = make_shared<nostr::Event>(getTextNoteTestEvent());
+ auto [successes, failures] = nostrService->publishEvent(testEvent);
ASSERT_EQ(successes.size(), 0);
@@ -365,7 +534,7 @@ TEST_F(NostrServiceTest, PublishEvent_CorrectlyIndicates_MixedSuccessesAndFailur
connectionStatus->insert({ defaultTestRelays[0], false });
connectionStatus->insert({ defaultTestRelays[1], false });
- EXPECT_CALL(*testClient, isConnected(_))
+ EXPECT_CALL(*mockClient, isConnected(_))
.WillRepeatedly(Invoke([connectionStatus, &connectionStatusMutex](string uri)
{
lock_guard<mutex> lock(connectionStatusMutex);
@@ -377,23 +546,149 @@ TEST_F(NostrServiceTest, PublishEvent_CorrectlyIndicates_MixedSuccessesAndFailur
return status;
}));
- auto nostrService = new nostr::NostrService(testAppender.get(), testClient.get(), defaultTestRelays);
+ auto nostrService = make_unique<nostr::NostrService>(
+ testAppender,
+ mockClient,
+ fakeSigner,
+ defaultTestRelays);
nostrService->openRelayConnections();
- EXPECT_CALL(*testClient, send(_, defaultTestRelays[0]))
+ // Simulate a scenario where the message fails to send to one relay, but sends successfully to
+ // the other, and the relay accepts it.
+ EXPECT_CALL(*mockClient, send(_, defaultTestRelays[0], _))
.Times(1)
- .WillRepeatedly(Invoke([](string message, string uri)
+ .WillRepeatedly(Invoke([](string message, string uri, function<void(const string&)> messageHandler)
+ {
+ return make_tuple(uri, false);
+ }));
+ EXPECT_CALL(*mockClient, send(_, defaultTestRelays[1], _))
+ .Times(1)
+ .WillRepeatedly(Invoke([](string message, string uri, function<void(const string&)> messageHandler)
+ {
+ json messageArr = json::parse(message);
+ auto event = nostr::Event::fromString(messageArr[1]);
+
+ json jarr = json::array({ "OK", event.id, true, "Event accepted" });
+ messageHandler(jarr.dump());
+
+ return make_tuple(uri, true);
+ }));
+
+ auto testEvent = make_shared<nostr::Event>(getTextNoteTestEvent());
+ auto [successes, failures] = nostrService->publishEvent(testEvent);
+
+ ASSERT_EQ(successes.size(), 1);
+ ASSERT_EQ(successes[0], defaultTestRelays[1]);
+
+ ASSERT_EQ(failures.size(), 1);
+ ASSERT_EQ(failures[0], defaultTestRelays[0]);
+};
+
+TEST_F(NostrServiceTest, PublishEvent_CorrectlyIndicates_RejectedEvent)
+{
+ 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();
+
+ // Simulate a scenario where the message is rejected by all target relays.
+ EXPECT_CALL(*mockClient, send(_, _, _))
+ .Times(2)
+ .WillRepeatedly(Invoke([](string message, string uri, function<void(const string&)> messageHandler)
{
+ json messageArr = json::parse(message);
+ auto event = nostr::Event::fromString(messageArr[1]);
+
+ json jarr = json::array({ "OK", event.id, false, "Event rejected" });
+ messageHandler(jarr.dump());
+
return make_tuple(uri, true);
}));
- EXPECT_CALL(*testClient, send(_, defaultTestRelays[1]))
+
+ auto testEvent = make_shared<nostr::Event>(getTextNoteTestEvent());
+ auto [successes, failures] = nostrService->publishEvent(testEvent);
+
+ ASSERT_EQ(failures.size(), defaultTestRelays.size());
+ for (auto relay : failures)
+ {
+ ASSERT_NE(find(defaultTestRelays.begin(), defaultTestRelays.end(), relay), defaultTestRelays.end());
+ }
+};
+
+TEST_F(NostrServiceTest, PublishEvent_CorrectlyIndicates_EventRejectedBySomeRelays)
+{
+ 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();
+
+ // Simulate a scenario where the message fails to send to one relay, but sends successfully to
+ // the other, and the relay accepts it.
+ EXPECT_CALL(*mockClient, send(_, defaultTestRelays[0], _))
.Times(1)
- .WillRepeatedly(Invoke([](string message, string uri)
+ .WillRepeatedly(Invoke([](string message, string uri, function<void(const string&)> messageHandler)
{
- return make_tuple(uri, false);
+ json messageArr = json::parse(message);
+ auto event = nostr::Event::fromString(messageArr[1]);
+
+ json jarr = json::array({ "OK", event.id, true, "Event accepted" });
+ messageHandler(jarr.dump());
+
+ return make_tuple(uri, true);
+ }));
+ EXPECT_CALL(*mockClient, send(_, defaultTestRelays[1], _))
+ .Times(1)
+ .WillRepeatedly(Invoke([](string message, string uri, function<void(const string&)> messageHandler)
+ {
+ json messageArr = json::parse(message);
+ auto event = nostr::Event::fromString(messageArr[1]);
+
+ json jarr = json::array({ "OK", event.id, false, "Event rejected" });
+ messageHandler(jarr.dump());
+
+ return make_tuple(uri, true);
}));
- auto [successes, failures] = nostrService->publishEvent(nostr::Event());
+ auto testEvent = make_shared<nostr::Event>(getTextNoteTestEvent());
+ auto [successes, failures] = nostrService->publishEvent(testEvent);
ASSERT_EQ(successes.size(), 1);
ASSERT_EQ(successes[0], defaultTestRelays[0]);
@@ -401,4 +696,390 @@ TEST_F(NostrServiceTest, PublishEvent_CorrectlyIndicates_MixedSuccessesAndFailur
ASSERT_EQ(failures.size(), 1);
ASSERT_EQ(failures[0], defaultTestRelays[1]);
};
+
+TEST_F(NostrServiceTest, QueryRelays_ReturnsEvents_UpToEOSE)
+{
+ 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 signer = make_unique<FakeSigner>();
+ auto nostrService = make_unique<nostr::NostrService>(
+ testAppender,
+ mockClient,
+ fakeSigner,
+ defaultTestRelays);
+ nostrService->openRelayConnections();
+
+ auto testEvents = getMultipleTextNoteTestEvents();
+ vector<shared_ptr<nostr::Event>> signedTestEvents;
+ for (nostr::Event testEvent : testEvents)
+ {
+ auto signedEvent = make_shared<nostr::Event>(testEvent);
+ signer->sign(signedEvent);
+
+ auto serializedEvent = signedEvent->serialize();
+ auto deserializedEvent = nostr::Event::fromString(serializedEvent);
+
+ signedEvent = make_shared<nostr::Event>(deserializedEvent);
+ signedTestEvents.push_back(signedEvent);
+ }
+
+ // Expect the query messages.
+ EXPECT_CALL(*mockClient, send(HasSubstr("REQ"), _, _))
+ .Times(2)
+ .WillRepeatedly(Invoke([&testEvents, &signer](
+ string message,
+ string uri,
+ function<void(const string&)> messageHandler)
+ {
+ json messageArr = json::parse(message);
+ string subscriptionId = messageArr.at(1);
+
+ for (auto event : testEvents)
+ {
+ auto sendableEvent = make_shared<nostr::Event>(event);
+ signer->sign(sendableEvent);
+ json jarr = json::array({ "EVENT", subscriptionId, sendableEvent->serialize() });
+ messageHandler(jarr.dump());
+ }
+
+ json jarr = json::array({ "EOSE", subscriptionId });
+ messageHandler(jarr.dump());
+
+ return make_tuple(uri, true);
+ }));
+ // Expect the close subscription messages after the client receives events.
+ EXPECT_CALL(*mockClient, send(HasSubstr("CLOSE"), _))
+ .Times(2)
+ .WillRepeatedly(Invoke([](string message, string uri)
+ {
+ return make_tuple(uri, true);
+ }));
+
+ auto filters = make_shared<nostr::Filters>(getKind0And1TestFilters());
+ auto results = nostrService->queryRelays(filters);
+
+ // TODO: Check results size when the queryRelays method deduplicates results before returning.
+ // ASSERT_EQ(results.size(), testEvents.size());
+
+ // Check that the results contain the expected events.
+ for (auto resultEvent : results)
+ {
+ ASSERT_NE(
+ find_if(
+ signedTestEvents.begin(),
+ signedTestEvents.end(),
+ [&resultEvent](shared_ptr<nostr::Event> testEvent)
+ {
+ return *testEvent == *resultEvent;
+ }),
+ signedTestEvents.end());
+ }
+
+ auto subscriptions = nostrService->subscriptions();
+ ASSERT_TRUE(subscriptions.empty());
+};
+
+TEST_F(NostrServiceTest, QueryRelays_CallsHandler_WithReturnedEvents)
+{
+ 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 signer = make_unique<FakeSigner>();
+ auto nostrService = make_unique<nostr::NostrService>(
+ testAppender,
+ mockClient,
+ fakeSigner,
+ defaultTestRelays);
+ nostrService->openRelayConnections();
+
+ auto testEvents = getMultipleTextNoteTestEvents();
+ vector<shared_ptr<nostr::Event>> signedTestEvents;
+ for (nostr::Event testEvent : testEvents)
+ {
+ auto signedEvent = make_shared<nostr::Event>(testEvent);
+ signer->sign(signedEvent);
+
+ auto serializedEvent = signedEvent->serialize();
+ auto deserializedEvent = nostr::Event::fromString(serializedEvent);
+
+ signedEvent = make_shared<nostr::Event>(deserializedEvent);
+ signedTestEvents.push_back(signedEvent);
+ }
+
+ EXPECT_CALL(*mockClient, send(HasSubstr("REQ"), _, _))
+ .Times(2)
+ .WillRepeatedly(Invoke([&testEvents, &signer](
+ string message,
+ string uri,
+ function<void(const string&)> messageHandler)
+ {
+ json messageArr = json::parse(message);
+ string subscriptionId = messageArr.at(1);
+
+ for (auto event : testEvents)
+ {
+ auto sendableEvent = make_shared<nostr::Event>(event);
+ signer->sign(sendableEvent);
+ json jarr = json::array({ "EVENT", subscriptionId, sendableEvent->serialize() });
+ messageHandler(jarr.dump());
+ }
+
+ json jarr = json::array({ "EOSE", subscriptionId });
+ messageHandler(jarr.dump());
+
+ return make_tuple(uri, true);
+ }));
+
+ auto filters = make_shared<nostr::Filters>(getKind0And1TestFilters());
+ promise<void> eoseReceivedPromise;
+ auto eoseReceivedFuture = eoseReceivedPromise.get_future();
+ int eoseCount = 0;
+
+ string generatedSubscriptionId = nostrService->queryRelays(
+ filters,
+ [&generatedSubscriptionId, &signedTestEvents](const string& subscriptionId, shared_ptr<nostr::Event> event)
+ {
+ ASSERT_STREQ(subscriptionId.c_str(), generatedSubscriptionId.c_str());
+ ASSERT_NE(
+ find_if(
+ signedTestEvents.begin(),
+ signedTestEvents.end(),
+ [&event](shared_ptr<nostr::Event> testEvent)
+ {
+ return *testEvent == *event;
+ }),
+ signedTestEvents.end());
+ },
+ [&generatedSubscriptionId, &eoseReceivedPromise, &eoseCount]
+ (const string& subscriptionId)
+ {
+ ASSERT_STREQ(subscriptionId.c_str(), generatedSubscriptionId.c_str());
+
+ if (++eoseCount == 2)
+ {
+ eoseReceivedPromise.set_value();
+ }
+ },
+ [](const string&, const string&) {});
+
+ eoseReceivedFuture.wait();
+
+ // Check that the service is keeping track of its active subscriptions.
+ auto subscriptions = nostrService->subscriptions();
+ ASSERT_NO_THROW(subscriptions.at(generatedSubscriptionId));
+ ASSERT_EQ(subscriptions.at(generatedSubscriptionId).size(), 2);
+
+ EXPECT_CALL(*mockClient, send(HasSubstr("CLOSE"), _))
+ .Times(2)
+ .WillRepeatedly(Invoke([](string message, string uri)
+ {
+ return make_tuple(uri, true);
+ }));
+
+ auto [successes, failures] = nostrService->closeSubscription(generatedSubscriptionId);
+
+ ASSERT_TRUE(failures.empty());
+
+ // Check that the service has forgotten about the subscriptions after closing them.
+ subscriptions = nostrService->subscriptions();
+ ASSERT_TRUE(subscriptions.empty());
+};
+
+TEST_F(NostrServiceTest, Service_MaintainsMultipleSubscriptions_ThenClosesAll)
+{
+ // Mock connections.
+ mutex connectionStatusMutex;
+ auto connectionStatus = make_shared<unordered_map<string, bool>>();
+ vector<string> testRelays = { "wss://theforest.nostr1.com" };
+ connectionStatus->insert({ testRelays[0], 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 signer = make_unique<FakeSigner>();
+ auto nostrService = make_unique<nostr::NostrService>(
+ testAppender,
+ mockClient,
+ fakeSigner,
+ testRelays);
+ nostrService->openRelayConnections();
+
+ // Mock relay responses.
+ auto testEvents = getMultipleTextNoteTestEvents();
+ vector<shared_ptr<nostr::Event>> signedTestEvents;
+ for (nostr::Event testEvent : testEvents)
+ {
+ auto signedEvent = make_shared<nostr::Event>(testEvent);
+ signer->sign(signedEvent);
+
+ auto serializedEvent = signedEvent->serialize();
+ auto deserializedEvent = nostr::Event::fromString(serializedEvent);
+
+ signedEvent = make_shared<nostr::Event>(deserializedEvent);
+ signedTestEvents.push_back(signedEvent);
+ }
+
+ vector<string> subscriptionIds;
+ EXPECT_CALL(*mockClient, send(HasSubstr("REQ"), _, _))
+ .Times(2)
+ .WillOnce(Invoke([&testEvents, &signer, &subscriptionIds](
+ string message,
+ string uri,
+ function<void(const string&)> messageHandler)
+ {
+ json messageArr = json::parse(message);
+ subscriptionIds.push_back(messageArr.at(1));
+
+ for (auto event : testEvents)
+ {
+ auto sendableEvent = make_shared<nostr::Event>(event);
+ signer->sign(sendableEvent);
+ json jarr = json::array({ "EVENT", subscriptionIds.at(0), sendableEvent->serialize() });
+ messageHandler(jarr.dump());
+ }
+
+ json jarr = json::array({ "EOSE", subscriptionIds.at(0), });
+ messageHandler(jarr.dump());
+
+ return make_tuple(uri, true);
+ }))
+ .WillOnce(Invoke([&testEvents, &signer, &subscriptionIds](
+ string message,
+ string uri,
+ function<void(const string&)> messageHandler)
+ {
+ json messageArr = json::parse(message);
+ subscriptionIds.push_back(messageArr.at(1));
+
+ for (auto event : testEvents)
+ {
+ auto sendableEvent = make_shared<nostr::Event>(event);
+ signer->sign(sendableEvent);
+ json jarr = json::array({ "EVENT", subscriptionIds.at(1), sendableEvent->serialize() });
+ messageHandler(jarr.dump());
+ }
+
+ json jarr = json::array({ "EOSE", subscriptionIds.at(1), });
+ messageHandler(jarr.dump());
+
+ return make_tuple(uri, true);
+ }));
+
+ // Send queries.
+ auto shortFormFilters = make_shared<nostr::Filters>(getKind0And1TestFilters());
+ auto longFormFilters = make_shared<nostr::Filters>(getKind30023TestFilters());
+ promise<void> shortFormPromise;
+ promise<void> longFormPromise;
+ auto shortFormFuture = shortFormPromise.get_future();
+ auto longFormFuture = longFormPromise.get_future();
+
+ string shortFormSubscriptionId = nostrService->queryRelays(
+ shortFormFilters,
+ [&shortFormSubscriptionId, &signedTestEvents](const string& subscriptionId, shared_ptr<nostr::Event> event)
+ {
+ ASSERT_STREQ(subscriptionId.c_str(), shortFormSubscriptionId.c_str());
+ ASSERT_NE(
+ find_if(
+ signedTestEvents.begin(),
+ signedTestEvents.end(),
+ [&event](shared_ptr<nostr::Event> testEvent)
+ {
+ return *testEvent == *event;
+ }),
+ signedTestEvents.end());
+ },
+ [&shortFormSubscriptionId, &shortFormPromise]
+ (const string& subscriptionId)
+ {
+ ASSERT_STREQ(subscriptionId.c_str(), shortFormSubscriptionId.c_str());
+ shortFormPromise.set_value();
+ },
+ [](const string&, const string&) {});
+ string longFormSubscriptionId = nostrService->queryRelays(
+ shortFormFilters,
+ [&longFormSubscriptionId, &signedTestEvents](const string& subscriptionId, shared_ptr<nostr::Event> event)
+ {
+ ASSERT_STREQ(subscriptionId.c_str(), longFormSubscriptionId.c_str());
+ ASSERT_NE(
+ find_if(
+ signedTestEvents.begin(),
+ signedTestEvents.end(),
+ [&event](shared_ptr<nostr::Event> testEvent)
+ {
+ return *testEvent == *event;
+ }),
+ signedTestEvents.end());
+ },
+ [&longFormSubscriptionId, &longFormPromise]
+ (const string& subscriptionId)
+ {
+ ASSERT_STREQ(subscriptionId.c_str(), longFormSubscriptionId.c_str());
+ longFormPromise.set_value();
+ },
+ [](const string&, const string&) {});
+
+ shortFormFuture.wait();
+ longFormFuture.wait();
+
+ // Check that the service has opened a subscription for each query.
+ auto subscriptions = nostrService->subscriptions();
+ ASSERT_NO_THROW(subscriptions.at(shortFormSubscriptionId));
+ ASSERT_EQ(subscriptions.at(shortFormSubscriptionId).size(), 1);
+ ASSERT_NO_THROW(subscriptions.at(longFormSubscriptionId));
+ ASSERT_EQ(subscriptions.at(longFormSubscriptionId).size(), 1);
+
+ // Mock the relay response for closing subscriptions.
+ EXPECT_CALL(*mockClient, send(HasSubstr("CLOSE"), _))
+ .Times(2)
+ .WillRepeatedly(Invoke([](string message, string uri)
+ {
+ return make_tuple(uri, true);
+ }));
+
+ // Close all subscriptions maintained by the service.
+ auto remainingSubscriptions = nostrService->closeSubscriptions();
+ ASSERT_TRUE(remainingSubscriptions.empty());
+
+ // Check that all subscriptions have been closed.
+ subscriptions = nostrService->subscriptions();
+ ASSERT_TRUE(subscriptions.empty());
+};
} // namespace nostr_test