xref: /aosp_15_r20/external/openscreen/cast/receiver/application_agent_unittest.cc (revision 3f982cf4871df8771c9d4abe6e9a6f8d829b2736)
1*3f982cf4SFabien Sanglard // Copyright 2020 The Chromium Authors. All rights reserved.
2*3f982cf4SFabien Sanglard // Use of this source code is governed by a BSD-style license that can be
3*3f982cf4SFabien Sanglard // found in the LICENSE file.
4*3f982cf4SFabien Sanglard 
5*3f982cf4SFabien Sanglard #include "cast/receiver/application_agent.h"
6*3f982cf4SFabien Sanglard 
7*3f982cf4SFabien Sanglard #include <iomanip>
8*3f982cf4SFabien Sanglard #include <sstream>
9*3f982cf4SFabien Sanglard #include <string>
10*3f982cf4SFabien Sanglard #include <utility>
11*3f982cf4SFabien Sanglard #include <vector>
12*3f982cf4SFabien Sanglard 
13*3f982cf4SFabien Sanglard #include "cast/common/channel/message_util.h"
14*3f982cf4SFabien Sanglard #include "cast/common/channel/testing/fake_cast_socket.h"
15*3f982cf4SFabien Sanglard #include "cast/common/public/message_port.h"
16*3f982cf4SFabien Sanglard #include "cast/receiver/channel/static_credentials.h"
17*3f982cf4SFabien Sanglard #include "cast/receiver/channel/testing/device_auth_test_helpers.h"
18*3f982cf4SFabien Sanglard #include "gmock/gmock.h"
19*3f982cf4SFabien Sanglard #include "gtest/gtest.h"
20*3f982cf4SFabien Sanglard #include "json/writer.h"  // Included to teach gtest how to pretty-print.
21*3f982cf4SFabien Sanglard #include "platform/api/time.h"
22*3f982cf4SFabien Sanglard #include "platform/test/fake_task_runner.h"
23*3f982cf4SFabien Sanglard #include "platform/test/paths.h"
24*3f982cf4SFabien Sanglard #include "testing/util/read_file.h"
25*3f982cf4SFabien Sanglard #include "util/json/json_serialization.h"
26*3f982cf4SFabien Sanglard 
27*3f982cf4SFabien Sanglard namespace openscreen {
28*3f982cf4SFabien Sanglard namespace cast {
29*3f982cf4SFabien Sanglard namespace {
30*3f982cf4SFabien Sanglard 
31*3f982cf4SFabien Sanglard using ::cast::channel::CastMessage;
32*3f982cf4SFabien Sanglard using ::testing::_;
33*3f982cf4SFabien Sanglard using ::testing::Invoke;
34*3f982cf4SFabien Sanglard using ::testing::Mock;
35*3f982cf4SFabien Sanglard using ::testing::Ne;
36*3f982cf4SFabien Sanglard using ::testing::NiceMock;
37*3f982cf4SFabien Sanglard using ::testing::NotNull;
38*3f982cf4SFabien Sanglard using ::testing::Sequence;
39*3f982cf4SFabien Sanglard using ::testing::StrEq;
40*3f982cf4SFabien Sanglard using ::testing::StrictMock;
41*3f982cf4SFabien Sanglard 
42*3f982cf4SFabien Sanglard // Returns the location of certificate and auth challenge data files for cast
43*3f982cf4SFabien Sanglard // receiver tests.
GetTestDataSubdir()44*3f982cf4SFabien Sanglard std::string GetTestDataSubdir() {
45*3f982cf4SFabien Sanglard   return GetTestDataPath() + "cast/receiver/channel";
46*3f982cf4SFabien Sanglard }
47*3f982cf4SFabien Sanglard 
48*3f982cf4SFabien Sanglard class TestCredentialsProvider final
49*3f982cf4SFabien Sanglard     : public DeviceAuthNamespaceHandler::CredentialsProvider {
50*3f982cf4SFabien Sanglard  public:
TestCredentialsProvider()51*3f982cf4SFabien Sanglard   TestCredentialsProvider() {
52*3f982cf4SFabien Sanglard     const std::string dir = GetTestDataSubdir();
53*3f982cf4SFabien Sanglard     bssl::UniquePtr<X509> parsed_cert;
54*3f982cf4SFabien Sanglard     TrustStore fake_trust_store;
55*3f982cf4SFabien Sanglard     InitStaticCredentialsFromFiles(
56*3f982cf4SFabien Sanglard         &creds_, &parsed_cert, &fake_trust_store, dir + "/device_key.pem",
57*3f982cf4SFabien Sanglard         dir + "/device_chain.pem", dir + "/device_tls.pem");
58*3f982cf4SFabien Sanglard   }
59*3f982cf4SFabien Sanglard 
GetCurrentTlsCertAsDer()60*3f982cf4SFabien Sanglard   absl::Span<const uint8_t> GetCurrentTlsCertAsDer() final {
61*3f982cf4SFabien Sanglard     return absl::Span<uint8_t>(creds_.tls_cert_der);
62*3f982cf4SFabien Sanglard   }
GetCurrentDeviceCredentials()63*3f982cf4SFabien Sanglard   const DeviceCredentials& GetCurrentDeviceCredentials() final {
64*3f982cf4SFabien Sanglard     return creds_.device_creds;
65*3f982cf4SFabien Sanglard   }
66*3f982cf4SFabien Sanglard 
67*3f982cf4SFabien Sanglard  private:
68*3f982cf4SFabien Sanglard   StaticCredentialsProvider creds_;
69*3f982cf4SFabien Sanglard };
70*3f982cf4SFabien Sanglard 
TestAuthChallengeMessage()71*3f982cf4SFabien Sanglard CastMessage TestAuthChallengeMessage() {
72*3f982cf4SFabien Sanglard   CastMessage message;
73*3f982cf4SFabien Sanglard   const auto result = message.ParseFromString(
74*3f982cf4SFabien Sanglard       ReadEntireFileToString(GetTestDataSubdir() + "/auth_challenge.pb"));
75*3f982cf4SFabien Sanglard   OSP_CHECK(result);
76*3f982cf4SFabien Sanglard   return message;
77*3f982cf4SFabien Sanglard }
78*3f982cf4SFabien Sanglard 
79*3f982cf4SFabien Sanglard class FakeApplication : public ApplicationAgent::Application,
80*3f982cf4SFabien Sanglard                         public MessagePort::Client {
81*3f982cf4SFabien Sanglard  public:
FakeApplication(const char * app_id,const char * display_name)82*3f982cf4SFabien Sanglard   FakeApplication(const char* app_id, const char* display_name)
83*3f982cf4SFabien Sanglard       : app_ids_({app_id}), display_name_(display_name) {
84*3f982cf4SFabien Sanglard     OSP_CHECK(app_ids_.front().size() == 8);
85*3f982cf4SFabien Sanglard   }
86*3f982cf4SFabien Sanglard 
87*3f982cf4SFabien Sanglard   // These are called at the end of the Launch() and Stop() methods for
88*3f982cf4SFabien Sanglard   // confirming those methods were called.
89*3f982cf4SFabien Sanglard   MOCK_METHOD(void, DidLaunch, (Json::Value params, MessagePort* port), ());
90*3f982cf4SFabien Sanglard   MOCK_METHOD(void, DidStop, (), ());
91*3f982cf4SFabien Sanglard 
92*3f982cf4SFabien Sanglard   // MessagePort::Client overrides.
93*3f982cf4SFabien Sanglard   MOCK_METHOD(void,
94*3f982cf4SFabien Sanglard               OnMessage,
95*3f982cf4SFabien Sanglard               (const std::string& source_sender_id,
96*3f982cf4SFabien Sanglard                const std::string& message_namespace,
97*3f982cf4SFabien Sanglard                const std::string& message),
98*3f982cf4SFabien Sanglard               (override));
99*3f982cf4SFabien Sanglard   MOCK_METHOD(void, OnError, (Error error), (override));
100*3f982cf4SFabien Sanglard 
GetAppIds() const101*3f982cf4SFabien Sanglard   const std::vector<std::string>& GetAppIds() const override {
102*3f982cf4SFabien Sanglard     return app_ids_;
103*3f982cf4SFabien Sanglard   }
104*3f982cf4SFabien Sanglard 
Launch(const std::string & app_id,const Json::Value & app_params,MessagePort * message_port)105*3f982cf4SFabien Sanglard   bool Launch(const std::string& app_id,
106*3f982cf4SFabien Sanglard               const Json::Value& app_params,
107*3f982cf4SFabien Sanglard               MessagePort* message_port) override {
108*3f982cf4SFabien Sanglard     EXPECT_EQ(GetAppIds().front(), app_id);
109*3f982cf4SFabien Sanglard     EXPECT_TRUE(message_port);
110*3f982cf4SFabien Sanglard     EXPECT_FALSE(is_launched_);
111*3f982cf4SFabien Sanglard     ++session_id_;
112*3f982cf4SFabien Sanglard     is_launched_ = true;
113*3f982cf4SFabien Sanglard     DidLaunch(app_params, message_port);
114*3f982cf4SFabien Sanglard     return true;
115*3f982cf4SFabien Sanglard   }
116*3f982cf4SFabien Sanglard 
GetSessionId()117*3f982cf4SFabien Sanglard   std::string GetSessionId() override {
118*3f982cf4SFabien Sanglard     std::ostringstream oss;
119*3f982cf4SFabien Sanglard     if (is_launched_) {
120*3f982cf4SFabien Sanglard       oss << GetAppIds().front() << "-9ABC-DEF0-1234-";
121*3f982cf4SFabien Sanglard       oss << std::setfill('0') << std::hex << std::setw(12) << session_id_;
122*3f982cf4SFabien Sanglard     }
123*3f982cf4SFabien Sanglard     return oss.str();
124*3f982cf4SFabien Sanglard   }
125*3f982cf4SFabien Sanglard 
GetDisplayName()126*3f982cf4SFabien Sanglard   std::string GetDisplayName() override { return display_name_; }
127*3f982cf4SFabien Sanglard 
GetSupportedNamespaces()128*3f982cf4SFabien Sanglard   std::vector<std::string> GetSupportedNamespaces() override {
129*3f982cf4SFabien Sanglard     return namespaces_;
130*3f982cf4SFabien Sanglard   }
SetSupportedNamespaces(std::vector<std::string> the_namespaces)131*3f982cf4SFabien Sanglard   void SetSupportedNamespaces(std::vector<std::string> the_namespaces) {
132*3f982cf4SFabien Sanglard     namespaces_ = std::move(the_namespaces);
133*3f982cf4SFabien Sanglard   }
134*3f982cf4SFabien Sanglard 
Stop()135*3f982cf4SFabien Sanglard   void Stop() override {
136*3f982cf4SFabien Sanglard     EXPECT_TRUE(is_launched_);
137*3f982cf4SFabien Sanglard     is_launched_ = false;
138*3f982cf4SFabien Sanglard     DidStop();
139*3f982cf4SFabien Sanglard   }
140*3f982cf4SFabien Sanglard 
141*3f982cf4SFabien Sanglard  private:
142*3f982cf4SFabien Sanglard   const std::vector<std::string> app_ids_;
143*3f982cf4SFabien Sanglard   const std::string display_name_;
144*3f982cf4SFabien Sanglard 
145*3f982cf4SFabien Sanglard   std::vector<std::string> namespaces_;
146*3f982cf4SFabien Sanglard 
147*3f982cf4SFabien Sanglard   int session_id_ = 0;
148*3f982cf4SFabien Sanglard   bool is_launched_ = false;
149*3f982cf4SFabien Sanglard };
150*3f982cf4SFabien Sanglard 
151*3f982cf4SFabien Sanglard class ApplicationAgentTest : public ::testing::Test {
152*3f982cf4SFabien Sanglard  public:
ApplicationAgentTest()153*3f982cf4SFabien Sanglard   ApplicationAgentTest() {
154*3f982cf4SFabien Sanglard     EXPECT_CALL(idle_app_, DidLaunch(_, NotNull()));
155*3f982cf4SFabien Sanglard     agent_.RegisterApplication(&idle_app_, true);
156*3f982cf4SFabien Sanglard     Mock::VerifyAndClearExpectations(&idle_app_);
157*3f982cf4SFabien Sanglard 
158*3f982cf4SFabien Sanglard     ConnectAndDoAuth();
159*3f982cf4SFabien Sanglard   }
160*3f982cf4SFabien Sanglard 
~ApplicationAgentTest()161*3f982cf4SFabien Sanglard   ~ApplicationAgentTest() override { EXPECT_CALL(idle_app_, DidStop()); }
162*3f982cf4SFabien Sanglard 
agent()163*3f982cf4SFabien Sanglard   ApplicationAgent* agent() { return &agent_; }
idle_app()164*3f982cf4SFabien Sanglard   StrictMock<FakeApplication>* idle_app() { return &idle_app_; }
165*3f982cf4SFabien Sanglard 
sender_inbound()166*3f982cf4SFabien Sanglard   MockCastSocketClient* sender_inbound() {
167*3f982cf4SFabien Sanglard     return &socket_pair_.mock_peer_client;
168*3f982cf4SFabien Sanglard   }
sender_outbound()169*3f982cf4SFabien Sanglard   CastSocket* sender_outbound() { return socket_pair_.peer_socket.get(); }
170*3f982cf4SFabien Sanglard 
171*3f982cf4SFabien Sanglard   // Examines the |message| for the correct source/destination transport IDs and
172*3f982cf4SFabien Sanglard   // namespace, confirms there is JSON in the payload, and returns parsed JSON
173*3f982cf4SFabien Sanglard   // (or an empty object if the parse fails).
ValidateAndParseMessage(const CastMessage & message,const std::string & from,const std::string & to,const std::string & the_namespace)174*3f982cf4SFabien Sanglard   static Json::Value ValidateAndParseMessage(const CastMessage& message,
175*3f982cf4SFabien Sanglard                                              const std::string& from,
176*3f982cf4SFabien Sanglard                                              const std::string& to,
177*3f982cf4SFabien Sanglard                                              const std::string& the_namespace) {
178*3f982cf4SFabien Sanglard     EXPECT_EQ(from, message.source_id());
179*3f982cf4SFabien Sanglard     EXPECT_EQ(to, message.destination_id());
180*3f982cf4SFabien Sanglard     EXPECT_EQ(the_namespace, message.namespace_());
181*3f982cf4SFabien Sanglard     EXPECT_EQ(::cast::channel::CastMessage_PayloadType_STRING,
182*3f982cf4SFabien Sanglard               message.payload_type());
183*3f982cf4SFabien Sanglard     EXPECT_FALSE(message.payload_utf8().empty());
184*3f982cf4SFabien Sanglard     ErrorOr<Json::Value> parsed = json::Parse(message.payload_utf8());
185*3f982cf4SFabien Sanglard     return parsed.value(Json::Value(Json::objectValue));
186*3f982cf4SFabien Sanglard   }
187*3f982cf4SFabien Sanglard 
188*3f982cf4SFabien Sanglard   // Constructs a CastMessage proto for sending via the CastSocket::Send() API.
MakeCastMessage(const std::string & source_id,const std::string & destination_id,const std::string & the_namespace,const std::string & json)189*3f982cf4SFabien Sanglard   static CastMessage MakeCastMessage(const std::string& source_id,
190*3f982cf4SFabien Sanglard                                      const std::string& destination_id,
191*3f982cf4SFabien Sanglard                                      const std::string& the_namespace,
192*3f982cf4SFabien Sanglard                                      const std::string& json) {
193*3f982cf4SFabien Sanglard     CastMessage message = MakeSimpleUTF8Message(the_namespace, json);
194*3f982cf4SFabien Sanglard     message.set_source_id(source_id);
195*3f982cf4SFabien Sanglard     message.set_destination_id(destination_id);
196*3f982cf4SFabien Sanglard     return message;
197*3f982cf4SFabien Sanglard   }
198*3f982cf4SFabien Sanglard 
199*3f982cf4SFabien Sanglard  private:
200*3f982cf4SFabien Sanglard   // Walk through all the steps to establish a network connection to the
201*3f982cf4SFabien Sanglard   // ApplicationAgent, and test the plumbing for the auth challenge/reply.
ConnectAndDoAuth()202*3f982cf4SFabien Sanglard   void ConnectAndDoAuth() {
203*3f982cf4SFabien Sanglard     static_cast<ReceiverSocketFactory::Client*>(&agent_)->OnConnected(
204*3f982cf4SFabien Sanglard         nullptr, socket_pair_.local_endpoint, std::move(socket_pair_.socket));
205*3f982cf4SFabien Sanglard 
206*3f982cf4SFabien Sanglard     // The remote will send the auth challenge message and get a reply.
207*3f982cf4SFabien Sanglard     EXPECT_CALL(*sender_inbound(), OnMessage(_, _))
208*3f982cf4SFabien Sanglard         .WillOnce(Invoke([](CastSocket*, CastMessage message) {
209*3f982cf4SFabien Sanglard           EXPECT_EQ(kAuthNamespace, message.namespace_());
210*3f982cf4SFabien Sanglard           EXPECT_FALSE(message.payload_binary().empty());
211*3f982cf4SFabien Sanglard         }));
212*3f982cf4SFabien Sanglard     const auto result = sender_outbound()->Send(TestAuthChallengeMessage());
213*3f982cf4SFabien Sanglard     ASSERT_TRUE(result.ok()) << result;
214*3f982cf4SFabien Sanglard     Mock::VerifyAndClearExpectations(sender_inbound());
215*3f982cf4SFabien Sanglard   }
216*3f982cf4SFabien Sanglard 
TearDown()217*3f982cf4SFabien Sanglard   void TearDown() override {
218*3f982cf4SFabien Sanglard     // The ApplicationAgent should send a final "no apps running"
219*3f982cf4SFabien Sanglard     // RECEIVER_STATUS broadcast to the sender at destruction time.
220*3f982cf4SFabien Sanglard     EXPECT_CALL(*sender_inbound(), OnMessage(_, _))
221*3f982cf4SFabien Sanglard         .WillOnce(Invoke([](CastSocket*, CastMessage message) {
222*3f982cf4SFabien Sanglard           constexpr char kExpectedJson[] = R"({
223*3f982cf4SFabien Sanglard             "requestId":0,
224*3f982cf4SFabien Sanglard             "type":"RECEIVER_STATUS",
225*3f982cf4SFabien Sanglard             "status":{
226*3f982cf4SFabien Sanglard               "userEq":{},
227*3f982cf4SFabien Sanglard               "volume":{
228*3f982cf4SFabien Sanglard                 "controlType":"attenuation",
229*3f982cf4SFabien Sanglard                 "level":1.0,
230*3f982cf4SFabien Sanglard                 "muted":false,
231*3f982cf4SFabien Sanglard                 "stepInterval":0.05
232*3f982cf4SFabien Sanglard               }
233*3f982cf4SFabien Sanglard             }
234*3f982cf4SFabien Sanglard           })";
235*3f982cf4SFabien Sanglard           const Json::Value payload = ValidateAndParseMessage(
236*3f982cf4SFabien Sanglard               message, kPlatformReceiverId, kBroadcastId, kReceiverNamespace);
237*3f982cf4SFabien Sanglard           EXPECT_EQ(json::Parse(kExpectedJson).value(), payload);
238*3f982cf4SFabien Sanglard         }));
239*3f982cf4SFabien Sanglard   }
240*3f982cf4SFabien Sanglard 
241*3f982cf4SFabien Sanglard   FakeClock clock_{Clock::time_point() + std::chrono::hours(1)};
242*3f982cf4SFabien Sanglard   FakeTaskRunner task_runner_{&clock_};
243*3f982cf4SFabien Sanglard   FakeCastSocketPair socket_pair_;
244*3f982cf4SFabien Sanglard   StrictMock<FakeApplication> idle_app_{"E8C28D3C", "Backdrop"};
245*3f982cf4SFabien Sanglard   TestCredentialsProvider creds_;
246*3f982cf4SFabien Sanglard   ApplicationAgent agent_{&task_runner_, &creds_};
247*3f982cf4SFabien Sanglard };
248*3f982cf4SFabien Sanglard 
TEST_F(ApplicationAgentTest,JustConnectsWithoutDoingAnything)249*3f982cf4SFabien Sanglard TEST_F(ApplicationAgentTest, JustConnectsWithoutDoingAnything) {}
250*3f982cf4SFabien Sanglard 
TEST_F(ApplicationAgentTest,IgnoresGarbageMessages)251*3f982cf4SFabien Sanglard TEST_F(ApplicationAgentTest, IgnoresGarbageMessages) {
252*3f982cf4SFabien Sanglard   EXPECT_CALL(*sender_inbound(), OnMessage(_, _)).Times(0);
253*3f982cf4SFabien Sanglard 
254*3f982cf4SFabien Sanglard   const char* kGarbageStrings[] = {
255*3f982cf4SFabien Sanglard       "",
256*3f982cf4SFabien Sanglard       R"(naked text)",
257*3f982cf4SFabien Sanglard       R"("")",
258*3f982cf4SFabien Sanglard       R"(123)",
259*3f982cf4SFabien Sanglard       R"("just a string")",
260*3f982cf4SFabien Sanglard       R"([])",
261*3f982cf4SFabien Sanglard       R"({})",
262*3f982cf4SFabien Sanglard       R"({"type":"GET_STATUS"})",  // Note: Missing requestId.
263*3f982cf4SFabien Sanglard   };
264*3f982cf4SFabien Sanglard   for (const char* some_string : kGarbageStrings) {
265*3f982cf4SFabien Sanglard     const auto result = sender_outbound()->Send(
266*3f982cf4SFabien Sanglard         MakeCastMessage(kPlatformSenderId, kPlatformReceiverId,
267*3f982cf4SFabien Sanglard                         kReceiverNamespace, some_string));
268*3f982cf4SFabien Sanglard     ASSERT_TRUE(result.ok()) << result;
269*3f982cf4SFabien Sanglard   }
270*3f982cf4SFabien Sanglard }
271*3f982cf4SFabien Sanglard 
TEST_F(ApplicationAgentTest,HandlesInvalidCommands)272*3f982cf4SFabien Sanglard TEST_F(ApplicationAgentTest, HandlesInvalidCommands) {
273*3f982cf4SFabien Sanglard   EXPECT_CALL(*sender_inbound(), OnMessage(_, _))
274*3f982cf4SFabien Sanglard       .WillOnce(Invoke([&](CastSocket*, CastMessage message) {
275*3f982cf4SFabien Sanglard         constexpr char kExpectedJson[] = R"({
276*3f982cf4SFabien Sanglard           "requestId":3,
277*3f982cf4SFabien Sanglard           "type":"INVALID_REQUEST",
278*3f982cf4SFabien Sanglard           "reason":"INVALID_COMMAND"
279*3f982cf4SFabien Sanglard         })";
280*3f982cf4SFabien Sanglard         const Json::Value payload =
281*3f982cf4SFabien Sanglard             ValidateAndParseMessage(message, kPlatformReceiverId,
282*3f982cf4SFabien Sanglard                                     kPlatformSenderId, kReceiverNamespace);
283*3f982cf4SFabien Sanglard         EXPECT_EQ(json::Parse(kExpectedJson).value(), payload);
284*3f982cf4SFabien Sanglard       }));
285*3f982cf4SFabien Sanglard   auto result = sender_outbound()->Send(MakeCastMessage(
286*3f982cf4SFabien Sanglard       kPlatformSenderId, kPlatformReceiverId, kReceiverNamespace, R"({
287*3f982cf4SFabien Sanglard         "requestId":3,
288*3f982cf4SFabien Sanglard         "type":"FINISH_Q3_OKRS_BY_END_OF_Q3"
289*3f982cf4SFabien Sanglard       })"));
290*3f982cf4SFabien Sanglard   ASSERT_TRUE(result.ok()) << result;
291*3f982cf4SFabien Sanglard }
292*3f982cf4SFabien Sanglard 
TEST_F(ApplicationAgentTest,HandlesPings)293*3f982cf4SFabien Sanglard TEST_F(ApplicationAgentTest, HandlesPings) {
294*3f982cf4SFabien Sanglard   constexpr int kNumPings = 3;
295*3f982cf4SFabien Sanglard 
296*3f982cf4SFabien Sanglard   int num_pongs = 0;
297*3f982cf4SFabien Sanglard   EXPECT_CALL(*sender_inbound(), OnMessage(_, _))
298*3f982cf4SFabien Sanglard       .Times(kNumPings)
299*3f982cf4SFabien Sanglard       .WillRepeatedly(Invoke([&num_pongs](CastSocket*, CastMessage message) {
300*3f982cf4SFabien Sanglard         const Json::Value payload =
301*3f982cf4SFabien Sanglard             ValidateAndParseMessage(message, kPlatformReceiverId,
302*3f982cf4SFabien Sanglard                                     kPlatformSenderId, kHeartbeatNamespace);
303*3f982cf4SFabien Sanglard         EXPECT_EQ(json::Parse(R"({"type":"PONG"})").value(), payload);
304*3f982cf4SFabien Sanglard         ++num_pongs;
305*3f982cf4SFabien Sanglard       }));
306*3f982cf4SFabien Sanglard 
307*3f982cf4SFabien Sanglard   const CastMessage message =
308*3f982cf4SFabien Sanglard       MakeCastMessage(kPlatformSenderId, kPlatformReceiverId,
309*3f982cf4SFabien Sanglard                       kHeartbeatNamespace, R"({"type":"PING"})");
310*3f982cf4SFabien Sanglard   for (int i = 0; i < kNumPings; ++i) {
311*3f982cf4SFabien Sanglard     const auto result = sender_outbound()->Send(message);
312*3f982cf4SFabien Sanglard     ASSERT_TRUE(result.ok()) << result;
313*3f982cf4SFabien Sanglard   }
314*3f982cf4SFabien Sanglard   EXPECT_EQ(kNumPings, num_pongs);
315*3f982cf4SFabien Sanglard }
316*3f982cf4SFabien Sanglard 
TEST_F(ApplicationAgentTest,HandlesGetAppAvailability)317*3f982cf4SFabien Sanglard TEST_F(ApplicationAgentTest, HandlesGetAppAvailability) {
318*3f982cf4SFabien Sanglard   // Send the request before any apps have been registered. Expect an
319*3f982cf4SFabien Sanglard   // "unavailable" response.
320*3f982cf4SFabien Sanglard   EXPECT_CALL(*sender_inbound(), OnMessage(_, _))
321*3f982cf4SFabien Sanglard       .WillOnce(Invoke([&](CastSocket*, CastMessage message) {
322*3f982cf4SFabien Sanglard         constexpr char kExpectedJson[] = R"({
323*3f982cf4SFabien Sanglard           "requestId":548,
324*3f982cf4SFabien Sanglard           "responseType":"GET_APP_AVAILABILITY",
325*3f982cf4SFabien Sanglard           "availability":{"1A2B3C4D":"APP_UNAVAILABLE"}
326*3f982cf4SFabien Sanglard         })";
327*3f982cf4SFabien Sanglard         const Json::Value payload =
328*3f982cf4SFabien Sanglard             ValidateAndParseMessage(message, kPlatformReceiverId,
329*3f982cf4SFabien Sanglard                                     kPlatformSenderId, kReceiverNamespace);
330*3f982cf4SFabien Sanglard         EXPECT_EQ(json::Parse(kExpectedJson).value(), payload);
331*3f982cf4SFabien Sanglard       }));
332*3f982cf4SFabien Sanglard   auto result = sender_outbound()->Send(MakeCastMessage(
333*3f982cf4SFabien Sanglard       kPlatformSenderId, kPlatformReceiverId, kReceiverNamespace, R"({
334*3f982cf4SFabien Sanglard         "requestId":548,
335*3f982cf4SFabien Sanglard         "type":"GET_APP_AVAILABILITY",
336*3f982cf4SFabien Sanglard         "appId":["1A2B3C4D"]
337*3f982cf4SFabien Sanglard       })"));
338*3f982cf4SFabien Sanglard   ASSERT_TRUE(result.ok()) << result;
339*3f982cf4SFabien Sanglard 
340*3f982cf4SFabien Sanglard   // Register an application.
341*3f982cf4SFabien Sanglard   FakeApplication some_app("1A2B3C4D", "Something Doer");
342*3f982cf4SFabien Sanglard   agent()->RegisterApplication(&some_app);
343*3f982cf4SFabien Sanglard 
344*3f982cf4SFabien Sanglard   // Send another request, and expect the application to be available.
345*3f982cf4SFabien Sanglard   EXPECT_CALL(*sender_inbound(), OnMessage(_, _))
346*3f982cf4SFabien Sanglard       .WillOnce(Invoke([&](CastSocket*, CastMessage message) {
347*3f982cf4SFabien Sanglard         constexpr char kExpectedJson[] = R"({
348*3f982cf4SFabien Sanglard           "requestId":549,
349*3f982cf4SFabien Sanglard           "responseType":"GET_APP_AVAILABILITY",
350*3f982cf4SFabien Sanglard           "availability":{"1A2B3C4D":"APP_AVAILABLE"}
351*3f982cf4SFabien Sanglard         })";
352*3f982cf4SFabien Sanglard         const Json::Value payload =
353*3f982cf4SFabien Sanglard             ValidateAndParseMessage(message, kPlatformReceiverId,
354*3f982cf4SFabien Sanglard                                     kPlatformSenderId, kReceiverNamespace);
355*3f982cf4SFabien Sanglard         EXPECT_EQ(json::Parse(kExpectedJson).value(), payload);
356*3f982cf4SFabien Sanglard       }));
357*3f982cf4SFabien Sanglard   result = sender_outbound()->Send(MakeCastMessage(
358*3f982cf4SFabien Sanglard       kPlatformSenderId, kPlatformReceiverId, kReceiverNamespace, R"({
359*3f982cf4SFabien Sanglard         "requestId":549,
360*3f982cf4SFabien Sanglard         "type":"GET_APP_AVAILABILITY",
361*3f982cf4SFabien Sanglard         "appId":["1A2B3C4D"]
362*3f982cf4SFabien Sanglard       })"));
363*3f982cf4SFabien Sanglard   ASSERT_TRUE(result.ok()) << result;
364*3f982cf4SFabien Sanglard 
365*3f982cf4SFabien Sanglard   agent()->UnregisterApplication(&some_app);
366*3f982cf4SFabien Sanglard }
367*3f982cf4SFabien Sanglard 
TEST_F(ApplicationAgentTest,HandlesGetStatus)368*3f982cf4SFabien Sanglard TEST_F(ApplicationAgentTest, HandlesGetStatus) {
369*3f982cf4SFabien Sanglard   EXPECT_CALL(*sender_inbound(), OnMessage(_, _))
370*3f982cf4SFabien Sanglard       .WillOnce(Invoke([&](CastSocket*, CastMessage message) {
371*3f982cf4SFabien Sanglard         constexpr char kExpectedJson[] = R"({
372*3f982cf4SFabien Sanglard           "requestId":123,
373*3f982cf4SFabien Sanglard           "type":"RECEIVER_STATUS",
374*3f982cf4SFabien Sanglard           "status":{
375*3f982cf4SFabien Sanglard             "applications":[
376*3f982cf4SFabien Sanglard               {
377*3f982cf4SFabien Sanglard                 // NOTE: These IDs and the displayName come from |idle_app_|.
378*3f982cf4SFabien Sanglard                 "sessionId":"E8C28D3C-9ABC-DEF0-1234-000000000001",
379*3f982cf4SFabien Sanglard                 "appId":"E8C28D3C",
380*3f982cf4SFabien Sanglard                 "universalAppId":"E8C28D3C",
381*3f982cf4SFabien Sanglard                 "displayName":"Backdrop",
382*3f982cf4SFabien Sanglard                 "isIdleScreen":true,
383*3f982cf4SFabien Sanglard                 "launchedFromCloud":false,
384*3f982cf4SFabien Sanglard                 "namespaces":[]
385*3f982cf4SFabien Sanglard               }
386*3f982cf4SFabien Sanglard             ],
387*3f982cf4SFabien Sanglard             "userEq":{},
388*3f982cf4SFabien Sanglard             "volume":{
389*3f982cf4SFabien Sanglard               "controlType":"attenuation",
390*3f982cf4SFabien Sanglard               "level":1.0,
391*3f982cf4SFabien Sanglard               "muted":false,
392*3f982cf4SFabien Sanglard               "stepInterval":0.05
393*3f982cf4SFabien Sanglard             }
394*3f982cf4SFabien Sanglard           }
395*3f982cf4SFabien Sanglard         })";
396*3f982cf4SFabien Sanglard         const Json::Value payload =
397*3f982cf4SFabien Sanglard             ValidateAndParseMessage(message, kPlatformReceiverId,
398*3f982cf4SFabien Sanglard                                     kPlatformSenderId, kReceiverNamespace);
399*3f982cf4SFabien Sanglard         EXPECT_EQ(json::Parse(kExpectedJson).value(), payload);
400*3f982cf4SFabien Sanglard       }));
401*3f982cf4SFabien Sanglard   auto result = sender_outbound()->Send(MakeCastMessage(
402*3f982cf4SFabien Sanglard       kPlatformSenderId, kPlatformReceiverId, kReceiverNamespace, R"({
403*3f982cf4SFabien Sanglard         "requestId":123,
404*3f982cf4SFabien Sanglard         "type":"GET_STATUS"
405*3f982cf4SFabien Sanglard       })"));
406*3f982cf4SFabien Sanglard   ASSERT_TRUE(result.ok()) << result;
407*3f982cf4SFabien Sanglard }
408*3f982cf4SFabien Sanglard 
TEST_F(ApplicationAgentTest,FailsLaunchRequestWithBadAppID)409*3f982cf4SFabien Sanglard TEST_F(ApplicationAgentTest, FailsLaunchRequestWithBadAppID) {
410*3f982cf4SFabien Sanglard   EXPECT_CALL(*sender_inbound(), OnMessage(_, _))
411*3f982cf4SFabien Sanglard       .WillOnce(Invoke([&](CastSocket*, CastMessage message) {
412*3f982cf4SFabien Sanglard         constexpr char kExpectedJson[] = R"({
413*3f982cf4SFabien Sanglard           "requestId":1,
414*3f982cf4SFabien Sanglard           "type":"LAUNCH_ERROR",
415*3f982cf4SFabien Sanglard           "reason":"NOT_FOUND"
416*3f982cf4SFabien Sanglard         })";
417*3f982cf4SFabien Sanglard         const Json::Value payload =
418*3f982cf4SFabien Sanglard             ValidateAndParseMessage(message, kPlatformReceiverId,
419*3f982cf4SFabien Sanglard                                     kPlatformSenderId, kReceiverNamespace);
420*3f982cf4SFabien Sanglard         EXPECT_EQ(json::Parse(kExpectedJson).value(), payload);
421*3f982cf4SFabien Sanglard       }));
422*3f982cf4SFabien Sanglard   auto launch_result = sender_outbound()->Send(MakeCastMessage(
423*3f982cf4SFabien Sanglard       kPlatformSenderId, kPlatformReceiverId, kReceiverNamespace, R"({
424*3f982cf4SFabien Sanglard         "requestId":1,
425*3f982cf4SFabien Sanglard         "type":"LAUNCH",
426*3f982cf4SFabien Sanglard         "appId":"DEADBEEF"
427*3f982cf4SFabien Sanglard       })"));
428*3f982cf4SFabien Sanglard   ASSERT_TRUE(launch_result.ok()) << launch_result;
429*3f982cf4SFabien Sanglard }
430*3f982cf4SFabien Sanglard 
TEST_F(ApplicationAgentTest,LaunchesApp_PassesMessages_ThenStopsApp)431*3f982cf4SFabien Sanglard TEST_F(ApplicationAgentTest, LaunchesApp_PassesMessages_ThenStopsApp) {
432*3f982cf4SFabien Sanglard   StrictMock<FakeApplication> some_app("1A2B3C4D", "Something Doer");
433*3f982cf4SFabien Sanglard   constexpr char kAppNamespace[] = "urn:x-cast:com.google.cast.something";
434*3f982cf4SFabien Sanglard   some_app.SetSupportedNamespaces({std::string(kAppNamespace)});
435*3f982cf4SFabien Sanglard   agent()->RegisterApplication(&some_app);
436*3f982cf4SFabien Sanglard 
437*3f982cf4SFabien Sanglard   // Phase 1: Sender sends a LAUNCH request, which causes the idle app to stop
438*3f982cf4SFabien Sanglard   // and the receiver app to launch. The receiver (ApplicationAgent) broadcasts
439*3f982cf4SFabien Sanglard   // a RECEIVER_STATUS to indicate the app is running; but the receiver app
440*3f982cf4SFabien Sanglard   // should not get a copy of that.
441*3f982cf4SFabien Sanglard   Sequence phase1;
442*3f982cf4SFabien Sanglard   MessagePort* port_for_app = nullptr;
443*3f982cf4SFabien Sanglard   EXPECT_CALL(*idle_app(), DidStop()).InSequence(phase1);
444*3f982cf4SFabien Sanglard   EXPECT_CALL(some_app, DidLaunch(_, NotNull()))
445*3f982cf4SFabien Sanglard       .InSequence(phase1)
446*3f982cf4SFabien Sanglard       .WillOnce(Invoke([&](Json::Value params, MessagePort* port) {
447*3f982cf4SFabien Sanglard         EXPECT_EQ(json::Parse(R"({"a":1,"b":2})").value(), params);
448*3f982cf4SFabien Sanglard         port_for_app = port;
449*3f982cf4SFabien Sanglard         port->SetClient(&some_app, some_app.GetSessionId());
450*3f982cf4SFabien Sanglard       }));
451*3f982cf4SFabien Sanglard   const std::string kRunningAppReceiverStatus = R"({
452*3f982cf4SFabien Sanglard       "requestId":0,  // Note: 0 for broadcast (no requestor).
453*3f982cf4SFabien Sanglard       "type":"RECEIVER_STATUS",
454*3f982cf4SFabien Sanglard       "status":{
455*3f982cf4SFabien Sanglard         "applications":[
456*3f982cf4SFabien Sanglard           {
457*3f982cf4SFabien Sanglard             // NOTE: These IDs and the displayName come from |some_app|.
458*3f982cf4SFabien Sanglard             "transportId":"1A2B3C4D-9ABC-DEF0-1234-000000000001",
459*3f982cf4SFabien Sanglard             "sessionId":"1A2B3C4D-9ABC-DEF0-1234-000000000001",
460*3f982cf4SFabien Sanglard             "appId":"1A2B3C4D",
461*3f982cf4SFabien Sanglard             "universalAppId":"1A2B3C4D",
462*3f982cf4SFabien Sanglard             "displayName":"Something Doer",
463*3f982cf4SFabien Sanglard             "isIdleScreen":false,
464*3f982cf4SFabien Sanglard             "launchedFromCloud":false,
465*3f982cf4SFabien Sanglard             "namespaces":[{"name":"urn:x-cast:com.google.cast.something"}]
466*3f982cf4SFabien Sanglard           }
467*3f982cf4SFabien Sanglard         ],
468*3f982cf4SFabien Sanglard         "userEq":{},
469*3f982cf4SFabien Sanglard         "volume":{
470*3f982cf4SFabien Sanglard           "controlType":"attenuation",
471*3f982cf4SFabien Sanglard           "level":1.0,
472*3f982cf4SFabien Sanglard           "muted":false,
473*3f982cf4SFabien Sanglard           "stepInterval":0.05
474*3f982cf4SFabien Sanglard         }
475*3f982cf4SFabien Sanglard       }
476*3f982cf4SFabien Sanglard   })";
477*3f982cf4SFabien Sanglard   EXPECT_CALL(*sender_inbound(), OnMessage(_, _))
478*3f982cf4SFabien Sanglard       .InSequence(phase1)
479*3f982cf4SFabien Sanglard       .WillOnce(Invoke([&](CastSocket*, CastMessage message) {
480*3f982cf4SFabien Sanglard         const Json::Value payload = ValidateAndParseMessage(
481*3f982cf4SFabien Sanglard             message, kPlatformReceiverId, kBroadcastId, kReceiverNamespace);
482*3f982cf4SFabien Sanglard         EXPECT_EQ(json::Parse(kRunningAppReceiverStatus).value(), payload);
483*3f982cf4SFabien Sanglard       }));
484*3f982cf4SFabien Sanglard   auto launch_result = sender_outbound()->Send(MakeCastMessage(
485*3f982cf4SFabien Sanglard       kPlatformSenderId, kPlatformReceiverId, kReceiverNamespace, R"({
486*3f982cf4SFabien Sanglard         "requestId":17,
487*3f982cf4SFabien Sanglard         "type":"LAUNCH",
488*3f982cf4SFabien Sanglard         "appId":"1A2B3C4D",
489*3f982cf4SFabien Sanglard         "appParams":{"a":1,"b":2},
490*3f982cf4SFabien Sanglard         "language":"en-US",
491*3f982cf4SFabien Sanglard         "supportedAppTypes":["WEB"]
492*3f982cf4SFabien Sanglard       })"));
493*3f982cf4SFabien Sanglard   ASSERT_TRUE(launch_result.ok()) << launch_result;
494*3f982cf4SFabien Sanglard   Mock::VerifyAndClearExpectations(idle_app());
495*3f982cf4SFabien Sanglard   Mock::VerifyAndClearExpectations(&some_app);
496*3f982cf4SFabien Sanglard   Mock::VerifyAndClearExpectations(sender_inbound());
497*3f982cf4SFabien Sanglard 
498*3f982cf4SFabien Sanglard   // Phase 2: Sender sends a message to the app, and the receiver app sends a
499*3f982cf4SFabien Sanglard   // reply.
500*3f982cf4SFabien Sanglard   constexpr char kMessage[] = R"({"type":"FOO","data":"Hello world!"})";
501*3f982cf4SFabien Sanglard   constexpr char kReplyMessage[] =
502*3f982cf4SFabien Sanglard       R"({"type":"FOO_REPLY","data":"Hi yourself!"})";
503*3f982cf4SFabien Sanglard   constexpr char kSenderTransportId[] = "sender-1";
504*3f982cf4SFabien Sanglard   Sequence phase2;
505*3f982cf4SFabien Sanglard   EXPECT_CALL(some_app, OnMessage(_, _, _))
506*3f982cf4SFabien Sanglard       .InSequence(phase2)
507*3f982cf4SFabien Sanglard       .WillOnce(Invoke([&](const std::string& source_id,
508*3f982cf4SFabien Sanglard                            const std::string& the_namespace,
509*3f982cf4SFabien Sanglard                            const std::string& message) {
510*3f982cf4SFabien Sanglard         EXPECT_EQ(kSenderTransportId, source_id);
511*3f982cf4SFabien Sanglard         EXPECT_EQ(kAppNamespace, the_namespace);
512*3f982cf4SFabien Sanglard         const auto parsed = json::Parse(message);
513*3f982cf4SFabien Sanglard         EXPECT_TRUE(parsed.is_value()) << parsed.error();
514*3f982cf4SFabien Sanglard         if (parsed.is_value()) {
515*3f982cf4SFabien Sanglard           EXPECT_EQ(json::Parse(kMessage).value(), parsed.value());
516*3f982cf4SFabien Sanglard           if (port_for_app) {
517*3f982cf4SFabien Sanglard             port_for_app->PostMessage(kSenderTransportId, kAppNamespace,
518*3f982cf4SFabien Sanglard                                       kReplyMessage);
519*3f982cf4SFabien Sanglard           }
520*3f982cf4SFabien Sanglard         }
521*3f982cf4SFabien Sanglard       }));
522*3f982cf4SFabien Sanglard   EXPECT_CALL(*sender_inbound(), OnMessage(_, _))
523*3f982cf4SFabien Sanglard       .InSequence(phase2)
524*3f982cf4SFabien Sanglard       .WillOnce(Invoke([&](CastSocket*, CastMessage message) {
525*3f982cf4SFabien Sanglard         const Json::Value payload =
526*3f982cf4SFabien Sanglard             ValidateAndParseMessage(message, some_app.GetSessionId(),
527*3f982cf4SFabien Sanglard                                     kSenderTransportId, kAppNamespace);
528*3f982cf4SFabien Sanglard         EXPECT_EQ(json::Parse(kReplyMessage).value(), payload);
529*3f982cf4SFabien Sanglard       }));
530*3f982cf4SFabien Sanglard   auto message_send_result = sender_outbound()->Send(MakeCastMessage(
531*3f982cf4SFabien Sanglard       kSenderTransportId, some_app.GetSessionId(), kAppNamespace, kMessage));
532*3f982cf4SFabien Sanglard   ASSERT_TRUE(message_send_result.ok()) << message_send_result;
533*3f982cf4SFabien Sanglard   Mock::VerifyAndClearExpectations(&some_app);
534*3f982cf4SFabien Sanglard   Mock::VerifyAndClearExpectations(sender_inbound());
535*3f982cf4SFabien Sanglard 
536*3f982cf4SFabien Sanglard   // Phase 3: Sender sends a STOP request, which causes the receiver
537*3f982cf4SFabien Sanglard   // (ApplicationAgent) to stop the app. Then, the idle app will automatically
538*3f982cf4SFabien Sanglard   // be re-launched, and a RECEIVER_STATUS broadcast message will notify the
539*3f982cf4SFabien Sanglard   // sender of that.
540*3f982cf4SFabien Sanglard   Sequence phase3;
541*3f982cf4SFabien Sanglard   EXPECT_CALL(some_app, DidStop()).InSequence(phase3);
542*3f982cf4SFabien Sanglard   EXPECT_CALL(*idle_app(), DidLaunch(_, NotNull())).InSequence(phase3);
543*3f982cf4SFabien Sanglard   EXPECT_CALL(*sender_inbound(), OnMessage(_, _))
544*3f982cf4SFabien Sanglard       .InSequence(phase3)
545*3f982cf4SFabien Sanglard       .WillOnce(Invoke([&](CastSocket*, CastMessage message) {
546*3f982cf4SFabien Sanglard         const std::string kExpectedJson = R"({
547*3f982cf4SFabien Sanglard           "requestId":0,  // Note: 0 for broadcast (no requestor).
548*3f982cf4SFabien Sanglard           "type":"RECEIVER_STATUS",
549*3f982cf4SFabien Sanglard           "status":{
550*3f982cf4SFabien Sanglard             "applications":[
551*3f982cf4SFabien Sanglard               {
552*3f982cf4SFabien Sanglard                 // NOTE: These IDs and the displayName come from |idle_app_|.
553*3f982cf4SFabien Sanglard                 "sessionId":"E8C28D3C-9ABC-DEF0-1234-000000000002",
554*3f982cf4SFabien Sanglard                 "appId":"E8C28D3C",
555*3f982cf4SFabien Sanglard                 "universalAppId":"E8C28D3C",
556*3f982cf4SFabien Sanglard                 "displayName":"Backdrop",
557*3f982cf4SFabien Sanglard                 "isIdleScreen":true,
558*3f982cf4SFabien Sanglard                 "launchedFromCloud":false,
559*3f982cf4SFabien Sanglard                 "namespaces":[]
560*3f982cf4SFabien Sanglard               }
561*3f982cf4SFabien Sanglard             ],
562*3f982cf4SFabien Sanglard             "userEq":{},
563*3f982cf4SFabien Sanglard             "volume":{
564*3f982cf4SFabien Sanglard               "controlType":"attenuation",
565*3f982cf4SFabien Sanglard               "level":1.0,
566*3f982cf4SFabien Sanglard               "muted":false,
567*3f982cf4SFabien Sanglard               "stepInterval":0.05
568*3f982cf4SFabien Sanglard             }
569*3f982cf4SFabien Sanglard           }
570*3f982cf4SFabien Sanglard         })";
571*3f982cf4SFabien Sanglard         const Json::Value payload = ValidateAndParseMessage(
572*3f982cf4SFabien Sanglard             message, kPlatformReceiverId, kBroadcastId, kReceiverNamespace);
573*3f982cf4SFabien Sanglard         EXPECT_EQ(json::Parse(kExpectedJson).value(), payload);
574*3f982cf4SFabien Sanglard       }));
575*3f982cf4SFabien Sanglard   auto stop_result = sender_outbound()->Send(MakeCastMessage(
576*3f982cf4SFabien Sanglard       kPlatformSenderId, kPlatformReceiverId, kReceiverNamespace, R"({
577*3f982cf4SFabien Sanglard     "requestId":18,
578*3f982cf4SFabien Sanglard     "type":"STOP",
579*3f982cf4SFabien Sanglard     "sessionId":"1A2B3C4D-9ABC-DEF0-1234-000000000001"
580*3f982cf4SFabien Sanglard   })"));
581*3f982cf4SFabien Sanglard   ASSERT_TRUE(stop_result.ok()) << stop_result;
582*3f982cf4SFabien Sanglard   Mock::VerifyAndClearExpectations(idle_app());
583*3f982cf4SFabien Sanglard   Mock::VerifyAndClearExpectations(&some_app);
584*3f982cf4SFabien Sanglard   Mock::VerifyAndClearExpectations(sender_inbound());
585*3f982cf4SFabien Sanglard 
586*3f982cf4SFabien Sanglard   agent()->UnregisterApplication(&some_app);
587*3f982cf4SFabien Sanglard }
588*3f982cf4SFabien Sanglard 
TEST_F(ApplicationAgentTest,AllowsVirtualConnectionsToApp)589*3f982cf4SFabien Sanglard TEST_F(ApplicationAgentTest, AllowsVirtualConnectionsToApp) {
590*3f982cf4SFabien Sanglard   NiceMock<FakeApplication> some_app("1A2B3C4D", "Something Doer");
591*3f982cf4SFabien Sanglard   agent()->RegisterApplication(&some_app);
592*3f982cf4SFabien Sanglard 
593*3f982cf4SFabien Sanglard   // Launch the app, using gMock to simulate an app that calls
594*3f982cf4SFabien Sanglard   // MessagePort::SetClient() (to permit messaging) and to get the transport ID
595*3f982cf4SFabien Sanglard   // of the app.
596*3f982cf4SFabien Sanglard   EXPECT_CALL(*idle_app(), DidStop());
597*3f982cf4SFabien Sanglard   EXPECT_CALL(some_app, DidLaunch(_, NotNull()))
598*3f982cf4SFabien Sanglard       .WillOnce(Invoke([&](Json::Value params, MessagePort* port) {
599*3f982cf4SFabien Sanglard         port->SetClient(&some_app, some_app.GetSessionId());
600*3f982cf4SFabien Sanglard       }));
601*3f982cf4SFabien Sanglard   std::string transport_id;
602*3f982cf4SFabien Sanglard   EXPECT_CALL(*sender_inbound(), OnMessage(_, _))
603*3f982cf4SFabien Sanglard       .WillRepeatedly(Invoke([&](CastSocket*, CastMessage message) {
604*3f982cf4SFabien Sanglard         const Json::Value payload = ValidateAndParseMessage(
605*3f982cf4SFabien Sanglard             message, kPlatformReceiverId, kBroadcastId, kReceiverNamespace);
606*3f982cf4SFabien Sanglard         if (payload["type"].asString() == "RECEIVER_STATUS") {
607*3f982cf4SFabien Sanglard           transport_id =
608*3f982cf4SFabien Sanglard               payload["status"]["applications"][0]["transportId"].asString();
609*3f982cf4SFabien Sanglard         }
610*3f982cf4SFabien Sanglard       }));
611*3f982cf4SFabien Sanglard   auto launch_result = sender_outbound()->Send(MakeCastMessage(
612*3f982cf4SFabien Sanglard       kPlatformSenderId, kPlatformReceiverId, kReceiverNamespace, R"({
613*3f982cf4SFabien Sanglard         "requestId":1,
614*3f982cf4SFabien Sanglard         "type":"LAUNCH",
615*3f982cf4SFabien Sanglard         "appId":"1A2B3C4D",
616*3f982cf4SFabien Sanglard         "appParams":{},
617*3f982cf4SFabien Sanglard         "language":"en-US",
618*3f982cf4SFabien Sanglard         "supportedAppTypes":["WEB"]
619*3f982cf4SFabien Sanglard       })"));
620*3f982cf4SFabien Sanglard   ASSERT_TRUE(launch_result.ok()) << launch_result;
621*3f982cf4SFabien Sanglard   Mock::VerifyAndClearExpectations(idle_app());
622*3f982cf4SFabien Sanglard   Mock::VerifyAndClearExpectations(&some_app);
623*3f982cf4SFabien Sanglard   Mock::VerifyAndClearExpectations(sender_inbound());
624*3f982cf4SFabien Sanglard 
625*3f982cf4SFabien Sanglard   // Now that the application has launched, check that the policy allows
626*3f982cf4SFabien Sanglard   // connections to both the ApplicationAgent and the running application.
627*3f982cf4SFabien Sanglard   auto* const policy =
628*3f982cf4SFabien Sanglard       static_cast<ConnectionNamespaceHandler::VirtualConnectionPolicy*>(
629*3f982cf4SFabien Sanglard           agent());
630*3f982cf4SFabien Sanglard   EXPECT_TRUE(policy->IsConnectionAllowed(
631*3f982cf4SFabien Sanglard       VirtualConnection{kPlatformReceiverId, "any-sender-12345", 0}));
632*3f982cf4SFabien Sanglard   ASSERT_FALSE(transport_id.empty());
633*3f982cf4SFabien Sanglard   EXPECT_TRUE(policy->IsConnectionAllowed(
634*3f982cf4SFabien Sanglard       VirtualConnection{transport_id, "any-sender-12345", 0}));
635*3f982cf4SFabien Sanglard   EXPECT_FALSE(policy->IsConnectionAllowed(
636*3f982cf4SFabien Sanglard       VirtualConnection{"wherever i likes", "any-sender-12345", 0}));
637*3f982cf4SFabien Sanglard 
638*3f982cf4SFabien Sanglard   // Unregister the app, which will automatically stop it too.
639*3f982cf4SFabien Sanglard   EXPECT_CALL(some_app, DidStop());
640*3f982cf4SFabien Sanglard   EXPECT_CALL(*idle_app(), DidLaunch(_, NotNull()));
641*3f982cf4SFabien Sanglard   EXPECT_CALL(*sender_inbound(), OnMessage(_, _));  // RECEIVER_STATUS update.
642*3f982cf4SFabien Sanglard   agent()->UnregisterApplication(&some_app);
643*3f982cf4SFabien Sanglard 
644*3f982cf4SFabien Sanglard   // With the app stopped, check that the policy no longer allows connections to
645*3f982cf4SFabien Sanglard   // the now-stale |transport_id|.
646*3f982cf4SFabien Sanglard   EXPECT_TRUE(policy->IsConnectionAllowed(
647*3f982cf4SFabien Sanglard       VirtualConnection{kPlatformReceiverId, "any-sender-12345", 0}));
648*3f982cf4SFabien Sanglard   EXPECT_FALSE(policy->IsConnectionAllowed(
649*3f982cf4SFabien Sanglard       VirtualConnection{transport_id, "any-sender-12345", 0}));
650*3f982cf4SFabien Sanglard   EXPECT_FALSE(policy->IsConnectionAllowed(
651*3f982cf4SFabien Sanglard       VirtualConnection{"wherever i likes", "any-sender-12345", 0}));
652*3f982cf4SFabien Sanglard }
653*3f982cf4SFabien Sanglard 
654*3f982cf4SFabien Sanglard }  // namespace
655*3f982cf4SFabien Sanglard }  // namespace cast
656*3f982cf4SFabien Sanglard }  // namespace openscreen
657