1 // Copyright 2015 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.
9 #include "base/callback.h"
10 #include "base/memory/ref_counted.h"
11 #include "base/memory/scoped_ptr.h"
12 #include "base/values.h"
13 #include "chrome/browser/extensions/api/audio_modem/audio_modem_api.h"
14 #include "chrome/browser/extensions/extension_api_unittest.h"
15 #include "chrome/browser/extensions/extension_function_test_utils.h"
16 #include "chrome/browser/extensions/test_extension_system.h"
17 #include "components/audio_modem/public/modem.h"
18 #include "components/audio_modem/test/stub_modem.h"
19 #include "components/audio_modem/test/stub_whispernet_client.h"
20 #include "extensions/browser/api_test_utils.h"
21 #include "extensions/browser/event_router.h"
22 #include "extensions/browser/event_router_factory.h"
24 using audio_modem::AUDIBLE
;
25 using audio_modem::AudioToken
;
26 using audio_modem::INAUDIBLE
;
27 using audio_modem::StubModem
;
28 using audio_modem::StubWhispernetClient
;
30 using base::BinaryValue
;
31 using base::DictionaryValue
;
32 using base::ListValue
;
33 using base::StringValue
;
36 using content::BrowserContext
;
38 namespace ext_test_utils
= extension_function_test_utils
;
40 namespace extensions
{
44 // The TestingFactoryFunction uses a BrowserContext as its context pointer.
45 // But each BrowserContext is still associated with a unit test.
46 // So we store the StubModem created in each test.
47 std::map
<BrowserContext
*, StubModem
*> g_modems
;
49 // Create a test AudioModemAPI and store the modem it uses.
50 scoped_ptr
<KeyedService
> ApiFactoryFunction(BrowserContext
* context
) {
51 StubModem
* modem
= new StubModem
;
52 g_modems
[context
] = modem
;
53 return make_scoped_ptr(new AudioModemAPI(
55 make_scoped_ptr
<audio_modem::WhispernetClient
>(new StubWhispernetClient
),
56 make_scoped_ptr
<audio_modem::Modem
>(modem
)));
59 DictionaryValue
* CreateParams(const std::string
& audio_band
) {
60 DictionaryValue
* params
= new DictionaryValue
;
61 params
->SetInteger("timeoutMillis", 60000);
62 params
->SetString("band", audio_band
);
63 params
->SetInteger("encoding.tokenLength", 4);
67 BinaryValue
* CreateToken(const std::string
& token
) {
68 return BinaryValue::CreateWithCopiedBuffer(token
.c_str(), token
.size());
71 scoped_ptr
<ListValue
> CreateList(Value
* single_elt
) {
72 scoped_ptr
<ListValue
> list(new ListValue
);
73 list
->Append(single_elt
);
77 scoped_ptr
<ListValue
> CreateList(Value
* elt1
, Value
* elt2
) {
78 scoped_ptr
<ListValue
> list(new ListValue
);
84 DictionaryValue
* CreateReceivedToken(const std::string
& token
,
85 const std::string
& audio_band
) {
86 DictionaryValue
* out
= new DictionaryValue
;
87 out
->Set("token", CreateToken(token
));
88 out
->SetString("band", audio_band
);
92 bool ReceivedSingleToken(const Event
* event
,
93 const DictionaryValue
* expected_token
) {
94 ListValue
* received_tokens
;
95 event
->event_args
->GetList(0, &received_tokens
);
96 if (received_tokens
->GetSize() != 1)
99 DictionaryValue
* received_token
;
100 received_tokens
->GetDictionary(0, &received_token
);
101 return received_token
->Equals(expected_token
);
104 // TODO(ckehoe): Put this in //extensions/test.
105 // Then replace the other EventRouter mocks.
106 class StubEventRouter
: public EventRouter
{
108 // Callback to receive events. First argument is
109 // the extension id to receive the event.
110 using EventCallback
= base::Callback
<void(const std::string
&,
113 explicit StubEventRouter(BrowserContext
* context
)
114 : EventRouter(context
, nullptr) {}
116 void DispatchEventToExtension(const std::string
& extension_id
,
117 scoped_ptr
<Event
> event
) override
{
118 event_callback_
.Run(extension_id
, event
.Pass());
121 void SetEventCallBack(EventCallback event_callback
) {
122 event_callback_
= event_callback
;
125 void ClearEventCallback() {
126 event_callback_
.Reset();
130 EventCallback event_callback_
;
133 // StubEventRouter factory function
134 scoped_ptr
<KeyedService
> StubEventRouterFactoryFunction(
135 content::BrowserContext
* context
) {
136 return make_scoped_ptr(new StubEventRouter(context
));
141 class AudioModemApiUnittest
: public ExtensionApiUnittest
{
143 AudioModemApiUnittest() {}
144 ~AudioModemApiUnittest() override
{
145 for (const auto& events
: events_by_extension_id_
) {
146 for (const Event
* event
: events
.second
)
152 template<typename Function
>
153 const std::string
RunFunction(scoped_ptr
<ListValue
> args
,
154 const Extension
* extension
) {
155 scoped_refptr
<UIThreadExtensionFunction
> function(new Function
);
156 function
->set_extension(extension
);
157 function
->set_browser_context(profile());
158 function
->set_has_callback(true);
159 ext_test_utils::RunFunction(
160 function
.get(), args
.Pass(), browser(), ext_test_utils::NONE
);
162 std::string result_status
;
163 CHECK(function
->GetResultList()->GetString(0, &result_status
));
164 return result_status
;
167 template<typename Function
>
168 const std::string
RunFunction(scoped_ptr
<ListValue
> args
) {
169 return RunFunction
<Function
>(args
.Pass(), GetExtension(std::string()));
172 StubModem
* GetModem() const {
173 return g_modems
[profile()];
176 const Extension
* GetExtension(const std::string
& name
) {
177 if (!extensions_by_name_
[name
].get()) {
178 scoped_ptr
<DictionaryValue
> extension_definition(new DictionaryValue
);
179 extension_definition
->SetString("name", name
);
180 extension_definition
->SetString("version", "1.0");
181 extensions_by_name_
[name
] = api_test_utils::CreateExtension(
182 Manifest::INTERNAL
, extension_definition
.get(), name
);
183 DVLOG(2) << "Created extension " << extensions_by_name_
[name
]->id();
185 return extensions_by_name_
[name
].get();
188 const std::vector
<const Event
*>&
189 GetEventsForExtension(const std::string
& name
) {
190 const Extension
* extension
= extensions_by_name_
[name
].get();
192 return events_by_extension_id_
[extension
->id()];
195 const std::vector
<const Event
*>& GetEvents() {
196 return GetEventsForExtension(std::string());
200 void SetUp() override
{
201 ExtensionApiUnittest::SetUp();
202 AudioModemAPI::GetFactoryInstance()->SetTestingFactory(
203 profile(), &ApiFactoryFunction
);
205 StubEventRouter
* stub_event_router
= static_cast<StubEventRouter
*>(
206 extensions::EventRouterFactory::GetInstance()->SetTestingFactoryAndUse(
207 profile(), &StubEventRouterFactoryFunction
));
208 stub_event_router
->SetEventCallBack(base::Bind(
209 &AudioModemApiUnittest::CaptureEvent
, base::Unretained(this)));
212 void CaptureEvent(const std::string
& extension_id
,
213 scoped_ptr
<Event
> event
) {
214 // Since scoped_ptr (and ScopedVector) do not work inside STL containers,
215 // we must manage this memory manually. It is cleaned up by the destructor.
216 events_by_extension_id_
[extension_id
].push_back(event
.release());
219 std::map
<std::string
, scoped_refptr
<Extension
>> extensions_by_name_
;
221 // We own all of these pointers.
222 // Do not remove them from the map without calling delete.
223 std::map
<std::string
, std::vector
<const Event
*>> events_by_extension_id_
;
226 TEST_F(AudioModemApiUnittest
, TransmitBasic
) {
227 // Start transmitting inaudibly.
228 EXPECT_EQ("success", RunFunction
<AudioModemTransmitFunction
>(
229 CreateList(CreateParams("inaudible"), CreateToken("1234"))));
230 EXPECT_TRUE(GetModem()->IsPlaying(INAUDIBLE
));
232 // Can't cancel audible transmit - we haven't started it yet.
233 EXPECT_EQ("invalidRequest", RunFunction
<AudioModemStopTransmitFunction
>(
234 CreateList(new StringValue("audible"))));
236 // Start transmitting audibly.
237 EXPECT_EQ("success", RunFunction
<AudioModemTransmitFunction
>(
238 CreateList(CreateParams("audible"), CreateToken("ABCD"))));
239 EXPECT_TRUE(GetModem()->IsPlaying(AUDIBLE
));
241 // Stop audible transmit. We're still transmitting inaudibly.
242 EXPECT_EQ("success", RunFunction
<AudioModemStopTransmitFunction
>(
243 CreateList(new StringValue("audible"))));
244 EXPECT_FALSE(GetModem()->IsPlaying(AUDIBLE
));
245 EXPECT_TRUE(GetModem()->IsPlaying(INAUDIBLE
));
247 // Stop inaudible transmit.
248 EXPECT_EQ("success", RunFunction
<AudioModemStopTransmitFunction
>(
249 CreateList(new StringValue("inaudible"))));
250 EXPECT_FALSE(GetModem()->IsPlaying(INAUDIBLE
));
253 TEST_F(AudioModemApiUnittest
, ReceiveBasic
) {
254 // Start listening for audible tokens.
255 EXPECT_EQ("success", RunFunction
<AudioModemReceiveFunction
>(
256 CreateList(CreateParams("audible"))));
257 EXPECT_TRUE(GetModem()->IsRecording(AUDIBLE
));
259 // Can't cancel inaudible receive - we haven't started it yet.
260 EXPECT_EQ("invalidRequest", RunFunction
<AudioModemStopReceiveFunction
>(
261 CreateList(new StringValue("inaudible"))));
263 // Send some audible tokens.
264 std::vector
<AudioToken
> tokens
;
265 tokens
.push_back(AudioToken("1234", true));
266 tokens
.push_back(AudioToken("ABCD", true));
267 tokens
.push_back(AudioToken("abcd", false));
268 GetModem()->DeliverTokens(tokens
);
270 // Check the tokens received.
271 EXPECT_EQ(1u, GetEvents().size());
272 scoped_ptr
<ListValue
> expected_tokens(new ListValue
);
273 expected_tokens
->Append(CreateReceivedToken("1234", "audible"));
274 expected_tokens
->Append(CreateReceivedToken("ABCD", "audible"));
275 ListValue
* received_tokens
;
276 GetEvents()[0]->event_args
->GetList(0, &received_tokens
);
277 EXPECT_TRUE(received_tokens
->Equals(expected_tokens
.get()));
279 // Start listening for inaudible tokens.
280 EXPECT_EQ("success", RunFunction
<AudioModemReceiveFunction
>(
281 CreateList(CreateParams("inaudible"))));
282 EXPECT_TRUE(GetModem()->IsRecording(INAUDIBLE
));
284 // Send some more tokens.
285 tokens
.push_back(AudioToken("5678", false));
286 GetModem()->DeliverTokens(tokens
);
288 // Check the tokens received.
289 EXPECT_EQ(2u, GetEvents().size());
290 expected_tokens
->Append(CreateReceivedToken("abcd", "inaudible"));
291 expected_tokens
->Append(CreateReceivedToken("5678", "inaudible"));
292 GetEvents()[1]->event_args
->GetList(0, &received_tokens
);
293 EXPECT_TRUE(received_tokens
->Equals(expected_tokens
.get()));
295 // Stop audible receive. We're still receiving inaudible.
296 EXPECT_EQ("success", RunFunction
<AudioModemStopReceiveFunction
>(
297 CreateList(new StringValue("audible"))));
298 EXPECT_FALSE(GetModem()->IsRecording(AUDIBLE
));
299 EXPECT_TRUE(GetModem()->IsRecording(INAUDIBLE
));
301 // Stop inaudible receive.
302 EXPECT_EQ("success", RunFunction
<AudioModemStopReceiveFunction
>(
303 CreateList(new StringValue("inaudible"))));
304 EXPECT_FALSE(GetModem()->IsRecording(INAUDIBLE
));
307 TEST_F(AudioModemApiUnittest
, TransmitMultiple
) {
309 EXPECT_EQ("success", RunFunction
<AudioModemTransmitFunction
>(
310 CreateList(CreateParams("audible"), CreateToken("1234")),
311 GetExtension("ext1")));
312 EXPECT_TRUE(GetModem()->IsPlaying(AUDIBLE
));
314 // Another extension can't interfere with the first one.
315 EXPECT_EQ("inUse", RunFunction
<AudioModemTransmitFunction
>(
316 CreateList(CreateParams("audible"), CreateToken("ABCD")),
317 GetExtension("ext2")));
318 EXPECT_EQ("invalidRequest", RunFunction
<AudioModemStopTransmitFunction
>(
319 CreateList(new StringValue("audible")), GetExtension("ext2")));
320 EXPECT_TRUE(GetModem()->IsPlaying(AUDIBLE
));
322 // The other extension can use the other audio band, however.
323 EXPECT_EQ("success", RunFunction
<AudioModemTransmitFunction
>(
324 CreateList(CreateParams("inaudible"), CreateToken("ABCD")),
325 GetExtension("ext2")));
326 EXPECT_TRUE(GetModem()->IsPlaying(INAUDIBLE
));
328 // The first extension can change its token.
329 // But the other band is still in use.
330 EXPECT_EQ("success", RunFunction
<AudioModemTransmitFunction
>(
331 CreateList(CreateParams("audible"), CreateToken("abcd")),
332 GetExtension("ext1")));
333 EXPECT_EQ("inUse", RunFunction
<AudioModemTransmitFunction
>(
334 CreateList(CreateParams("inaudible"), CreateToken("1234")),
335 GetExtension("ext1")));
337 // Stop transmission.
338 EXPECT_EQ("success", RunFunction
<AudioModemStopTransmitFunction
>(
339 CreateList(new StringValue("audible")), GetExtension("ext1")));
340 EXPECT_FALSE(GetModem()->IsPlaying(AUDIBLE
));
341 EXPECT_EQ("success", RunFunction
<AudioModemStopTransmitFunction
>(
342 CreateList(new StringValue("inaudible")), GetExtension("ext2")));
343 EXPECT_FALSE(GetModem()->IsPlaying(INAUDIBLE
));
346 TEST_F(AudioModemApiUnittest
, ReceiveMultiple
) {
347 // Start receive. Multiple extensions can receive on the same band.
348 EXPECT_EQ("success", RunFunction
<AudioModemReceiveFunction
>(
349 CreateList(CreateParams("inaudible")), GetExtension("ext1")));
350 EXPECT_TRUE(GetModem()->IsRecording(INAUDIBLE
));
351 EXPECT_EQ("success", RunFunction
<AudioModemReceiveFunction
>(
352 CreateList(CreateParams("inaudible")), GetExtension("ext2")));
355 GetModem()->DeliverTokens(std::vector
<AudioToken
>(
356 1, AudioToken("abcd", false)));
357 EXPECT_EQ(1u, GetEventsForExtension("ext1").size());
358 EXPECT_EQ(1u, GetEventsForExtension("ext2").size());
360 // Check the token received.
361 scoped_ptr
<DictionaryValue
> expected_token(
362 CreateReceivedToken("abcd", "inaudible"));
363 EXPECT_TRUE(ReceivedSingleToken(
364 GetEventsForExtension("ext1")[0], expected_token
.get()));
365 EXPECT_TRUE(ReceivedSingleToken(
366 GetEventsForExtension("ext2")[0], expected_token
.get()));
368 // If one extension stops, the modem is still receiving for the other.
369 EXPECT_EQ("success", RunFunction
<AudioModemStopReceiveFunction
>(
370 CreateList(new StringValue("inaudible")), GetExtension("ext1")));
371 EXPECT_TRUE(GetModem()->IsRecording(INAUDIBLE
));
373 // Receive another token. Should only go to ext2.
374 GetModem()->DeliverTokens(std::vector
<AudioToken
>(
375 1, AudioToken("1234", false)));
376 EXPECT_EQ(1u, GetEventsForExtension("ext1").size());
377 EXPECT_EQ(2u, GetEventsForExtension("ext2").size());
378 expected_token
.reset(CreateReceivedToken("1234", "inaudible"));
379 EXPECT_TRUE(ReceivedSingleToken(
380 GetEventsForExtension("ext2")[1], expected_token
.get()));
382 EXPECT_EQ("success", RunFunction
<AudioModemStopReceiveFunction
>(
383 CreateList(new StringValue("inaudible")), GetExtension("ext2")));
384 EXPECT_FALSE(GetModem()->IsRecording(INAUDIBLE
));
387 } // namespace extensions