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