1// Copyright 2014 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 "extensions/common/message_bundle.h"
6
7#include <string>
8#include <vector>
9
10#include "base/i18n/rtl.h"
11#include "base/memory/linked_ptr.h"
12#include "base/memory/scoped_ptr.h"
13#include "base/strings/string_util.h"
14#include "base/strings/utf_string_conversions.h"
15#include "base/values.h"
16#include "extensions/common/error_utils.h"
17#include "extensions/common/extension_l10n_util.h"
18#include "extensions/common/manifest_constants.h"
19#include "testing/gtest/include/gtest/gtest.h"
20
21namespace extensions {
22
23namespace errors = manifest_errors;
24
25class MessageBundleTest : public testing::Test {
26 protected:
27  enum BadDictionary {
28    INVALID_NAME,
29    NAME_NOT_A_TREE,
30    EMPTY_NAME_TREE,
31    MISSING_MESSAGE,
32    PLACEHOLDER_NOT_A_TREE,
33    EMPTY_PLACEHOLDER_TREE,
34    CONTENT_MISSING,
35    MESSAGE_PLACEHOLDER_DOESNT_MATCH,
36  };
37
38  // Helper method for dictionary building.
39  void SetDictionary(const std::string& name,
40                     base::DictionaryValue* subtree,
41                     base::DictionaryValue* target) {
42    target->Set(name, static_cast<base::Value*>(subtree));
43  }
44
45  void CreateContentTree(const std::string& name,
46                         const std::string& content,
47                         base::DictionaryValue* dict) {
48    base::DictionaryValue* content_tree = new base::DictionaryValue;
49    content_tree->SetString(MessageBundle::kContentKey, content);
50    SetDictionary(name, content_tree, dict);
51  }
52
53  void CreatePlaceholdersTree(base::DictionaryValue* dict) {
54    base::DictionaryValue* placeholders_tree = new base::DictionaryValue;
55    CreateContentTree("a", "A", placeholders_tree);
56    CreateContentTree("b", "B", placeholders_tree);
57    CreateContentTree("c", "C", placeholders_tree);
58    SetDictionary(MessageBundle::kPlaceholdersKey,
59                  placeholders_tree,
60                  dict);
61  }
62
63  void CreateMessageTree(const std::string& name,
64                         const std::string& message,
65                         bool create_placeholder_subtree,
66                         base::DictionaryValue* dict) {
67    base::DictionaryValue* message_tree = new base::DictionaryValue;
68    if (create_placeholder_subtree)
69      CreatePlaceholdersTree(message_tree);
70    message_tree->SetString(MessageBundle::kMessageKey, message);
71    SetDictionary(name, message_tree, dict);
72  }
73
74  // Caller owns the memory.
75  base::DictionaryValue* CreateGoodDictionary() {
76    base::DictionaryValue* dict = new base::DictionaryValue;
77    CreateMessageTree("n1", "message1 $a$ $b$", true, dict);
78    CreateMessageTree("n2", "message2 $c$", true, dict);
79    CreateMessageTree("n3", "message3", false, dict);
80    return dict;
81  }
82
83  // Caller owns the memory.
84  base::DictionaryValue* CreateBadDictionary(enum BadDictionary what_is_bad) {
85    base::DictionaryValue* dict = CreateGoodDictionary();
86    // Now remove/break things.
87    switch (what_is_bad) {
88      case INVALID_NAME:
89        CreateMessageTree("n 5", "nevermind", false, dict);
90        break;
91      case NAME_NOT_A_TREE:
92        dict->SetString("n4", "whatever");
93        break;
94      case EMPTY_NAME_TREE: {
95          base::DictionaryValue* empty_tree = new base::DictionaryValue;
96          SetDictionary("n4", empty_tree, dict);
97        }
98        break;
99      case MISSING_MESSAGE:
100        dict->Remove("n1.message", NULL);
101        break;
102      case PLACEHOLDER_NOT_A_TREE:
103        dict->SetString("n1.placeholders", "whatever");
104        break;
105      case EMPTY_PLACEHOLDER_TREE: {
106          base::DictionaryValue* empty_tree = new base::DictionaryValue;
107          SetDictionary("n1.placeholders", empty_tree, dict);
108        }
109        break;
110      case CONTENT_MISSING:
111         dict->Remove("n1.placeholders.a.content", NULL);
112        break;
113      case MESSAGE_PLACEHOLDER_DOESNT_MATCH:
114        base::DictionaryValue* value;
115        dict->Remove("n1.placeholders.a", NULL);
116        dict->GetDictionary("n1.placeholders", &value);
117        CreateContentTree("x", "X", value);
118        break;
119    }
120
121    return dict;
122  }
123
124  unsigned int ReservedMessagesCount() {
125    // Update when adding new reserved messages.
126    return 5U;
127  }
128
129  void CheckReservedMessages(MessageBundle* handler) {
130    std::string ui_locale = extension_l10n_util::CurrentLocaleOrDefault();
131    EXPECT_EQ(ui_locale,
132              handler->GetL10nMessage(MessageBundle::kUILocaleKey));
133
134    std::string text_dir = "ltr";
135    if (base::i18n::GetTextDirectionForLocale(ui_locale.c_str()) ==
136        base::i18n::RIGHT_TO_LEFT)
137      text_dir = "rtl";
138
139    EXPECT_EQ(text_dir, handler->GetL10nMessage(
140        MessageBundle::kBidiDirectionKey));
141  }
142
143  bool AppendReservedMessages(const std::string& application_locale) {
144    std::string error;
145    return handler_->AppendReservedMessagesForLocale(
146        application_locale, &error);
147  }
148
149  std::string CreateMessageBundle() {
150    std::string error;
151    handler_.reset(MessageBundle::Create(catalogs_, &error));
152
153    return error;
154  }
155
156  void ClearDictionary() {
157    handler_->dictionary_.clear();
158  }
159
160  scoped_ptr<MessageBundle> handler_;
161  std::vector<linked_ptr<base::DictionaryValue> > catalogs_;
162};
163
164TEST_F(MessageBundleTest, ReservedMessagesCount) {
165  ASSERT_EQ(5U, ReservedMessagesCount());
166}
167
168TEST_F(MessageBundleTest, InitEmptyDictionaries) {
169  CreateMessageBundle();
170  EXPECT_TRUE(handler_.get() != NULL);
171  EXPECT_EQ(0U + ReservedMessagesCount(), handler_->size());
172  CheckReservedMessages(handler_.get());
173}
174
175TEST_F(MessageBundleTest, InitGoodDefaultDict) {
176  catalogs_.push_back(
177      linked_ptr<base::DictionaryValue>(CreateGoodDictionary()));
178  CreateMessageBundle();
179
180  EXPECT_TRUE(handler_.get() != NULL);
181  EXPECT_EQ(3U + ReservedMessagesCount(), handler_->size());
182
183  EXPECT_EQ("message1 A B", handler_->GetL10nMessage("n1"));
184  EXPECT_EQ("message2 C", handler_->GetL10nMessage("n2"));
185  EXPECT_EQ("message3", handler_->GetL10nMessage("n3"));
186  CheckReservedMessages(handler_.get());
187}
188
189TEST_F(MessageBundleTest, InitAppDictConsultedFirst) {
190  catalogs_.push_back(
191      linked_ptr<base::DictionaryValue>(CreateGoodDictionary()));
192  catalogs_.push_back(
193      linked_ptr<base::DictionaryValue>(CreateGoodDictionary()));
194
195  base::DictionaryValue* app_dict = catalogs_[0].get();
196  // Flip placeholders in message of n1 tree.
197  app_dict->SetString("n1.message", "message1 $b$ $a$");
198  // Remove one message from app dict.
199  app_dict->Remove("n2", NULL);
200  // Replace n3 with N3.
201  app_dict->Remove("n3", NULL);
202  CreateMessageTree("N3", "message3_app_dict", false, app_dict);
203
204  CreateMessageBundle();
205
206  EXPECT_TRUE(handler_.get() != NULL);
207  EXPECT_EQ(3U + ReservedMessagesCount(), handler_->size());
208
209  EXPECT_EQ("message1 B A", handler_->GetL10nMessage("n1"));
210  EXPECT_EQ("message2 C", handler_->GetL10nMessage("n2"));
211  EXPECT_EQ("message3_app_dict", handler_->GetL10nMessage("n3"));
212  CheckReservedMessages(handler_.get());
213}
214
215TEST_F(MessageBundleTest, InitBadAppDict) {
216  catalogs_.push_back(
217      linked_ptr<base::DictionaryValue>(CreateBadDictionary(INVALID_NAME)));
218  catalogs_.push_back(
219      linked_ptr<base::DictionaryValue>(CreateGoodDictionary()));
220
221  std::string error = CreateMessageBundle();
222
223  EXPECT_TRUE(handler_.get() == NULL);
224  EXPECT_EQ("Name of a key \"n 5\" is invalid. Only ASCII [a-z], "
225            "[A-Z], [0-9] and \"_\" are allowed.", error);
226
227  catalogs_[0].reset(CreateBadDictionary(NAME_NOT_A_TREE));
228  handler_.reset(MessageBundle::Create(catalogs_, &error));
229  EXPECT_TRUE(handler_.get() == NULL);
230  EXPECT_EQ("Not a valid tree for key n4.", error);
231
232  catalogs_[0].reset(CreateBadDictionary(EMPTY_NAME_TREE));
233  handler_.reset(MessageBundle::Create(catalogs_, &error));
234  EXPECT_TRUE(handler_.get() == NULL);
235  EXPECT_EQ("There is no \"message\" element for key n4.", error);
236
237  catalogs_[0].reset(CreateBadDictionary(MISSING_MESSAGE));
238  handler_.reset(MessageBundle::Create(catalogs_, &error));
239  EXPECT_TRUE(handler_.get() == NULL);
240  EXPECT_EQ("There is no \"message\" element for key n1.", error);
241
242  catalogs_[0].reset(CreateBadDictionary(PLACEHOLDER_NOT_A_TREE));
243  handler_.reset(MessageBundle::Create(catalogs_, &error));
244  EXPECT_TRUE(handler_.get() == NULL);
245  EXPECT_EQ("Not a valid \"placeholders\" element for key n1.", error);
246
247  catalogs_[0].reset(CreateBadDictionary(EMPTY_PLACEHOLDER_TREE));
248  handler_.reset(MessageBundle::Create(catalogs_, &error));
249  EXPECT_TRUE(handler_.get() == NULL);
250  EXPECT_EQ("Variable $a$ used but not defined.", error);
251
252  catalogs_[0].reset(CreateBadDictionary(CONTENT_MISSING));
253  handler_.reset(MessageBundle::Create(catalogs_, &error));
254  EXPECT_TRUE(handler_.get() == NULL);
255  EXPECT_EQ("Invalid \"content\" element for key n1.", error);
256
257  catalogs_[0].reset(CreateBadDictionary(MESSAGE_PLACEHOLDER_DOESNT_MATCH));
258  handler_.reset(MessageBundle::Create(catalogs_, &error));
259  EXPECT_TRUE(handler_.get() == NULL);
260  EXPECT_EQ("Variable $a$ used but not defined.", error);
261}
262
263TEST_F(MessageBundleTest, ReservedMessagesOverrideDeveloperMessages) {
264  catalogs_.push_back(
265      linked_ptr<base::DictionaryValue>(CreateGoodDictionary()));
266
267  base::DictionaryValue* dict = catalogs_[0].get();
268  CreateMessageTree(MessageBundle::kUILocaleKey, "x", false, dict);
269
270  std::string error = CreateMessageBundle();
271
272  EXPECT_TRUE(handler_.get() == NULL);
273  std::string expected_error = ErrorUtils::FormatErrorMessage(
274      errors::kReservedMessageFound, MessageBundle::kUILocaleKey);
275  EXPECT_EQ(expected_error, error);
276}
277
278TEST_F(MessageBundleTest, AppendReservedMessagesForLTR) {
279  CreateMessageBundle();
280
281  ASSERT_TRUE(handler_.get() != NULL);
282  ClearDictionary();
283  ASSERT_TRUE(AppendReservedMessages("en_US"));
284
285  EXPECT_EQ("en_US",
286            handler_->GetL10nMessage(MessageBundle::kUILocaleKey));
287  EXPECT_EQ("ltr", handler_->GetL10nMessage(
288      MessageBundle::kBidiDirectionKey));
289  EXPECT_EQ("rtl", handler_->GetL10nMessage(
290      MessageBundle::kBidiReversedDirectionKey));
291  EXPECT_EQ("left", handler_->GetL10nMessage(
292      MessageBundle::kBidiStartEdgeKey));
293  EXPECT_EQ("right", handler_->GetL10nMessage(
294      MessageBundle::kBidiEndEdgeKey));
295}
296
297TEST_F(MessageBundleTest, AppendReservedMessagesForRTL) {
298  CreateMessageBundle();
299
300  ASSERT_TRUE(handler_.get() != NULL);
301  ClearDictionary();
302  ASSERT_TRUE(AppendReservedMessages("he"));
303
304  EXPECT_EQ("he",
305            handler_->GetL10nMessage(MessageBundle::kUILocaleKey));
306  EXPECT_EQ("rtl", handler_->GetL10nMessage(
307      MessageBundle::kBidiDirectionKey));
308  EXPECT_EQ("ltr", handler_->GetL10nMessage(
309      MessageBundle::kBidiReversedDirectionKey));
310  EXPECT_EQ("right", handler_->GetL10nMessage(
311      MessageBundle::kBidiStartEdgeKey));
312  EXPECT_EQ("left", handler_->GetL10nMessage(
313      MessageBundle::kBidiEndEdgeKey));
314}
315
316TEST_F(MessageBundleTest, IsValidNameCheckValidCharacters) {
317  EXPECT_TRUE(MessageBundle::IsValidName(std::string("a__BV_9")));
318  EXPECT_TRUE(MessageBundle::IsValidName(std::string("@@a__BV_9")));
319  EXPECT_FALSE(MessageBundle::IsValidName(std::string("$a__BV_9$")));
320  EXPECT_FALSE(MessageBundle::IsValidName(std::string("a-BV-9")));
321  EXPECT_FALSE(MessageBundle::IsValidName(std::string("a#BV!9")));
322  EXPECT_FALSE(MessageBundle::IsValidName(std::string("a<b")));
323}
324
325struct ReplaceVariables {
326  const char* original;
327  const char* result;
328  const char* error;
329  const char* begin_delimiter;
330  const char* end_delimiter;
331  bool pass;
332};
333
334TEST(MessageBundle, ReplaceMessagesInText) {
335  const char* kMessageBegin = MessageBundle::kMessageBegin;
336  const char* kMessageEnd = MessageBundle::kMessageEnd;
337  const char* kPlaceholderBegin = MessageBundle::kPlaceholderBegin;
338  const char* kPlaceholderEnd = MessageBundle::kPlaceholderEnd;
339
340  static ReplaceVariables test_cases[] = {
341    // Message replacement.
342    { "This is __MSG_siMPle__ message", "This is simple message",
343      "", kMessageBegin, kMessageEnd, true },
344    { "This is __MSG_", "This is __MSG_",
345      "", kMessageBegin, kMessageEnd, true },
346    { "This is __MSG__simple__ message", "This is __MSG__simple__ message",
347      "Variable __MSG__simple__ used but not defined.",
348      kMessageBegin, kMessageEnd, false },
349    { "__MSG_LoNg__", "A pretty long replacement",
350      "", kMessageBegin, kMessageEnd, true },
351    { "A __MSG_SimpLE__MSG_ a", "A simpleMSG_ a",
352      "", kMessageBegin, kMessageEnd, true },
353    { "A __MSG_simple__MSG_long__", "A simpleMSG_long__",
354      "", kMessageBegin, kMessageEnd, true },
355    { "A __MSG_simple____MSG_long__", "A simpleA pretty long replacement",
356      "", kMessageBegin, kMessageEnd, true },
357    { "__MSG_d1g1ts_are_ok__", "I are d1g1t",
358      "", kMessageBegin, kMessageEnd, true },
359    // Placeholder replacement.
360    { "This is $sImpLe$ message", "This is simple message",
361       "", kPlaceholderBegin, kPlaceholderEnd, true },
362    { "This is $", "This is $",
363       "", kPlaceholderBegin, kPlaceholderEnd, true },
364    { "This is $$sIMPle$ message", "This is $simple message",
365       "", kPlaceholderBegin, kPlaceholderEnd, true },
366    { "$LONG_V$", "A pretty long replacement",
367       "", kPlaceholderBegin, kPlaceholderEnd, true },
368    { "A $simple$$ a", "A simple$ a",
369       "", kPlaceholderBegin, kPlaceholderEnd, true },
370    { "A $simple$long_v$", "A simplelong_v$",
371       "", kPlaceholderBegin, kPlaceholderEnd, true },
372    { "A $simple$$long_v$", "A simpleA pretty long replacement",
373       "", kPlaceholderBegin, kPlaceholderEnd, true },
374    { "This is $bad name$", "This is $bad name$",
375       "", kPlaceholderBegin, kPlaceholderEnd, true },
376    { "This is $missing$", "This is $missing$",
377       "Variable $missing$ used but not defined.",
378       kPlaceholderBegin, kPlaceholderEnd, false },
379  };
380
381  MessageBundle::SubstitutionMap messages;
382  messages.insert(std::make_pair("simple", "simple"));
383  messages.insert(std::make_pair("long", "A pretty long replacement"));
384  messages.insert(std::make_pair("long_v", "A pretty long replacement"));
385  messages.insert(std::make_pair("bad name", "Doesn't matter"));
386  messages.insert(std::make_pair("d1g1ts_are_ok", "I are d1g1t"));
387
388  for (size_t i = 0; i < arraysize(test_cases); ++i) {
389    std::string text = test_cases[i].original;
390    std::string error;
391    EXPECT_EQ(test_cases[i].pass,
392              MessageBundle::ReplaceVariables(messages,
393                                              test_cases[i].begin_delimiter,
394                                              test_cases[i].end_delimiter,
395                                              &text,
396                                              &error));
397    EXPECT_EQ(test_cases[i].result, text);
398  }
399}
400
401///////////////////////////////////////////////////////////////////////////////
402//
403// Renderer helper functions test.
404//
405///////////////////////////////////////////////////////////////////////////////
406
407TEST(GetExtensionToL10nMessagesMapTest, ReturnsTheSameObject) {
408  ExtensionToL10nMessagesMap* map1 = GetExtensionToL10nMessagesMap();
409  ASSERT_TRUE(NULL != map1);
410
411  ExtensionToL10nMessagesMap* map2 = GetExtensionToL10nMessagesMap();
412  ASSERT_EQ(map1, map2);
413}
414
415TEST(GetExtensionToL10nMessagesMapTest, ReturnsNullForUnknownExtensionId) {
416  const std::string extension_id("some_unique_12334212314234_id");
417  L10nMessagesMap* map = GetL10nMessagesMap(extension_id);
418  EXPECT_TRUE(NULL == map);
419}
420
421TEST(GetExtensionToL10nMessagesMapTest, ReturnsMapForKnownExtensionId) {
422  const std::string extension_id("some_unique_121212121212121_id");
423  // Store a map for given id.
424  L10nMessagesMap messages;
425  messages.insert(std::make_pair("message_name", "message_value"));
426  (*GetExtensionToL10nMessagesMap())[extension_id] = messages;
427
428  L10nMessagesMap* map = GetL10nMessagesMap(extension_id);
429  ASSERT_TRUE(NULL != map);
430  EXPECT_EQ(1U, map->size());
431  EXPECT_EQ("message_value", (*map)["message_name"]);
432}
433
434}  // namespace extensions
435