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