1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "third_party/blink/renderer/modules/presentation/presentation_connection_callbacks.h"
6 
7 #include <utility>
8 
9 #include "mojo/public/cpp/bindings/pending_receiver.h"
10 #include "mojo/public/cpp/bindings/pending_remote.h"
11 #include "testing/gmock/include/gmock/gmock.h"  // MockFunctionScope
12 #include "testing/gtest/include/gtest/gtest.h"
13 #include "third_party/blink/public/mojom/presentation/presentation.mojom-blink.h"
14 #include "third_party/blink/renderer/bindings/core/v8/script_function.h"  // MockFunctionScope
15 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
16 #include "third_party/blink/renderer/bindings/core/v8/script_value.h"  // MockFunctionScope
17 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
18 #include "third_party/blink/renderer/modules/presentation/presentation_connection.h"
19 #include "third_party/blink/renderer/modules/presentation/presentation_request.h"
20 #include "third_party/blink/renderer/platform/bindings/script_state.h"  // MockFunctionScope
21 #include "third_party/blink/renderer/platform/heap/heap.h"
22 #include "third_party/blink/renderer/platform/heap/persistent.h"  // MockFunctionScope
23 #include "third_party/blink/renderer/platform/testing/url_test_helpers.h"
24 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"  // MockFunctionScope
25 #include "third_party/blink/renderer/platform/wtf/vector.h"  // MockFunctionScope
26 
27 constexpr char kPresentationUrl[] = "https://example.com";
28 constexpr char kPresentationId[] = "xyzzy";
29 
30 namespace blink {
31 
32 using mojom::blink::PresentationConnectionResult;
33 using mojom::blink::PresentationConnectionResultPtr;
34 using mojom::blink::PresentationConnectionState;
35 using mojom::blink::PresentationError;
36 using mojom::blink::PresentationErrorType;
37 using mojom::blink::PresentationInfo;
38 using mojom::blink::PresentationInfoPtr;
39 
40 namespace {
41 
42 // TODO(crbug.com/1120218): Consolidate with PaymentRequestMockFunctionScope and
43 // move to shared folder
44 class MockFunctionScope {
45   STACK_ALLOCATED();
46 
47  public:
MockFunctionScope(ScriptState * script_state)48   explicit MockFunctionScope(ScriptState* script_state)
49       : script_state_(script_state) {}
~MockFunctionScope()50   ~MockFunctionScope() {
51     v8::MicrotasksScope::PerformCheckpoint(script_state_->GetIsolate());
52     for (MockFunction* mock_function : mock_functions_)
53       testing::Mock::VerifyAndClearExpectations(mock_function);
54   }
55 
ExpectCall()56   v8::Local<v8::Function> ExpectCall() {
57     mock_functions_.push_back(
58         MakeGarbageCollected<MockFunction>(script_state_));
59     EXPECT_CALL(*mock_functions_.back(), Call(testing::_));
60     return mock_functions_.back()->Bind();
61   }
62 
ExpectNoCall()63   v8::Local<v8::Function> ExpectNoCall() {
64     mock_functions_.push_back(
65         MakeGarbageCollected<MockFunction>(script_state_));
66     EXPECT_CALL(*mock_functions_.back(), Call(testing::_)).Times(0);
67     return mock_functions_.back()->Bind();
68   }
69 
70  private:
71   class MockFunction : public ScriptFunction {
72    public:
MockFunction(ScriptState * script_state)73     explicit MockFunction(ScriptState* script_state)
74         : ScriptFunction(script_state) {
75       ON_CALL(*this, Call(testing::_)).WillByDefault(testing::ReturnArg<0>());
76     }
Bind()77     v8::Local<v8::Function> Bind() { return BindToV8Function(); }
78     MOCK_METHOD1(Call, ScriptValue(ScriptValue));
79   };
80 
81   ScriptState* script_state_;
82   Vector<Persistent<MockFunction>> mock_functions_;
83 };
84 
MakeRequest(V8TestingScope * scope)85 static PresentationRequest* MakeRequest(V8TestingScope* scope) {
86   PresentationRequest* request =
87       PresentationRequest::Create(scope->GetExecutionContext(),
88                                   kPresentationUrl, scope->GetExceptionState());
89   EXPECT_FALSE(scope->GetExceptionState().HadException());
90   return request;
91 }
92 
93 }  // namespace
94 
TEST(PresentationConnectionCallbacksTest,HandleSuccess)95 TEST(PresentationConnectionCallbacksTest, HandleSuccess) {
96   V8TestingScope scope;
97   MockFunctionScope funcs(scope.GetScriptState());
98   auto* resolver =
99       MakeGarbageCollected<ScriptPromiseResolver>(scope.GetScriptState());
100   resolver->Promise().Then(funcs.ExpectCall(), funcs.ExpectNoCall());
101 
102   PresentationConnectionCallbacks callbacks(resolver, MakeRequest(&scope));
103 
104   // No connection currently exists.
105   EXPECT_FALSE(callbacks.connection_);
106 
107   mojo::PendingRemote<mojom::blink::PresentationConnection> connection_remote;
108   mojo::PendingReceiver<mojom::blink::PresentationConnection>
109       connection_receiver = connection_remote.InitWithNewPipeAndPassReceiver();
110   PresentationConnectionResultPtr result = PresentationConnectionResult::New(
111       PresentationInfo::New(url_test_helpers::ToKURL(kPresentationUrl),
112                             kPresentationId),
113       std::move(connection_remote), std::move(connection_receiver));
114 
115   callbacks.HandlePresentationResponse(std::move(result), nullptr);
116 
117   // New connection was created.
118   ControllerPresentationConnection* connection = callbacks.connection_.Get();
119   ASSERT_TRUE(connection);
120   EXPECT_EQ(connection->GetState(), PresentationConnectionState::CONNECTING);
121 }
122 
TEST(PresentationConnectionCallbacksTest,HandleReconnect)123 TEST(PresentationConnectionCallbacksTest, HandleReconnect) {
124   V8TestingScope scope;
125   MockFunctionScope funcs(scope.GetScriptState());
126   PresentationInfoPtr info = PresentationInfo::New(
127       url_test_helpers::ToKURL(kPresentationUrl), kPresentationId);
128   auto* resolver =
129       MakeGarbageCollected<ScriptPromiseResolver>(scope.GetScriptState());
130   resolver->Promise().Then(funcs.ExpectCall(), funcs.ExpectNoCall());
131 
132   auto* connection = ControllerPresentationConnection::Take(
133       resolver, *info, MakeRequest(&scope));
134   // Connection must be closed for reconnection to succeed.
135   connection->close();
136 
137   PresentationConnectionCallbacks callbacks(resolver, connection);
138 
139   mojo::PendingRemote<mojom::blink::PresentationConnection> connection_remote;
140   mojo::PendingReceiver<mojom::blink::PresentationConnection>
141       connection_receiver = connection_remote.InitWithNewPipeAndPassReceiver();
142   PresentationConnectionResultPtr result = PresentationConnectionResult::New(
143       std::move(info), std::move(connection_remote),
144       std::move(connection_receiver));
145 
146   callbacks.HandlePresentationResponse(std::move(result), nullptr);
147 
148   // Previous connection was returned.
149   ControllerPresentationConnection* new_connection =
150       callbacks.connection_.Get();
151   EXPECT_EQ(connection, new_connection);
152   EXPECT_EQ(new_connection->GetState(),
153             PresentationConnectionState::CONNECTING);
154 }
155 
TEST(PresentationConnectionCallbacksTest,HandleError)156 TEST(PresentationConnectionCallbacksTest, HandleError) {
157   V8TestingScope scope;
158   MockFunctionScope funcs(scope.GetScriptState());
159   auto* resolver =
160       MakeGarbageCollected<ScriptPromiseResolver>(scope.GetScriptState());
161   resolver->Promise().Then(funcs.ExpectNoCall(), funcs.ExpectCall());
162 
163   PresentationConnectionCallbacks callbacks(resolver, MakeRequest(&scope));
164 
165   // No connection currently exists.
166   EXPECT_FALSE(callbacks.connection_);
167 
168   callbacks.HandlePresentationResponse(
169       nullptr,
170       PresentationError::New(PresentationErrorType::NO_PRESENTATION_FOUND,
171                              "Something bad happened"));
172 
173   // No connection was created.
174   EXPECT_FALSE(callbacks.connection_);
175 }
176 
177 }  // namespace blink
178