- 37개 파일 IP → zioinfo.co.kr 치환 (소스/매뉴얼/설정/하네스) - Manager DrConsole/NetworkConsole/CsapConsole 빌드 + /var/www/manager/ 배포 - 테스트: Manager HTTP 200, ITSM 신규 API 7개 전체 200 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1344 lines
43 KiB
C++
1344 lines
43 KiB
C++
/*
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
#include <folly/Format.h>
|
|
#include <folly/dynamic.h>
|
|
#include <folly/executors/ManualExecutor.h>
|
|
#include <folly/executors/QueuedImmediateExecutor.h>
|
|
#include <folly/json.h>
|
|
#include <gmock/gmock.h>
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <jsinspector-modern/InspectorInterfaces.h>
|
|
#include <jsinspector-modern/InspectorPackagerConnection.h>
|
|
|
|
#include <functional>
|
|
#include <memory>
|
|
|
|
#include "FollyDynamicMatchers.h"
|
|
#include "InspectorMocks.h"
|
|
#include "UniquePtrFactory.h"
|
|
|
|
using namespace ::testing;
|
|
using namespace std::literals::chrono_literals;
|
|
using namespace std::literals::string_literals;
|
|
using folly::dynamic, folly::parseJson, folly::toJson, folly::format,
|
|
folly::sformat;
|
|
|
|
namespace facebook::react::jsinspector_modern {
|
|
|
|
namespace {
|
|
|
|
template <typename Executor>
|
|
class InspectorPackagerConnectionTestBase : public testing::Test {
|
|
protected:
|
|
InspectorPackagerConnectionTestBase()
|
|
: packagerConnection_(InspectorPackagerConnection{
|
|
"ws://mock-host:12345",
|
|
"my-app",
|
|
packagerConnectionDelegates_.make_unique(asyncExecutor_)}) {
|
|
ON_CALL(*packagerConnectionDelegate(), connectWebSocket(_, _))
|
|
.WillByDefault(webSockets_.lazily_make_unique<
|
|
const std::string&,
|
|
std::weak_ptr<IWebSocketDelegate>>());
|
|
}
|
|
|
|
void TearDown() override {
|
|
// Forcibly clean up all pages currently registered with the inspector in
|
|
// order to isolate state between tests. NOTE: Using TearDown instead of a
|
|
// destructor so that we can use FAIL() etc.
|
|
std::vector<int> pagesToRemove;
|
|
auto pages = getInspectorInstance().getPages();
|
|
int liveConnectionCount = 0;
|
|
for (size_t i = 0; i != localConnections_.objectsVended(); ++i) {
|
|
if (localConnections_[i]) {
|
|
liveConnectionCount++;
|
|
// localConnections_[i] is a strict mock and will complain when we
|
|
// removePage if the call is unexpected.
|
|
EXPECT_CALL(*localConnections_[i], disconnect());
|
|
}
|
|
}
|
|
for (auto& page : pages) {
|
|
getInspectorInstance().removePage(page.id);
|
|
}
|
|
if (!pages.empty() && liveConnectionCount) {
|
|
if (!::testing::Test::HasFailure()) {
|
|
FAIL()
|
|
<< "Test case ended with " << liveConnectionCount
|
|
<< " open connection(s) and " << pages.size()
|
|
<< " registered page(s). You must manually call removePage for each page.";
|
|
}
|
|
}
|
|
::testing::Test::TearDown();
|
|
}
|
|
|
|
MockInspectorPackagerConnectionDelegate* packagerConnectionDelegate() {
|
|
// We only create one PackagerConnectionDelegate per test.
|
|
EXPECT_EQ(packagerConnectionDelegates_.objectsVended(), 1);
|
|
return packagerConnectionDelegates_[0];
|
|
}
|
|
|
|
Executor asyncExecutor_;
|
|
|
|
UniquePtrFactory<MockInspectorPackagerConnectionDelegate>
|
|
packagerConnectionDelegates_;
|
|
/**
|
|
* webSockets_ will hold the WebSocket instance(s) owned by
|
|
* packagerConnection_ while also allowing us to access them during
|
|
* the test. We can send messages *to* packagerConnection_ by
|
|
* calling webSockets_[i]->getDelegate().didReceiveMessage(...). Messages
|
|
* *from* packagerConnection_ will be found as calls to
|
|
* webSockets_[i]->send, which is a mock method installed by gmock.
|
|
* These are strict mocks, so method calls will fail if they are not
|
|
* expected with a corresponding call to EXPECT_CALL(...) - for example
|
|
* if unexpected WebSocket messages are sent.
|
|
*/
|
|
UniquePtrFactory<StrictMock<MockWebSocket>> webSockets_;
|
|
/**
|
|
* localConnections_ will hold the LocalConnection instances owned
|
|
* by packagerConnection_ while also allowing us to access them
|
|
* during the test.
|
|
* These are strict mocks, so method calls will fail if they are not
|
|
* expected with a corresponding call to EXPECT_CALL(...).
|
|
*/
|
|
UniquePtrFactory<StrictMock<MockLocalConnection>> localConnections_;
|
|
std::optional<InspectorPackagerConnection> packagerConnection_;
|
|
};
|
|
|
|
using InspectorPackagerConnectionTest =
|
|
InspectorPackagerConnectionTestBase<folly::QueuedImmediateExecutor>;
|
|
|
|
/**
|
|
* Fixture class for tests that run on a ManualExecutor. Work scheduled
|
|
* on the executor is *not* run automatically; it must be manually advanced
|
|
* in the body of the test.
|
|
*/
|
|
class InspectorPackagerConnectionTestAsync
|
|
: public InspectorPackagerConnectionTestBase<folly::ManualExecutor> {
|
|
public:
|
|
virtual void TearDown() override {
|
|
// Assert there are no pending tasks on the ManualExecutor.
|
|
auto tasksCleared = asyncExecutor_.clear();
|
|
EXPECT_EQ(tasksCleared, 0)
|
|
<< "There were still pending tasks on asyncExecutor_ at the end of the test. Use advance() or run() as needed.";
|
|
InspectorPackagerConnectionTestBase<folly::ManualExecutor>::TearDown();
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
TEST_F(InspectorPackagerConnectionTest, TestConnectThenDestroy) {
|
|
packagerConnection_->connect();
|
|
|
|
// The connection should be established immediately.
|
|
ASSERT_TRUE(webSockets_[0]);
|
|
EXPECT_EQ(webSockets_[0]->url, "ws://mock-host:12345");
|
|
EXPECT_TRUE(packagerConnection_->isConnected());
|
|
|
|
// Destroying packagerConnection_ should close the underlying WebSocket (by
|
|
// destroying it).
|
|
packagerConnection_.reset();
|
|
EXPECT_FALSE(webSockets_[0]);
|
|
}
|
|
|
|
TEST_F(InspectorPackagerConnectionTest, TestConnectMultipleTimes) {
|
|
packagerConnection_->connect();
|
|
|
|
packagerConnection_->connect();
|
|
|
|
// The WebSocket gets recreated and the connection is in a valid state.
|
|
EXPECT_FALSE(webSockets_[0]);
|
|
ASSERT_TRUE(webSockets_[1]);
|
|
EXPECT_EQ(webSockets_[1]->url, "ws://mock-host:12345");
|
|
EXPECT_TRUE(packagerConnection_->isConnected());
|
|
|
|
// Destroying packagerConnection_ should close the underlying WebSocket (by
|
|
// destroying it).
|
|
packagerConnection_.reset();
|
|
EXPECT_FALSE(webSockets_[1]);
|
|
}
|
|
|
|
TEST_F(InspectorPackagerConnectionTest, TestCloseQuietly) {
|
|
packagerConnection_->connect();
|
|
|
|
ASSERT_TRUE(webSockets_[0]);
|
|
EXPECT_TRUE(packagerConnection_->isConnected());
|
|
|
|
packagerConnection_->closeQuietly();
|
|
EXPECT_FALSE(packagerConnection_->isConnected());
|
|
EXPECT_FALSE(webSockets_[0]);
|
|
|
|
// Calling closeQuietly again has no effect.
|
|
packagerConnection_->closeQuietly();
|
|
EXPECT_FALSE(packagerConnection_->isConnected());
|
|
EXPECT_FALSE(webSockets_[0]);
|
|
|
|
// Connecting again is a noop (except for logging an error).
|
|
packagerConnection_->connect();
|
|
EXPECT_FALSE(packagerConnection_->isConnected());
|
|
EXPECT_FALSE(webSockets_[0]);
|
|
}
|
|
|
|
TEST_F(InspectorPackagerConnectionTest, TestGetPages) {
|
|
// Configure gmock to expect calls in a specific order.
|
|
InSequence mockCallsMustBeInSequence;
|
|
|
|
packagerConnection_->connect();
|
|
|
|
// The list of pages is empty at first.
|
|
EXPECT_CALL(*webSockets_[0], send(JsonEq(R"({
|
|
"event": "getPages",
|
|
"payload": []
|
|
})")))
|
|
.RetiresOnSaturation();
|
|
webSockets_[0]->getDelegate().didReceiveMessage(R"({
|
|
"event": "getPages"
|
|
})");
|
|
|
|
auto pageId = getInspectorInstance().addPage(
|
|
"mock-title",
|
|
"mock-vm",
|
|
localConnections_
|
|
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>(),
|
|
{.nativePageReloads = true});
|
|
|
|
// getPages now reports the page we registered.
|
|
EXPECT_CALL(
|
|
*webSockets_[0],
|
|
send(JsonParsed(AllOf(
|
|
AtJsonPtr("/event", Eq("getPages")),
|
|
AtJsonPtr(
|
|
"/payload",
|
|
ElementsAreArray({AllOf(
|
|
AtJsonPtr("/app", Eq("my-app")),
|
|
AtJsonPtr("/title", Eq("mock-title [C++ connection]")),
|
|
AtJsonPtr("/vm", Eq("mock-vm")),
|
|
AtJsonPtr("/id", Eq(std::to_string(pageId))),
|
|
AtJsonPtr("/capabilities/nativePageReloads", Eq(true)),
|
|
AtJsonPtr(
|
|
"/capabilities/nativeSourceCodeFetching",
|
|
Eq(false)))}))))))
|
|
.RetiresOnSaturation();
|
|
webSockets_[0]->getDelegate().didReceiveMessage(R"({
|
|
"event": "getPages"
|
|
})");
|
|
|
|
getInspectorInstance().removePage(pageId);
|
|
|
|
// getPages is back to reporting no pages.
|
|
EXPECT_CALL(
|
|
*webSockets_[0],
|
|
send(JsonEq(
|
|
R"({
|
|
"event": "getPages",
|
|
"payload": []
|
|
})")))
|
|
.RetiresOnSaturation();
|
|
webSockets_[0]->getDelegate().didReceiveMessage(R"({
|
|
"event": "getPages"
|
|
})");
|
|
}
|
|
|
|
TEST_F(InspectorPackagerConnectionTest, TestSendReceiveEvents) {
|
|
// Configure gmock to expect calls in a specific order.
|
|
InSequence mockCallsMustBeInSequence;
|
|
|
|
packagerConnection_->connect();
|
|
auto pageId = getInspectorInstance().addPage(
|
|
"mock-title",
|
|
"mock-vm",
|
|
localConnections_
|
|
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
|
|
|
|
// Connect to the page.
|
|
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
|
|
R"({{
|
|
"event": "connect",
|
|
"payload": {{
|
|
"pageId": {0}
|
|
}}
|
|
}})",
|
|
toJson(std::to_string(pageId))));
|
|
ASSERT_TRUE(localConnections_[0]);
|
|
|
|
// Send an event from the mocked backend (local) to the frontend (remote)
|
|
// and observe it being sent via the socket.
|
|
EXPECT_CALL(
|
|
*webSockets_[0],
|
|
send(JsonParsed(AllOf(
|
|
AtJsonPtr("/event", Eq("wrappedEvent")),
|
|
AtJsonPtr("/payload/pageId", Eq(std::to_string(pageId))),
|
|
AtJsonPtr(
|
|
"/payload/wrappedEvent",
|
|
JsonEq(
|
|
R"({
|
|
"method": "FakeDomain.eventTriggered",
|
|
"params": ["arg1", "arg2"]
|
|
})"))))))
|
|
.RetiresOnSaturation();
|
|
localConnections_[0]->getRemoteConnection().onMessage(R"({
|
|
"method": "FakeDomain.eventTriggered",
|
|
"params": ["arg1", "arg2"]
|
|
})");
|
|
|
|
// Send an event from the frontend (remote) to the backend (local) and
|
|
// observe it being received by localConnection.
|
|
EXPECT_CALL(
|
|
*localConnections_[0],
|
|
sendMessage(JsonParsed(AllOf(
|
|
AtJsonPtr("/method", Eq("FakeDomain.fakeMethod")),
|
|
AtJsonPtr("/id", Eq(1234)),
|
|
AtJsonPtr("/params", ElementsAre("arg1", "arg2"))))))
|
|
.RetiresOnSaturation();
|
|
|
|
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
|
|
R"({{
|
|
"event": "wrappedEvent",
|
|
"payload": {{
|
|
"pageId": {0},
|
|
"wrappedEvent": {1}
|
|
}}
|
|
}})",
|
|
toJson(std::to_string(pageId)),
|
|
toJson(R"({
|
|
"method": "FakeDomain.fakeMethod",
|
|
"id": 1234,
|
|
"params": ["arg1", "arg2"]
|
|
})")));
|
|
|
|
// Send a 'disconnect' event from the mocked backend (local) to the frontend
|
|
// (remote) and observe it being sent via the socket.
|
|
EXPECT_CALL(
|
|
*webSockets_[0],
|
|
send(JsonParsed(AllOf(
|
|
AtJsonPtr("/event", Eq("disconnect")),
|
|
AtJsonPtr("/payload/pageId", Eq(std::to_string(pageId)))))))
|
|
.RetiresOnSaturation();
|
|
localConnections_[0]->getRemoteConnection().onDisconnect();
|
|
|
|
EXPECT_CALL(*localConnections_[0], disconnect()).RetiresOnSaturation();
|
|
getInspectorInstance().removePage(pageId);
|
|
}
|
|
|
|
TEST_F(InspectorPackagerConnectionTest, TestSendReceiveEventsToMultiplePages) {
|
|
// Configure gmock to expect calls in a specific order.
|
|
InSequence mockCallsMustBeInSequence;
|
|
|
|
packagerConnection_->connect();
|
|
|
|
std::vector<int> pageIds;
|
|
|
|
const int kNumPages = 2;
|
|
for (int i = 0; i < kNumPages; ++i) {
|
|
pageIds.push_back(getInspectorInstance().addPage(
|
|
"mock-title",
|
|
"mock-vm",
|
|
localConnections_
|
|
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>()));
|
|
if (i > 0) {
|
|
ASSERT_NE(pageIds[i], pageIds[i - 1])
|
|
<< "Received duplicate page IDs from inspector.";
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < kNumPages; ++i) {
|
|
// Connect to the i-th page.
|
|
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
|
|
R"({{
|
|
"event": "connect",
|
|
"payload": {{
|
|
"pageId": {0}
|
|
}}
|
|
}})",
|
|
toJson(std::to_string(pageIds[i]))));
|
|
ASSERT_TRUE(localConnections_[i]);
|
|
}
|
|
|
|
// Send an event from each LocalConnection and observe it being sent via
|
|
// the socket.
|
|
for (int i = 0; i < kNumPages; ++i) {
|
|
// Generate a unique method name for this page to validate that we are
|
|
// routing the events correctly.
|
|
std::string method =
|
|
"FakeDomain.eventFromPage"s + std::to_string(pageIds[i]);
|
|
EXPECT_CALL(
|
|
*webSockets_[0],
|
|
send(JsonParsed(AllOf(
|
|
AtJsonPtr("/event", Eq("wrappedEvent")),
|
|
AtJsonPtr("/payload/pageId", Eq(std::to_string(pageIds[i]))),
|
|
AtJsonPtr(
|
|
"/payload/wrappedEvent",
|
|
JsonParsed(AtJsonPtr("/method", Eq(method))))))))
|
|
.RetiresOnSaturation();
|
|
localConnections_[i]->getRemoteConnection().onMessage(
|
|
toJson(dynamic::object("method", method)));
|
|
}
|
|
|
|
// Send an event from the frontend (remote) to the backend (local) and
|
|
// observe it being received by each LocalConnection.
|
|
for (int i = 0; i < kNumPages; ++i) {
|
|
// Generate a unique method name for this page to validate that we are
|
|
// routing the events correctly.
|
|
std::string method =
|
|
"FakeDomain.methodToPage"s + std::to_string(pageIds[i]);
|
|
EXPECT_CALL(
|
|
*localConnections_[i],
|
|
sendMessage(JsonParsed(AtJsonPtr("/method", Eq(method)))))
|
|
.RetiresOnSaturation();
|
|
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
|
|
R"({{
|
|
"event": "wrappedEvent",
|
|
"payload": {{
|
|
"pageId": {0},
|
|
"wrappedEvent": {1}
|
|
}}
|
|
}})",
|
|
toJson(std::to_string(pageIds[i])),
|
|
toJson(toJson(dynamic::object("method", method)))));
|
|
}
|
|
|
|
for (int i = 0; i < kNumPages; ++i) {
|
|
EXPECT_CALL(*localConnections_[i], disconnect()).RetiresOnSaturation();
|
|
getInspectorInstance().removePage(pageIds[i]);
|
|
}
|
|
}
|
|
|
|
TEST_F(InspectorPackagerConnectionTest, TestSendEventToAllConnections) {
|
|
// Configure gmock to expect calls in a specific order.
|
|
InSequence mockCallsMustBeInSequence;
|
|
|
|
packagerConnection_->connect();
|
|
auto pageId = getInspectorInstance().addPage(
|
|
"mock-title",
|
|
"mock-vm",
|
|
localConnections_
|
|
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
|
|
|
|
// Connect to the page.
|
|
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
|
|
R"({{
|
|
"event": "connect",
|
|
"payload": {{
|
|
"pageId": {0}
|
|
}}
|
|
}})",
|
|
toJson(std::to_string(pageId))));
|
|
ASSERT_TRUE(localConnections_[0]);
|
|
|
|
// Impersonate the frontend (remote) to send a message to all (local)
|
|
// connections.
|
|
EXPECT_CALL(
|
|
*localConnections_[0],
|
|
sendMessage(JsonParsed(AllOf(
|
|
AtJsonPtr("/method", Eq("FakeDomain.fakeMethod")),
|
|
AtJsonPtr("/id", Eq(1234)),
|
|
AtJsonPtr("/params", ElementsAre("arg1", "arg2"))))))
|
|
.RetiresOnSaturation();
|
|
packagerConnection_->sendEventToAllConnections(R"({
|
|
"method": "FakeDomain.fakeMethod",
|
|
"id": 1234,
|
|
"params": ["arg1", "arg2"]
|
|
})");
|
|
|
|
EXPECT_CALL(*localConnections_[0], disconnect()).RetiresOnSaturation();
|
|
getInspectorInstance().removePage(pageId);
|
|
}
|
|
|
|
TEST_F(InspectorPackagerConnectionTest, TestConnectThenDisconnect) {
|
|
// Configure gmock to expect calls in a specific order.
|
|
InSequence mockCallsMustBeInSequence;
|
|
|
|
packagerConnection_->connect();
|
|
auto pageId = getInspectorInstance().addPage(
|
|
"mock-title",
|
|
"mock-vm",
|
|
localConnections_
|
|
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
|
|
|
|
// Connect to the page.
|
|
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
|
|
R"({{
|
|
"event": "connect",
|
|
"payload": {{
|
|
"pageId": {0}
|
|
}}
|
|
}})",
|
|
toJson(std::to_string(pageId))));
|
|
ASSERT_TRUE(localConnections_[0]);
|
|
|
|
// Disconnect from the page.
|
|
EXPECT_CALL(*localConnections_[0], disconnect()).RetiresOnSaturation();
|
|
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
|
|
R"({{
|
|
"event": "disconnect",
|
|
"payload": {{
|
|
"pageId": {0}
|
|
}}
|
|
}})",
|
|
toJson(std::to_string(pageId))));
|
|
EXPECT_FALSE(localConnections_[0]);
|
|
}
|
|
|
|
TEST_F(InspectorPackagerConnectionTest, TestConnectThenCloseSocket) {
|
|
// Configure gmock to expect calls in a specific order.
|
|
InSequence mockCallsMustBeInSequence;
|
|
|
|
packagerConnection_->connect();
|
|
auto pageId = getInspectorInstance().addPage(
|
|
"mock-title",
|
|
"mock-vm",
|
|
localConnections_
|
|
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
|
|
|
|
// Connect to the page.
|
|
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
|
|
R"({{
|
|
"event": "connect",
|
|
"payload": {{
|
|
"pageId": {0}
|
|
}}
|
|
}})",
|
|
toJson(std::to_string(pageId))));
|
|
ASSERT_TRUE(localConnections_[0]);
|
|
|
|
// Notify that the socket was closed.
|
|
EXPECT_CALL(*localConnections_[0], disconnect()).RetiresOnSaturation();
|
|
webSockets_[0]->getDelegate().didClose();
|
|
EXPECT_FALSE(localConnections_[0]);
|
|
}
|
|
|
|
TEST_F(InspectorPackagerConnectionTest, TestConnectThenSocketFailure) {
|
|
// Configure gmock to expect calls in a specific order.
|
|
InSequence mockCallsMustBeInSequence;
|
|
|
|
packagerConnection_->connect();
|
|
auto pageId = getInspectorInstance().addPage(
|
|
"mock-title",
|
|
"mock-vm",
|
|
localConnections_
|
|
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
|
|
|
|
// Connect to the page.
|
|
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
|
|
R"({{
|
|
"event": "connect",
|
|
"payload": {{
|
|
"pageId": {0}
|
|
}}
|
|
}})",
|
|
toJson(std::to_string(pageId))));
|
|
ASSERT_TRUE(localConnections_[0]);
|
|
|
|
// Notify that the socket was closed (implicitly, as the result of an error).
|
|
EXPECT_CALL(*localConnections_[0], disconnect()).RetiresOnSaturation();
|
|
webSockets_[0]->getDelegate().didFailWithError(ECONNABORTED, "Test error");
|
|
EXPECT_FALSE(localConnections_[0]);
|
|
}
|
|
|
|
TEST_F(
|
|
InspectorPackagerConnectionTestAsync,
|
|
TestExplicitCloseAfterSocketFailure) {
|
|
// Configure gmock to expect calls in a specific order.
|
|
InSequence mockCallsMustBeInSequence;
|
|
|
|
packagerConnection_->connect();
|
|
auto pageId = getInspectorInstance().addPage(
|
|
"mock-title",
|
|
"mock-vm",
|
|
localConnections_
|
|
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
|
|
|
|
// Connect to the page.
|
|
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
|
|
R"({{
|
|
"event": "connect",
|
|
"payload": {{
|
|
"pageId": {0}
|
|
}}
|
|
}})",
|
|
toJson(std::to_string(pageId))));
|
|
ASSERT_TRUE(localConnections_[0]);
|
|
|
|
// Notify that the socket was closed (implicitly, as the result of an error).
|
|
EXPECT_CALL(*localConnections_[0], disconnect()).RetiresOnSaturation();
|
|
|
|
{
|
|
// The WebSocket instance gets destroyed during didFailWithError, so extract
|
|
// the delegate in order to call didClose.
|
|
std::shared_ptr webSocketDelegate = webSockets_[0]->delegate.lock();
|
|
|
|
webSocketDelegate->didFailWithError(ECONNABORTED, "Test error");
|
|
webSocketDelegate->didClose();
|
|
}
|
|
|
|
EXPECT_FALSE(localConnections_[0]);
|
|
// We're still disconnected since we haven't called the reconnect callback.
|
|
EXPECT_FALSE(packagerConnection_->isConnected());
|
|
|
|
// Flush the callback queue.
|
|
asyncExecutor_.advance(2000ms);
|
|
|
|
EXPECT_TRUE(packagerConnection_->isConnected());
|
|
}
|
|
|
|
TEST_F(
|
|
InspectorPackagerConnectionTest,
|
|
TestConnectWhileAlreadyConnectedCausesDisconnection) {
|
|
// Configure gmock to expect calls in a specific order.
|
|
InSequence mockCallsMustBeInSequence;
|
|
|
|
packagerConnection_->connect();
|
|
auto pageId = getInspectorInstance().addPage(
|
|
"mock-title",
|
|
"mock-vm",
|
|
localConnections_
|
|
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
|
|
|
|
// Connect to the page.
|
|
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
|
|
R"({{
|
|
"event": "connect",
|
|
"payload": {{
|
|
"pageId": {0}
|
|
}}
|
|
}})",
|
|
toJson(std::to_string(pageId))));
|
|
ASSERT_TRUE(localConnections_[0]);
|
|
|
|
// Try connecting to the same page again. This results in a disconnection.
|
|
EXPECT_CALL(*localConnections_[0], disconnect()).RetiresOnSaturation();
|
|
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
|
|
R"({{
|
|
"event": "connect",
|
|
"payload": {{
|
|
"pageId": {0}
|
|
}}
|
|
}})",
|
|
toJson(std::to_string(pageId))));
|
|
EXPECT_FALSE(localConnections_[0]);
|
|
}
|
|
|
|
TEST_F(InspectorPackagerConnectionTest, TestMultipleDisconnect) {
|
|
// Configure gmock to expect calls in a specific order.
|
|
InSequence mockCallsMustBeInSequence;
|
|
|
|
packagerConnection_->connect();
|
|
auto pageId = getInspectorInstance().addPage(
|
|
"mock-title",
|
|
"mock-vm",
|
|
localConnections_
|
|
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
|
|
|
|
// Connect to the page.
|
|
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
|
|
R"({{
|
|
"event": "connect",
|
|
"payload": {{
|
|
"pageId": {0}
|
|
}}
|
|
}})",
|
|
toJson(std::to_string(pageId))));
|
|
ASSERT_TRUE(localConnections_[0]);
|
|
|
|
// Disconnect from the page.
|
|
EXPECT_CALL(*localConnections_[0], disconnect()).RetiresOnSaturation();
|
|
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
|
|
R"({{
|
|
"event": "disconnect",
|
|
"payload": {{
|
|
"pageId": {0}
|
|
}}
|
|
}})",
|
|
toJson(std::to_string(pageId))));
|
|
EXPECT_FALSE(localConnections_[0]);
|
|
|
|
// Disconnect again. This is a noop.
|
|
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
|
|
R"({{
|
|
"event": "disconnect",
|
|
"payload": {{
|
|
"pageId": {0}
|
|
}}
|
|
}})",
|
|
toJson(std::to_string(pageId))));
|
|
EXPECT_FALSE(localConnections_[0]);
|
|
}
|
|
|
|
TEST_F(InspectorPackagerConnectionTest, TestDisconnectThenSendEvent) {
|
|
// Configure gmock to expect calls in a specific order.
|
|
InSequence mockCallsMustBeInSequence;
|
|
|
|
packagerConnection_->connect();
|
|
auto pageId = getInspectorInstance().addPage(
|
|
"mock-title",
|
|
"mock-vm",
|
|
localConnections_
|
|
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
|
|
|
|
// Connect to the page.
|
|
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
|
|
R"({{
|
|
"event": "connect",
|
|
"payload": {{
|
|
"pageId": {0}
|
|
}}
|
|
}})",
|
|
toJson(std::to_string(pageId))));
|
|
ASSERT_TRUE(localConnections_[0]);
|
|
|
|
// Disconnect from the page.
|
|
EXPECT_CALL(*localConnections_[0], disconnect()).RetiresOnSaturation();
|
|
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
|
|
R"({{
|
|
"event": "disconnect",
|
|
"payload": {{
|
|
"pageId": {0}
|
|
}}
|
|
}})",
|
|
toJson(std::to_string(pageId))));
|
|
EXPECT_FALSE(localConnections_[0]);
|
|
|
|
// Send an event from the frontend (remote) to the backend (local). This
|
|
// is a noop.
|
|
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
|
|
R"({{
|
|
"event": "wrappedEvent",
|
|
"payload": {{
|
|
"pageId": {0},
|
|
"wrappedEvent": {1}
|
|
}}
|
|
}})",
|
|
toJson(std::to_string(pageId)),
|
|
toJson(R"({
|
|
"method": "FakeDomain.fakeMethod",
|
|
"id": 1234,
|
|
"params": ["arg1", "arg2"]
|
|
})")));
|
|
}
|
|
|
|
TEST_F(InspectorPackagerConnectionTest, TestSendEventToUnknownPage) {
|
|
// Configure gmock to expect calls in a specific order.
|
|
InSequence mockCallsMustBeInSequence;
|
|
|
|
packagerConnection_->connect();
|
|
|
|
// Send an event from the frontend (remote) to the backend (local). This
|
|
// is a noop (except for logging).
|
|
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
|
|
R"({{
|
|
"event": "wrappedEvent",
|
|
"payload": {{
|
|
"pageId": "1234",
|
|
"wrappedEvent": {0}
|
|
}}
|
|
}})",
|
|
toJson(R"({
|
|
"method": "FakeDomain.fakeMethod",
|
|
"id": 1234,
|
|
"params": ["arg1", "arg2"]
|
|
})")));
|
|
}
|
|
|
|
TEST_F(InspectorPackagerConnectionTest, TestReconnectSuccessful) {
|
|
// Configure gmock to expect calls in a specific order.
|
|
InSequence mockCallsMustBeInSequence;
|
|
|
|
packagerConnection_->connect();
|
|
ASSERT_TRUE(webSockets_[0]);
|
|
EXPECT_CALL(*packagerConnectionDelegate(), scheduleCallback(_, _))
|
|
.RetiresOnSaturation();
|
|
webSockets_[0]->getDelegate().didClose();
|
|
EXPECT_FALSE(webSockets_[0]);
|
|
EXPECT_TRUE(webSockets_[1]);
|
|
EXPECT_TRUE(packagerConnection_->isConnected());
|
|
|
|
// Stops attempting to reconnect after closeQuietly
|
|
|
|
packagerConnection_->closeQuietly();
|
|
}
|
|
|
|
TEST_F(InspectorPackagerConnectionTest, TestReconnectFailure) {
|
|
// Configure gmock to expect calls in a specific order.
|
|
InSequence mockCallsMustBeInSequence;
|
|
|
|
packagerConnection_->connect();
|
|
ASSERT_TRUE(webSockets_[0]);
|
|
EXPECT_CALL(*packagerConnectionDelegate(), scheduleCallback(_, _))
|
|
.Times(2)
|
|
.RetiresOnSaturation();
|
|
|
|
webSockets_[0]->getDelegate().didClose();
|
|
EXPECT_FALSE(webSockets_[0]);
|
|
ASSERT_TRUE(webSockets_[1]);
|
|
webSockets_[1]->getDelegate().didClose();
|
|
EXPECT_FALSE(webSockets_[1]);
|
|
ASSERT_TRUE(webSockets_[2]);
|
|
EXPECT_TRUE(packagerConnection_->isConnected());
|
|
|
|
// Stops attempting to reconnect after closeQuietly
|
|
|
|
packagerConnection_->closeQuietly();
|
|
|
|
EXPECT_FALSE(webSockets_[2]);
|
|
EXPECT_FALSE(packagerConnection_->isConnected());
|
|
}
|
|
|
|
TEST_F(InspectorPackagerConnectionTest, TestReconnectOnSocketError) {
|
|
// Configure gmock to expect calls in a specific order.
|
|
InSequence mockCallsMustBeInSequence;
|
|
|
|
packagerConnection_->connect();
|
|
ASSERT_TRUE(webSockets_[0]);
|
|
EXPECT_CALL(*packagerConnectionDelegate(), scheduleCallback(_, _))
|
|
.RetiresOnSaturation();
|
|
webSockets_[0]->getDelegate().didFailWithError(ECONNRESET, "Test error");
|
|
EXPECT_FALSE(webSockets_[0]);
|
|
EXPECT_TRUE(webSockets_[1]);
|
|
EXPECT_TRUE(packagerConnection_->isConnected());
|
|
|
|
// Stops attempting to reconnect after closeQuietly
|
|
|
|
packagerConnection_->closeQuietly();
|
|
}
|
|
|
|
TEST_F(InspectorPackagerConnectionTest, TestReconnectOnSocketErrorWithNoCode) {
|
|
// Configure gmock to expect calls in a specific order.
|
|
InSequence mockCallsMustBeInSequence;
|
|
|
|
packagerConnection_->connect();
|
|
ASSERT_TRUE(webSockets_[0]);
|
|
EXPECT_CALL(*packagerConnectionDelegate(), scheduleCallback(_, _))
|
|
.RetiresOnSaturation();
|
|
webSockets_[0]->getDelegate().didFailWithError(std::nullopt, "Test error");
|
|
EXPECT_FALSE(webSockets_[0]);
|
|
EXPECT_TRUE(webSockets_[1]);
|
|
EXPECT_TRUE(packagerConnection_->isConnected());
|
|
|
|
// Stops attempting to reconnect after closeQuietly
|
|
|
|
packagerConnection_->closeQuietly();
|
|
}
|
|
|
|
TEST_F(InspectorPackagerConnectionTest, TestNoReconnectOnConnectionRefused) {
|
|
// Configure gmock to expect calls in a specific order.
|
|
InSequence mockCallsMustBeInSequence;
|
|
|
|
packagerConnection_->connect();
|
|
ASSERT_TRUE(webSockets_[0]);
|
|
webSockets_[0]->getDelegate().didFailWithError(ECONNREFUSED, "Test error");
|
|
EXPECT_FALSE(webSockets_[0]);
|
|
EXPECT_FALSE(packagerConnection_->isConnected());
|
|
}
|
|
|
|
TEST_F(InspectorPackagerConnectionTest, TestUnknownEvent) {
|
|
packagerConnection_->connect();
|
|
ASSERT_TRUE(webSockets_[0]);
|
|
|
|
// This is a noop (other than logging an error).
|
|
webSockets_[0]->getDelegate().didReceiveMessage(R"({"event": "foo"})");
|
|
}
|
|
|
|
TEST_F(InspectorPackagerConnectionTest, TestMalformedEvent) {
|
|
packagerConnection_->connect();
|
|
ASSERT_TRUE(webSockets_[0]);
|
|
|
|
// This is a noop (other than logging an error).
|
|
webSockets_[0]->getDelegate().didReceiveMessage("this is not json");
|
|
webSockets_[0]->getDelegate().didReceiveMessage("{");
|
|
webSockets_[0]->getDelegate().didReceiveMessage("");
|
|
}
|
|
|
|
TEST_F(InspectorPackagerConnectionTest, TestEventsNotConformingToType) {
|
|
packagerConnection_->connect();
|
|
ASSERT_TRUE(webSockets_[0]);
|
|
|
|
// These are all noops (other than logging an error).
|
|
webSockets_[0]->getDelegate().didReceiveMessage(R"({})");
|
|
webSockets_[0]->getDelegate().didReceiveMessage(
|
|
R"({"event": "wrappedEvent"})");
|
|
webSockets_[0]->getDelegate().didReceiveMessage(R"({"event": "connect"})");
|
|
webSockets_[0]->getDelegate().didReceiveMessage(R"({"event": "disconnect"})");
|
|
webSockets_[0]->getDelegate().didReceiveMessage(
|
|
R"({"payload": {"pageId": "1"}})");
|
|
}
|
|
|
|
TEST_F(
|
|
InspectorPackagerConnectionTest,
|
|
TestWebSocketDelegateIsDestroyedWithConnectionByDefault) {
|
|
packagerConnection_->connect();
|
|
ASSERT_TRUE(webSockets_[0]);
|
|
|
|
std::weak_ptr delegate = webSockets_[0]->delegate;
|
|
EXPECT_TRUE(delegate.lock());
|
|
|
|
packagerConnection_.reset();
|
|
EXPECT_FALSE(delegate.lock());
|
|
}
|
|
|
|
// Edge case: When the C++ layer has released the InspectorPackagerConnection,
|
|
// the platform bindings can still call methods on IWebSocketDelegate through
|
|
// a shared_ptr (typically _briefly_ upgraded from the weak_ptr we provide).
|
|
TEST_F(
|
|
InspectorPackagerConnectionTest,
|
|
TestWebSocketDelegateCanOutliveConnection) {
|
|
// Configure gmock to expect calls in a specific order.
|
|
InSequence mockCallsMustBeInSequence;
|
|
|
|
packagerConnection_->connect();
|
|
ASSERT_TRUE(webSockets_[0]);
|
|
|
|
std::shared_ptr retainedWebSocketDelegate = webSockets_[0]->delegate.lock();
|
|
ASSERT_TRUE(retainedWebSocketDelegate);
|
|
|
|
// Destroy our InspectorPackagerConnection. We can't call methods on it
|
|
// anymore, but its internals are still valid and it is still responding to
|
|
// socket messages.
|
|
packagerConnection_.reset();
|
|
|
|
auto pageId = getInspectorInstance().addPage(
|
|
"mock-title",
|
|
"mock-vm",
|
|
localConnections_
|
|
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
|
|
|
|
// Connect to the page.
|
|
retainedWebSocketDelegate->didReceiveMessage(sformat(
|
|
R"({{
|
|
"event": "connect",
|
|
"payload": {{
|
|
"pageId": {0}
|
|
}}
|
|
}})",
|
|
toJson(std::to_string(pageId))));
|
|
ASSERT_TRUE(localConnections_[0]);
|
|
|
|
// Send an event from the frontend (remote) to the backend (local) and
|
|
// observe it being received by localConnection.
|
|
EXPECT_CALL(
|
|
*localConnections_[0],
|
|
sendMessage(JsonParsed(AllOf(
|
|
AtJsonPtr("/method", Eq("FakeDomain.fakeMethod")),
|
|
AtJsonPtr("/id", Eq(1234)),
|
|
AtJsonPtr("/params", ElementsAre("arg1", "arg2"))))))
|
|
.RetiresOnSaturation();
|
|
|
|
retainedWebSocketDelegate->didReceiveMessage(sformat(
|
|
R"({{
|
|
"event": "wrappedEvent",
|
|
"payload": {{
|
|
"pageId": {0},
|
|
"wrappedEvent": {1}
|
|
}}
|
|
}})",
|
|
toJson(std::to_string(pageId)),
|
|
toJson(R"({
|
|
"method": "FakeDomain.fakeMethod",
|
|
"id": 1234,
|
|
"params": ["arg1", "arg2"]
|
|
})")));
|
|
|
|
retainedWebSocketDelegate.reset();
|
|
EXPECT_FALSE(localConnections_[0]);
|
|
EXPECT_FALSE(webSockets_[0]);
|
|
}
|
|
|
|
TEST_F(InspectorPackagerConnectionTest, TestDestroyConnectionOnPageRemoved) {
|
|
// Configure gmock to expect calls in a specific order.
|
|
InSequence mockCallsMustBeInSequence;
|
|
|
|
packagerConnection_->connect();
|
|
ASSERT_TRUE(webSockets_[0]);
|
|
|
|
auto pageId = getInspectorInstance().addPage(
|
|
"mock-title",
|
|
"mock-vm",
|
|
localConnections_
|
|
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
|
|
|
|
// Connect to the page.
|
|
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
|
|
R"({{
|
|
"event": "connect",
|
|
"payload": {{
|
|
"pageId": {0}
|
|
}}
|
|
}})",
|
|
toJson(std::to_string(pageId))));
|
|
EXPECT_TRUE(localConnections_[0]);
|
|
|
|
// Remove the page.
|
|
EXPECT_CALL(*localConnections_[0], disconnect()).RetiresOnSaturation();
|
|
getInspectorInstance().removePage(pageId);
|
|
EXPECT_FALSE(localConnections_[0]);
|
|
}
|
|
|
|
TEST_F(
|
|
InspectorPackagerConnectionTestAsync,
|
|
TestAttemptSendToRemoteAfterDestroyed) {
|
|
// Configure gmock to expect calls in a specific order.
|
|
InSequence mockCallsMustBeInSequence;
|
|
|
|
packagerConnection_->connect();
|
|
auto pageId = getInspectorInstance().addPage(
|
|
"mock-title",
|
|
"mock-vm",
|
|
localConnections_
|
|
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
|
|
|
|
// Connect to the page.
|
|
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
|
|
R"({{
|
|
"event": "connect",
|
|
"payload": {{
|
|
"pageId": {0}
|
|
}}
|
|
}})",
|
|
toJson(std::to_string(pageId))));
|
|
ASSERT_TRUE(localConnections_[0]);
|
|
|
|
// Send an event from the mocked backend (local) to the frontend (remote)
|
|
// but don't flush the callback queue yet.
|
|
localConnections_[0]->getRemoteConnection().onMessage(R"({
|
|
"method": "FakeDomain.eventTriggered",
|
|
"params": ["arg1", "arg2"]
|
|
})");
|
|
|
|
EXPECT_CALL(*localConnections_[0], disconnect()).RetiresOnSaturation();
|
|
getInspectorInstance().removePage(pageId);
|
|
|
|
packagerConnection_.reset();
|
|
|
|
// Flush the callback queue. This doesn't crash.
|
|
EXPECT_EQ(asyncExecutor_.run(), 1);
|
|
}
|
|
|
|
TEST_F(
|
|
InspectorPackagerConnectionTestAsync,
|
|
TestAttemptSendToStaleRemoteConnection) {
|
|
// Configure gmock to expect calls in a specific order.
|
|
InSequence mockCallsMustBeInSequence;
|
|
|
|
packagerConnection_->connect();
|
|
auto pageId = getInspectorInstance().addPage(
|
|
"mock-title",
|
|
"mock-vm",
|
|
localConnections_
|
|
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
|
|
|
|
// Connect to the page.
|
|
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
|
|
R"({{
|
|
"event": "connect",
|
|
"payload": {{
|
|
"pageId": {0}
|
|
}}
|
|
}})",
|
|
toJson(std::to_string(pageId))));
|
|
ASSERT_TRUE(localConnections_[0]);
|
|
|
|
// Send an event from the mocked backend (local) to the frontend (remote)
|
|
// but don't flush the callback queue yet.
|
|
localConnections_[0]->getRemoteConnection().onMessage(R"({
|
|
"method": "FakeDomain.eventToBeDropped",
|
|
"params": ["arg1", "arg2"]
|
|
})");
|
|
|
|
// Disconnect from the page.
|
|
EXPECT_CALL(*localConnections_[0], disconnect()).RetiresOnSaturation();
|
|
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
|
|
R"({{
|
|
"event": "disconnect",
|
|
"payload": {{
|
|
"pageId": {0}
|
|
}}
|
|
}})",
|
|
toJson(std::to_string(pageId))));
|
|
EXPECT_FALSE(localConnections_[0]);
|
|
|
|
// Connect to the same page again.
|
|
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
|
|
R"({{
|
|
"event": "connect",
|
|
"payload": {{
|
|
"pageId": {0}
|
|
}}
|
|
}})",
|
|
toJson(std::to_string(pageId))));
|
|
|
|
EXPECT_TRUE(localConnections_[1]);
|
|
|
|
// Send an event from the mocked backend (local) to the frontend (remote) over
|
|
// the new connection, then flush the callback queue.
|
|
// Only this event should be sent over the socket.
|
|
EXPECT_CALL(
|
|
*webSockets_[0],
|
|
send(JsonParsed(AllOf(
|
|
AtJsonPtr("/event", Eq("wrappedEvent")),
|
|
AtJsonPtr("/payload/pageId", Eq(std::to_string(pageId))),
|
|
AtJsonPtr(
|
|
"/payload/wrappedEvent",
|
|
JsonEq(
|
|
R"({
|
|
"method": "FakeDomain.eventToBeDelivered",
|
|
"params": ["arg1", "arg2"]
|
|
})"))))))
|
|
.RetiresOnSaturation();
|
|
localConnections_[1]->getRemoteConnection().onMessage(R"({
|
|
"method": "FakeDomain.eventToBeDelivered",
|
|
"params": ["arg1", "arg2"]
|
|
})");
|
|
EXPECT_EQ(asyncExecutor_.run(), 2);
|
|
|
|
// Clean up.
|
|
EXPECT_CALL(*localConnections_[1], disconnect()).RetiresOnSaturation();
|
|
getInspectorInstance().removePage(pageId);
|
|
}
|
|
|
|
TEST_F(
|
|
InspectorPackagerConnectionTestAsync,
|
|
TestAttemptSendToStaleRemoteConnectionWhenRetained) {
|
|
// Configure gmock to expect calls in a specific order.
|
|
InSequence mockCallsMustBeInSequence;
|
|
|
|
packagerConnection_->connect();
|
|
auto pageId = getInspectorInstance().addPage(
|
|
"mock-title",
|
|
"mock-vm",
|
|
localConnections_
|
|
.lazily_make_unique<std::unique_ptr<IRemoteConnection>>());
|
|
|
|
// Connect to the page.
|
|
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
|
|
R"({{
|
|
"event": "connect",
|
|
"payload": {{
|
|
"pageId": {0}
|
|
}}
|
|
}})",
|
|
toJson(std::to_string(pageId))));
|
|
ASSERT_TRUE(localConnections_[0]);
|
|
|
|
// Send an event from the mocked backend (local) to the frontend (remote)
|
|
// but don't flush the callback queue yet.
|
|
localConnections_[0]->getRemoteConnection().onMessage(R"({
|
|
"method": "FakeDomain.eventToBeDropped",
|
|
"params": ["arg1", "arg2"]
|
|
})");
|
|
|
|
// Forcibly retain the remote connection beyond localConnections_[0]'s
|
|
// lifetime.
|
|
auto retainedRemoteConnection0 =
|
|
localConnections_[0]->dangerouslyReleaseRemoteConnection();
|
|
|
|
// Disconnect from the page.
|
|
EXPECT_CALL(*localConnections_[0], disconnect()).RetiresOnSaturation();
|
|
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
|
|
R"({{
|
|
"event": "disconnect",
|
|
"payload": {{
|
|
"pageId": {0}
|
|
}}
|
|
}})",
|
|
toJson(std::to_string(pageId))));
|
|
EXPECT_FALSE(localConnections_[0]);
|
|
|
|
// Connect to the same page again.
|
|
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
|
|
R"({{
|
|
"event": "connect",
|
|
"payload": {{
|
|
"pageId": {0}
|
|
}}
|
|
}})",
|
|
toJson(std::to_string(pageId))));
|
|
|
|
EXPECT_TRUE(localConnections_[1]);
|
|
|
|
// Remember localConnections_[0]'s remote connection? We can still use it
|
|
// without crashing, but it will not deliver any messages.
|
|
retainedRemoteConnection0->onMessage(R"({
|
|
"method": "FakeDomain.anotherEventToBeDropped",
|
|
"params": ["arg1", "arg2"]
|
|
})");
|
|
retainedRemoteConnection0->onDisconnect();
|
|
|
|
// Send events from the mocked backend (local) to the frontend (remote) over
|
|
// the new connection, then flush the callback queue.
|
|
// Only these events should be sent over the socket.
|
|
EXPECT_CALL(
|
|
*webSockets_[0],
|
|
send(JsonParsed(AllOf(
|
|
AtJsonPtr("/event", Eq("wrappedEvent")),
|
|
AtJsonPtr("/payload/pageId", Eq(std::to_string(pageId))),
|
|
AtJsonPtr(
|
|
"/payload/wrappedEvent",
|
|
JsonEq(
|
|
R"({
|
|
"method": "FakeDomain.eventToBeDelivered",
|
|
"params": ["arg1", "arg2"]
|
|
})"))))))
|
|
.RetiresOnSaturation();
|
|
localConnections_[1]->getRemoteConnection().onMessage(R"({
|
|
"method": "FakeDomain.eventToBeDelivered",
|
|
"params": ["arg1", "arg2"]
|
|
})");
|
|
EXPECT_CALL(
|
|
*webSockets_[0],
|
|
send(JsonParsed(AllOf(
|
|
AtJsonPtr("/event", Eq("disconnect")),
|
|
AtJsonPtr("/payload/pageId", Eq(std::to_string(pageId)))))))
|
|
.RetiresOnSaturation();
|
|
localConnections_[1]->getRemoteConnection().onDisconnect();
|
|
|
|
EXPECT_EQ(asyncExecutor_.run(), 5);
|
|
|
|
// Clean up.
|
|
EXPECT_CALL(*localConnections_[1], disconnect()).RetiresOnSaturation();
|
|
getInspectorInstance().removePage(pageId);
|
|
}
|
|
|
|
TEST_F(InspectorPackagerConnectionTest, TestRejectedPageConnection) {
|
|
// Configure gmock to expect calls in a specific order.
|
|
InSequence mockCallsMustBeInSequence;
|
|
|
|
enum {
|
|
Accept,
|
|
RejectSilently,
|
|
RejectWithDisconnect
|
|
} mockNextConnectionBehavior;
|
|
|
|
auto pageId = getInspectorInstance().addPage(
|
|
"mock-title",
|
|
"mock-vm",
|
|
[&mockNextConnectionBehavior,
|
|
this](auto remoteConnection) -> std::unique_ptr<ILocalConnection> {
|
|
switch (mockNextConnectionBehavior) {
|
|
case Accept:
|
|
return localConnections_.make_unique(std::move(remoteConnection));
|
|
case RejectSilently:
|
|
return nullptr;
|
|
case RejectWithDisconnect:
|
|
remoteConnection->onDisconnect();
|
|
return nullptr;
|
|
}
|
|
});
|
|
|
|
packagerConnection_->connect();
|
|
|
|
ASSERT_TRUE(webSockets_[0]);
|
|
|
|
// Reject the connection by returning nullptr.
|
|
mockNextConnectionBehavior = RejectSilently;
|
|
|
|
EXPECT_CALL(
|
|
*webSockets_[0],
|
|
send(JsonParsed(AllOf(
|
|
AtJsonPtr("/event", Eq("disconnect")),
|
|
AtJsonPtr("/payload/pageId", Eq(std::to_string(pageId)))))))
|
|
.RetiresOnSaturation();
|
|
|
|
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
|
|
R"({{
|
|
"event": "connect",
|
|
"payload": {{
|
|
"pageId": {0}
|
|
}}
|
|
}})",
|
|
toJson(std::to_string(pageId))));
|
|
|
|
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
|
|
R"({{
|
|
"event": "wrappedEvent",
|
|
"payload": {{
|
|
"pageId": {0},
|
|
"wrappedEvent": {1}
|
|
}}
|
|
}})",
|
|
toJson(std::to_string(pageId)),
|
|
toJson(R"({
|
|
"method": "FakeDomain.fakeMethod",
|
|
"id": 1,
|
|
"params": ["arg1", "arg2"]
|
|
})")));
|
|
|
|
// Reject the connection by explicitly calling onDisconnect(), then returning
|
|
// nullptr.
|
|
mockNextConnectionBehavior = RejectWithDisconnect;
|
|
|
|
EXPECT_CALL(
|
|
*webSockets_[0],
|
|
send(JsonParsed(AllOf(
|
|
AtJsonPtr("/event", Eq("disconnect")),
|
|
AtJsonPtr("/payload/pageId", Eq(std::to_string(pageId)))))))
|
|
.RetiresOnSaturation();
|
|
|
|
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
|
|
R"({{
|
|
"event": "connect",
|
|
"payload": {{
|
|
"pageId": {0}
|
|
}}
|
|
}})",
|
|
toJson(std::to_string(pageId))));
|
|
|
|
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
|
|
R"({{
|
|
"event": "wrappedEvent",
|
|
"payload": {{
|
|
"pageId": {0},
|
|
"wrappedEvent": {1}
|
|
}}
|
|
}})",
|
|
toJson(std::to_string(pageId)),
|
|
toJson(R"({
|
|
"method": "FakeDomain.fakeMethod",
|
|
"id": 2,
|
|
"params": ["arg1", "arg2"]
|
|
})")));
|
|
|
|
// Accept a connection after previously rejecting connections to the same
|
|
// page.
|
|
mockNextConnectionBehavior = Accept;
|
|
|
|
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
|
|
R"({{
|
|
"event": "connect",
|
|
"payload": {{
|
|
"pageId": {0}
|
|
}}
|
|
}})",
|
|
toJson(std::to_string(pageId))));
|
|
|
|
EXPECT_CALL(
|
|
*localConnections_[0],
|
|
sendMessage(JsonParsed(AllOf(
|
|
AtJsonPtr("/method", Eq("FakeDomain.fakeMethod")),
|
|
AtJsonPtr("/id", Eq(3)),
|
|
AtJsonPtr("/params", ElementsAre("arg1", "arg2"))))))
|
|
.RetiresOnSaturation();
|
|
|
|
webSockets_[0]->getDelegate().didReceiveMessage(sformat(
|
|
R"({{
|
|
"event": "wrappedEvent",
|
|
"payload": {{
|
|
"pageId": {0},
|
|
"wrappedEvent": {1}
|
|
}}
|
|
}})",
|
|
toJson(std::to_string(pageId)),
|
|
toJson(R"({
|
|
"method": "FakeDomain.fakeMethod",
|
|
"id": 3,
|
|
"params": ["arg1", "arg2"]
|
|
})")));
|
|
|
|
EXPECT_CALL(*localConnections_[0], disconnect()).RetiresOnSaturation();
|
|
getInspectorInstance().removePage(pageId);
|
|
}
|
|
|
|
} // namespace facebook::react::jsinspector_modern
|