From 0d87b4053983ec8edaff5b73491b717866876586 Mon Sep 17 00:00:00 2001 From: Michael J <37635304+buttercat1791@users.noreply.github.com> Date: Sun, 3 Mar 2024 11:56:37 -0600 Subject: Create Nostr Service and Add Write Capabilities (#1) --- test/nostr_service_test.cpp | 404 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 404 insertions(+) create mode 100644 test/nostr_service_test.cpp (limited to 'test') diff --git a/test/nostr_service_test.cpp b/test/nostr_service_test.cpp new file mode 100644 index 0000000..83de3be --- /dev/null +++ b/test/nostr_service_test.cpp @@ -0,0 +1,404 @@ +#include +#include +#include +#include +#include + +#include +#include + +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; + +namespace nostr_test +{ +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), send, (std::string message, std::string uri), (override)); + MOCK_METHOD(void, closeConnection, (std::string uri), (override)); +}; + +class NostrServiceTest : public testing::Test +{ +public: + inline static const nostr::RelayList defaultTestRelays = + { + "wss://relay.damus.io", + "wss://nostr.thesamecat.io" + }; + +protected: + shared_ptr> testAppender; + shared_ptr testClient; + + void SetUp() override + { + testAppender = make_shared>(); + testClient = make_shared(); + }; +}; + +TEST_F(NostrServiceTest, Constructor_StartsClient) +{ + EXPECT_CALL(*testClient, start()).Times(1); + + auto nostrService = new nostr::NostrService(testAppender.get(), testClient.get()); +}; + +TEST_F(NostrServiceTest, Constructor_InitializesService_WithNoDefaultRelays) +{ + auto nostrService = new nostr::NostrService(testAppender.get(), testClient.get()); + auto defaultRelays = nostrService->defaultRelays(); + auto activeRelays = nostrService->activeRelays(); + + ASSERT_EQ(defaultRelays.size(), 0); + ASSERT_EQ(activeRelays.size(), 0); +}; + +TEST_F(NostrServiceTest, Constructor_InitializesService_WithProvidedDefaultRelays) +{ + auto nostrService = new nostr::NostrService(testAppender.get(), testClient.get(), defaultTestRelays); + auto defaultRelays = nostrService->defaultRelays(); + auto activeRelays = nostrService->activeRelays(); + + ASSERT_EQ(defaultRelays.size(), defaultTestRelays.size()); + for (auto relay : defaultRelays) + { + ASSERT_NE(find(defaultTestRelays.begin(), defaultTestRelays.end(), relay), defaultTestRelays.end()); + } + ASSERT_EQ(activeRelays.size(), 0); +}; + +TEST_F(NostrServiceTest, Destructor_StopsClient) +{ + EXPECT_CALL(*testClient, start()).Times(1); + + auto nostrService = new nostr::NostrService(testAppender.get(), testClient.get()); +}; + +TEST_F(NostrServiceTest, OpenRelayConnections_OpensConnections_ToDefaultRelays) +{ + mutex connectionStatusMutex; + auto connectionStatus = make_shared>(); + 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(*testClient, isConnected(_)) + .WillRepeatedly(Invoke([connectionStatus, &connectionStatusMutex](string uri) + { + lock_guard lock(connectionStatusMutex); + bool status = connectionStatus->at(uri); + if (status == false) + { + connectionStatus->at(uri) = true; + } + return status; + })); + + auto nostrService = new nostr::NostrService(testAppender.get(), testClient.get(), defaultTestRelays); + nostrService->openRelayConnections(); + + auto activeRelays = nostrService->activeRelays(); + ASSERT_EQ(activeRelays.size(), defaultTestRelays.size()); + for (auto relay : activeRelays) + { + ASSERT_NE(find(defaultTestRelays.begin(), defaultTestRelays.end(), relay), defaultTestRelays.end()); + } +}; + +TEST_F(NostrServiceTest, OpenRelayConnections_OpensConnections_ToProvidedRelays) +{ + nostr::RelayList testRelays = { "wss://nos.lol" }; + + mutex connectionStatusMutex; + auto connectionStatus = make_shared>(); + 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(*testClient, isConnected(_)) + .WillRepeatedly(Invoke([connectionStatus, &connectionStatusMutex](string uri) + { + lock_guard lock(connectionStatusMutex); + bool status = connectionStatus->at(uri); + if (status == false) + { + connectionStatus->at(uri) = true; + } + return status; + })); + + auto nostrService = new nostr::NostrService(testAppender.get(), testClient.get(), defaultTestRelays); + nostrService->openRelayConnections(testRelays); + + auto activeRelays = nostrService->activeRelays(); + ASSERT_EQ(activeRelays.size(), testRelays.size()); + for (auto relay : activeRelays) + { + ASSERT_NE(find(testRelays.begin(), testRelays.end(), relay), testRelays.end()); + } +}; + +TEST_F(NostrServiceTest, OpenRelayConnections_AddsOpenConnections_ToActiveRelays) +{ + nostr::RelayList testRelays = { "wss://nos.lol" }; + + mutex connectionStatusMutex; + auto connectionStatus = make_shared>(); + connectionStatus->insert({ defaultTestRelays[0], false }); + 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(*testClient, isConnected(_)) + .WillRepeatedly(Invoke([connectionStatus, &connectionStatusMutex](string uri) + { + lock_guard lock(connectionStatusMutex); + bool status = connectionStatus->at(uri); + if (status == false) + { + connectionStatus->at(uri) = true; + } + return status; + })); + + auto nostrService = new nostr::NostrService(testAppender.get(), testClient.get(), defaultTestRelays); + nostrService->openRelayConnections(); + + auto activeRelays = nostrService->activeRelays(); + ASSERT_EQ(activeRelays.size(), defaultTestRelays.size()); + for (auto relay : activeRelays) + { + ASSERT_NE(find(defaultTestRelays.begin(), defaultTestRelays.end(), relay), defaultTestRelays.end()); + } + + nostrService->openRelayConnections(testRelays); + + activeRelays = nostrService->activeRelays(); + ASSERT_EQ(activeRelays.size(), defaultTestRelays.size() + testRelays.size()); + for (auto relay : activeRelays) + { + bool isDefaultRelay = find(defaultTestRelays.begin(), defaultTestRelays.end(), relay) + != defaultTestRelays.end(); + bool isTestRelay = find(testRelays.begin(), testRelays.end(), relay) + != testRelays.end(); + ASSERT_TRUE(isDefaultRelay || isTestRelay); + } +}; + +TEST_F(NostrServiceTest, CloseRelayConnections_ClosesConnections_ToActiveRelays) +{ + mutex connectionStatusMutex; + auto connectionStatus = make_shared>(); + connectionStatus->insert({ defaultTestRelays[0], false }); + connectionStatus->insert({ defaultTestRelays[1], false }); + + EXPECT_CALL(*testClient, isConnected(_)) + .WillRepeatedly(Invoke([connectionStatus, &connectionStatusMutex](string uri) + { + lock_guard lock(connectionStatusMutex); + bool status = connectionStatus->at(uri); + if (status == false) + { + connectionStatus->at(uri) = true; + } + return status; + })); + + auto nostrService = new nostr::NostrService(testAppender.get(), testClient.get(), defaultTestRelays); + nostrService->openRelayConnections(); + + EXPECT_CALL(*testClient, closeConnection(defaultTestRelays[0])).Times(1); + EXPECT_CALL(*testClient, closeConnection(defaultTestRelays[1])).Times(1); + + nostrService->closeRelayConnections(); + + auto activeRelays = nostrService->activeRelays(); + ASSERT_EQ(activeRelays.size(), 0); +}; + +TEST_F(NostrServiceTest, CloseRelayConnections_RemovesClosedConnections_FromActiveRelays) +{ + nostr::RelayList testRelays = { "wss://nos.lol" }; + nostr::RelayList allTestRelays = { defaultTestRelays[0], defaultTestRelays[1], testRelays[0] }; + + mutex connectionStatusMutex; + auto connectionStatus = make_shared>(); + connectionStatus->insert({ defaultTestRelays[0], false }); + connectionStatus->insert({ defaultTestRelays[1], false }); + connectionStatus->insert({ testRelays[0], false }); + + EXPECT_CALL(*testClient, isConnected(_)) + .WillRepeatedly(Invoke([connectionStatus, &connectionStatusMutex](string uri) + { + lock_guard lock(connectionStatusMutex); + bool status = connectionStatus->at(uri); + if (status == false) + { + connectionStatus->at(uri) = true; + } + return status; + })); + + auto nostrService = new nostr::NostrService(testAppender.get(), testClient.get(), allTestRelays); + nostrService->openRelayConnections(); + + EXPECT_CALL(*testClient, closeConnection(testRelays[0])).Times(1); + + nostrService->closeRelayConnections(testRelays); + + auto activeRelays = nostrService->activeRelays(); + ASSERT_EQ(activeRelays.size(), defaultTestRelays.size()); + for (auto relay : activeRelays) + { + bool isDefaultRelay = find(defaultTestRelays.begin(), defaultTestRelays.end(), relay) + != defaultTestRelays.end(); + bool isTestRelay = find(testRelays.begin(), testRelays.end(), relay) + != testRelays.end(); + ASSERT_TRUE((isDefaultRelay || isTestRelay) && !(isDefaultRelay && isTestRelay)); // XOR + } +}; + +TEST_F(NostrServiceTest, PublishEvent_CorrectlyIndicates_AllSuccesses) +{ + mutex connectionStatusMutex; + auto connectionStatus = make_shared>(); + connectionStatus->insert({ defaultTestRelays[0], false }); + connectionStatus->insert({ defaultTestRelays[1], false }); + + EXPECT_CALL(*testClient, isConnected(_)) + .WillRepeatedly(Invoke([connectionStatus, &connectionStatusMutex](string uri) + { + lock_guard lock(connectionStatusMutex); + bool status = connectionStatus->at(uri); + if (status == false) + { + connectionStatus->at(uri) = true; + } + return status; + })); + + auto nostrService = new nostr::NostrService(testAppender.get(), testClient.get(), defaultTestRelays); + nostrService->openRelayConnections(); + + EXPECT_CALL(*testClient, send(_, _)) + .Times(2) + .WillRepeatedly(Invoke([](string message, string uri) + { + return make_tuple(uri, true); + })); + + auto [successes, failures] = nostrService->publishEvent(nostr::Event()); + + ASSERT_EQ(successes.size(), defaultTestRelays.size()); + for (auto relay : successes) + { + ASSERT_NE(find(defaultTestRelays.begin(), defaultTestRelays.end(), relay), defaultTestRelays.end()); + } + + ASSERT_EQ(failures.size(), 0); +}; + +TEST_F(NostrServiceTest, PublishEvent_CorrectlyIndicates_AllFailures) +{ + mutex connectionStatusMutex; + auto connectionStatus = make_shared>(); + connectionStatus->insert({ defaultTestRelays[0], false }); + connectionStatus->insert({ defaultTestRelays[1], false }); + + EXPECT_CALL(*testClient, isConnected(_)) + .WillRepeatedly(Invoke([connectionStatus, &connectionStatusMutex](string uri) + { + lock_guard lock(connectionStatusMutex); + bool status = connectionStatus->at(uri); + if (status == false) + { + connectionStatus->at(uri) = true; + } + return status; + })); + + auto nostrService = new nostr::NostrService(testAppender.get(), testClient.get(), defaultTestRelays); + nostrService->openRelayConnections(); + + EXPECT_CALL(*testClient, send(_, _)) + .Times(2) + .WillRepeatedly(Invoke([](string message, string uri) + { + return make_tuple(uri, false); + })); + + auto [successes, failures] = nostrService->publishEvent(nostr::Event()); + + ASSERT_EQ(successes.size(), 0); + + 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_MixedSuccessesAndFailures) +{ + mutex connectionStatusMutex; + auto connectionStatus = make_shared>(); + connectionStatus->insert({ defaultTestRelays[0], false }); + connectionStatus->insert({ defaultTestRelays[1], false }); + + EXPECT_CALL(*testClient, isConnected(_)) + .WillRepeatedly(Invoke([connectionStatus, &connectionStatusMutex](string uri) + { + lock_guard lock(connectionStatusMutex); + bool status = connectionStatus->at(uri); + if (status == false) + { + connectionStatus->at(uri) = true; + } + return status; + })); + + auto nostrService = new nostr::NostrService(testAppender.get(), testClient.get(), defaultTestRelays); + nostrService->openRelayConnections(); + + EXPECT_CALL(*testClient, send(_, defaultTestRelays[0])) + .Times(1) + .WillRepeatedly(Invoke([](string message, string uri) + { + return make_tuple(uri, true); + })); + EXPECT_CALL(*testClient, send(_, defaultTestRelays[1])) + .Times(1) + .WillRepeatedly(Invoke([](string message, string uri) + { + return make_tuple(uri, false); + })); + + auto [successes, failures] = nostrService->publishEvent(nostr::Event()); + + ASSERT_EQ(successes.size(), 1); + ASSERT_EQ(successes[0], defaultTestRelays[0]); + + ASSERT_EQ(failures.size(), 1); + ASSERT_EQ(failures[0], defaultTestRelays[1]); +}; +} // namespace nostr_test -- cgit