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