1// Copyright (C) 2013 Google Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#include <libaddressinput/address_input_helper.h>
16
17#include <libaddressinput/address_data.h>
18#include <libaddressinput/callback.h>
19#include <libaddressinput/null_storage.h>
20#include <libaddressinput/preload_supplier.h>
21#include <libaddressinput/util/basictypes.h>
22#include <libaddressinput/util/scoped_ptr.h>
23
24#include <string>
25#include <utility>
26
27#include <gtest/gtest.h>
28
29#include "mock_source.h"
30#include "testdata_source.h"
31
32namespace {
33
34using i18n::addressinput::AddressData;
35using i18n::addressinput::AddressInputHelper;
36using i18n::addressinput::BuildCallback;
37using i18n::addressinput::Callback;
38using i18n::addressinput::MockSource;
39using i18n::addressinput::NullStorage;
40using i18n::addressinput::PreloadSupplier;
41using i18n::addressinput::scoped_ptr;
42using i18n::addressinput::TestdataSource;
43
44class AddressInputHelperTest : public testing::Test {
45 protected:
46  AddressInputHelperTest()
47      // Our PreloadSupplier loads all data for a country at a time.
48      : supplier_(new TestdataSource(true), new NullStorage),
49        address_input_helper_(&supplier_),
50        loaded_(BuildCallback(this, &AddressInputHelperTest::Loaded)) {}
51
52  // Helper method to test FillAddress that ensures the PreloadSupplier has the
53  // necessary data preloaded.
54  void FillAddress(AddressData* address) {
55    const std::string& region_code = address->region_code;
56    if (!region_code.empty()) {
57      supplier_.LoadRules(region_code, *loaded_);
58    }
59    address_input_helper_.FillAddress(address);
60  }
61
62 private:
63  // Used to preload data that we need.
64  void Loaded(bool success, const std::string&, int) { ASSERT_TRUE(success); }
65
66  PreloadSupplier supplier_;
67  const AddressInputHelper address_input_helper_;
68  const scoped_ptr<const PreloadSupplier::Callback> loaded_;
69  DISALLOW_COPY_AND_ASSIGN(AddressInputHelperTest);
70};
71
72TEST_F(AddressInputHelperTest, AddressWithMissingPostalCode) {
73  AddressData address;
74  address.region_code = "CX";
75  address.administrative_area = "WA";
76
77  // There is only one postal code for Christmas Island
78  AddressData expected = address;
79  expected.postal_code = "6798";
80  FillAddress(&address);
81  EXPECT_EQ(expected, address);
82}
83
84TEST_F(AddressInputHelperTest, AddressWithPostalCodeMatchingAdmin) {
85  AddressData address;
86  address.region_code = "US";
87  address.postal_code = "58098";
88  // Other data should be left alone.
89  address.address_line.push_back("10 High St");
90
91  // North Dakota has post codes starting with 58.
92  AddressData expected = address;
93  expected.administrative_area = "ND";
94  FillAddress(&address);
95  EXPECT_EQ(expected, address);
96
97  address.administrative_area = "CA";  // Override the admin area.
98  // Now, since the admin area was already filled in, we don't fix it, even
99  // though it was correct.
100  expected.administrative_area = "CA";
101  FillAddress(&address);
102  EXPECT_EQ(expected, address);
103}
104
105TEST_F(AddressInputHelperTest, AddressWithPostalCodeMatchingLowerLevel) {
106  AddressData address;
107  address.region_code = "TW";
108  address.postal_code = "53012";
109
110  /* This matches 二水鄉 - Ershuei Township. */
111  AddressData expected = address;
112  /* This locality is in 彰化縣 - Changhua County. */
113  expected.administrative_area = "\xE5\xBD\xB0\xE5\x8C\x96\xE7\xB8\xA3";
114  expected.locality = "\xE4\xBA\x8C\xE6\xB0\xB4\xE9\x84\x89";
115  FillAddress(&address);
116  EXPECT_EQ(expected, address);
117
118  // Override the admin area.
119  address.administrative_area = "Already filled in";
120  expected.administrative_area = "Already filled in";
121  address.locality = "";
122  // However, the locality will still be filled in.
123  FillAddress(&address);
124  EXPECT_EQ(expected, address);
125}
126
127TEST_F(AddressInputHelperTest, AddressWithPostalCodeMatchingLowerLevelLatin) {
128  AddressData address;
129  address.region_code = "TW";
130  address.postal_code = "53012";
131  address.language_code = "zh-Latn";
132
133  /* This matches 二水鄉 - Ershuei Township. */
134  AddressData expected = address;
135  /* This locality is in 彰化縣 - Changhua County. */
136  expected.locality = "Ershuei Township";
137  expected.administrative_area = "Changhua County";
138  FillAddress(&address);
139  EXPECT_EQ(expected, address);
140
141  // Override the admin area.
142  address.administrative_area = "Already filled in";
143  expected.administrative_area = "Already filled in";
144  address.locality = "";
145  // However, the locality will still be filled in.
146  FillAddress(&address);
147  EXPECT_EQ(expected, address);
148}
149
150TEST_F(AddressInputHelperTest, AddressWithPostalCodeMatchingDependentLocality) {
151  AddressData address;
152  address.region_code = "KR";
153  // This matches Danwon-gu district.
154  address.postal_code = "425-111";
155
156  AddressData expected = address;
157  /* The province is Gyeonggi - 경기도. */
158  expected.administrative_area = "\xEA\xB2\xBD\xEA\xB8\xB0\xEB\x8F\x84";
159  /* The city is Ansan-si - 안산시. */
160  expected.locality = "\xEC\x95\x88\xEC\x82\xB0\xEC\x8B\x9C";
161  /* The district is Danwon-gu - 단원구 */
162  expected.dependent_locality = "\xEB\x8B\xA8\xEC\x9B\x90\xEA\xB5\xAC";
163
164  FillAddress(&address);
165  EXPECT_EQ(expected, address);
166
167  AddressData address_ko_latn;
168  address_ko_latn.region_code = "KR";
169  address_ko_latn.postal_code = "425-111";
170  address_ko_latn.language_code = "ko-latn";
171
172  expected = address_ko_latn;
173  /* The province is Gyeonggi - 경기도. */
174  expected.administrative_area = "Gyeonggi";
175  /* The city is Ansan-si - 안산시. */
176  expected.locality = "Ansan-si";
177  /* The district is Danwon-gu - 단원구 */
178  expected.dependent_locality = "Danwon-gu";
179
180  FillAddress(&address_ko_latn);
181  EXPECT_EQ(expected, address_ko_latn);
182}
183
184TEST_F(AddressInputHelperTest, AddressWithPostalCodeMatchingMultipleValues) {
185  AddressData address;
186  address.region_code = "KR";
187  // This matches Wando-gun and Ganjin-gun, both in Jeonnam province.
188  address.postal_code = "527-111";
189
190  AddressData expected = address;
191  /* The province, Jeonnam - 전라남도 - is known, but we have several locality
192   * matches so none of them are populated. */
193  expected.administrative_area =
194      "\xEC\xA0\x84\xEB\x9D\xBC\xEB\x82\xA8\xEB\x8F\x84";
195  FillAddress(&address);
196  EXPECT_EQ(expected, address);
197}
198
199TEST_F(AddressInputHelperTest, AddressWithInvalidPostalCode) {
200  AddressData address;
201  address.postal_code = "970";
202  address.region_code = "US";
203
204  // We don't expect any changes, since the postal code couldn't be determined
205  // as valid.
206  AddressData expected = address;
207  FillAddress(&address);
208  EXPECT_EQ(expected, address);
209}
210
211TEST_F(AddressInputHelperTest, AddressWithNoPostalCodeValidation) {
212  AddressData address;
213  address.postal_code = "123";
214  address.region_code = "GA";
215
216  // We don't expect any changes, since the postal code couldn't be determined
217  // as valid - we have no information about postal codes in Gabon (or even that
218  // they are in use).
219  AddressData expected = address;
220  FillAddress(&address);
221  EXPECT_EQ(expected, address);
222}
223
224TEST_F(AddressInputHelperTest, AddressWithInvalidOrMissingRegionCode) {
225  AddressData address;
226  address.postal_code = "XXX";
227  address.administrative_area = "YYY";
228
229  // We don't expect any changes, since there was no region code.
230  AddressData expected = address;
231  FillAddress(&address);
232  EXPECT_EQ(expected, address);
233
234  address.region_code = "XXXX";
235  expected.region_code = "XXXX";
236  // Again, nothing should change.
237  FillAddress(&address);
238  EXPECT_EQ(expected, address);
239}
240
241class AddressInputHelperMockDataTest : public testing::Test {
242 protected:
243  AddressInputHelperMockDataTest()
244      : source_(new MockSource),
245        // Our PreloadSupplier loads all data for a country at a time.
246        supplier_(source_, new NullStorage),
247        address_input_helper_(&supplier_),
248        loaded_(BuildCallback(this, &AddressInputHelperMockDataTest::Loaded)) {}
249
250  // Helper method to test FillAddress that ensures the PreloadSupplier has the
251  // necessary data preloaded.
252  void FillAddress(AddressData* address) {
253    const std::string& region_code = address->region_code;
254    if (!region_code.empty()) {
255      supplier_.LoadRules(region_code, *loaded_);
256    }
257    address_input_helper_.FillAddress(address);
258  }
259
260  MockSource* const source_;
261
262 private:
263  // Our mock source we assume will always succeed.
264  void Loaded(bool success, const std::string&, int) { ASSERT_TRUE(success); }
265
266  PreloadSupplier supplier_;
267  const AddressInputHelper address_input_helper_;
268  const scoped_ptr<const PreloadSupplier::Callback> loaded_;
269  DISALLOW_COPY_AND_ASSIGN(AddressInputHelperMockDataTest);
270};
271
272TEST_F(AddressInputHelperMockDataTest,
273       PostalCodeSharedAcrossDifferentHierarchies) {
274  // Note that this data is in the format of data that would be returned from
275  // the aggregate server.
276  source_->data_.insert(std::make_pair(
277      // We use KR since we need a country where we format all fields down to
278      // dependent locality, or the hierarchy won't be loaded.
279      "data/KR",
280      "{\"data/KR\": "
281      // The top-level ZIP expression must be present for sub-key matches to be
282      // evaluated.
283      "{\"id\":\"data/KR\", \"sub_keys\":\"A~B\", \"zip\":\"\\\\d{5}\"}, "
284      "\"data/KR/A\": "
285      "{\"id\":\"data/KR/A\", \"sub_keys\":\"A1\"}, "
286      "\"data/KR/A/A1\": "
287      "{\"id\":\"data/KR/A/A1\", \"zip\":\"1\"}, "
288      "\"data/KR/B\": "
289      "{\"id\":\"data/KR/B\", \"sub_keys\":\"B1\"}, "
290      "\"data/KR/B/B1\": "
291      "{\"id\":\"data/KR/B/B1\", \"zip\":\"12\"}}"));
292
293  AddressData address;
294  address.region_code = "KR";
295  address.postal_code = "12345";
296  address.administrative_area = "";
297
298  AddressData expected = address;
299  FillAddress(&address);
300  // Nothing should have changed, since the ZIP code matches both of the cities,
301  // and they aren't even in the same state.
302  EXPECT_EQ(expected, address);
303}
304
305TEST_F(AddressInputHelperMockDataTest,
306       PostalCodeSharedAcrossDifferentHierarchiesSameState) {
307  // Create data where one state matches the ZIP code, but the other doesn't:
308  // within the state which does, multiple cities and sub-cities match. The only
309  // thing we can be certain of is therefore the state.
310  source_->data_.insert(std::make_pair(
311      // We use KR since we need a country where we format all fields down to
312      // dependent locality, or the hierarchy won't be loaded.
313      "data/KR",
314      "{\"data/KR\": "
315      // The top-level ZIP expression must be present for sub-key matches to be
316      // evaluated.
317      "{\"id\":\"data/KR\", \"sub_keys\":\"A~B\", \"zip\":\"\\\\d{5}\"}, "
318      "\"data/KR/A\": "
319      "{\"id\":\"data/KR/A\", \"sub_keys\":\"A1~A2\"}, "
320      "\"data/KR/A/A1\": "
321      "{\"id\":\"data/KR/A/A1\", \"sub_keys\":\"A1a\", \"zip\":\"1\"}, "
322      // This key matches the ZIP code.
323      "\"data/KR/A/A1/A1a\": "
324      "{\"id\":\"data/KR/A/A1/A1a\", \"zip\":\"12\"}, "
325      "\"data/KR/A/A2\": "
326      "{\"id\":\"data/KR/A/A2\", \"sub_keys\":\"A2a\", \"zip\":\"1\"}, "
327      // This key, also in state A, but in city A2, matches the ZIP code.
328      "\"data/KR/A/A2/A2a\": "
329      "{\"id\":\"data/KR/A/A2/A2a\", \"zip\":\"123\"}, "
330      // This key, in state B, does not match the ZIP code.
331      "\"data/KR/B\": "
332      "{\"id\":\"data/KR/B\", \"zip\":\"2\"}}"));
333
334  AddressData address;
335  address.region_code = "KR";
336  address.postal_code = "12345";
337  address.administrative_area = "";
338
339  AddressData expected = address;
340  expected.administrative_area = "A";
341  FillAddress(&address);
342  // The ZIP code matches multiple city districts and cities; but only one
343  // state, so we fill this in.
344  EXPECT_EQ(expected, address);
345}
346
347}  // namespace
348