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 "retriever.h" 16 17 #include <libaddressinput/callback.h> 18 #include <libaddressinput/downloader.h> 19 #include <libaddressinput/storage.h> 20 #include <libaddressinput/util/scoped_ptr.h> 21 22 #include <cstddef> 23 #include <ctime> 24 #include <string> 25 26 #include <gtest/gtest.h> 27 28 #include "fake_downloader.h" 29 #include "fake_storage.h" 30 #include "region_data_constants.h" 31 #include "util/string_util.h" 32 33 namespace i18n { 34 namespace addressinput { 35 36 namespace { 37 38 const char kKey[] = "data/CA"; 39 40 // Empty data that the downloader can return. 41 const char kEmptyData[] = "{}"; 42 43 // The MD5 checksum for kEmptyData. Retriever uses MD5 to validate data 44 // integrity. 45 const char kEmptyDataChecksum[] = "99914b932bd37a50b983c5e7c90ae93b"; 46 47 scoped_ptr<std::string> Wrap(const std::string& data, 48 const std::string& checksum, 49 const std::string& timestamp) { 50 return make_scoped_ptr(new std::string( 51 data + "\n" + "checksum=" + checksum + "\n" + "timestamp=" + timestamp)); 52 } 53 54 } // namespace 55 56 // Tests for Retriever object. 57 class RetrieverTest : public testing::Test { 58 protected: 59 RetrieverTest() 60 : storage_(NULL), 61 retriever_(), 62 success_(false), 63 key_(), 64 data_(), 65 reject_empty_data_(false) { 66 ResetRetriever(FakeDownloader::kFakeDataUrl); 67 } 68 69 virtual ~RetrieverTest() {} 70 71 scoped_ptr<Retriever::Callback> BuildCallback() { 72 return ::i18n::addressinput::BuildCallback( 73 this, &RetrieverTest::OnDataReady); 74 } 75 76 void ResetRetriever(const std::string& url) { 77 storage_ = new FakeStorage; 78 retriever_.reset( 79 new Retriever(url, 80 scoped_ptr<Downloader>(new FakeDownloader), 81 scoped_ptr<Storage>(storage_))); 82 } 83 84 std::string GetUrlForKey(const std::string& key) { 85 return retriever_->GetUrlForKey(key); 86 } 87 88 std::string GetKeyForUrl(const std::string& url) { 89 return retriever_->GetKeyForUrl(url); 90 } 91 92 FakeStorage* storage_; // Owned by |retriever_|. 93 scoped_ptr<Retriever> retriever_; 94 bool success_; 95 std::string key_; 96 std::string data_; 97 bool reject_empty_data_; 98 99 private: 100 bool OnDataReady(bool success, 101 const std::string& key, 102 const std::string& data) { 103 success_ = success; 104 key_ = key; 105 data_ = data; 106 return !reject_empty_data_ || data_ != kEmptyData; 107 } 108 }; 109 110 TEST_F(RetrieverTest, RegionHasData) { 111 const std::vector<std::string>& region_codes = 112 RegionDataConstants::GetRegionCodes(); 113 for (size_t i = 0; i < region_codes.size(); ++i) { 114 std::string key = "data/" + region_codes[i]; 115 SCOPED_TRACE("For key: " + key); 116 117 retriever_->Retrieve(key, BuildCallback()); 118 EXPECT_TRUE(success_); 119 EXPECT_EQ(key, key_); 120 EXPECT_FALSE(data_.empty()); 121 EXPECT_NE(kEmptyData, data_); 122 } 123 } 124 125 TEST_F(RetrieverTest, RetrieveData) { 126 retriever_->Retrieve(kKey, BuildCallback()); 127 128 EXPECT_TRUE(success_); 129 EXPECT_EQ(kKey, key_); 130 EXPECT_FALSE(data_.empty()); 131 EXPECT_NE(kEmptyData, data_); 132 } 133 134 TEST_F(RetrieverTest, ReadDataFromStorage) { 135 retriever_->Retrieve(kKey, BuildCallback()); 136 retriever_->Retrieve(kKey, BuildCallback()); 137 138 EXPECT_TRUE(success_); 139 EXPECT_EQ(kKey, key_); 140 EXPECT_FALSE(data_.empty()); 141 EXPECT_NE(kEmptyData, data_); 142 } 143 144 TEST_F(RetrieverTest, MissingKeyReturnsEmptyData) { 145 static const char kMissingKey[] = "junk"; 146 147 retriever_->Retrieve(kMissingKey, BuildCallback()); 148 149 EXPECT_TRUE(success_); 150 EXPECT_EQ(kMissingKey, key_); 151 EXPECT_EQ(kEmptyData, data_); 152 } 153 154 // The downloader that always fails. 155 class FaultyDownloader : public Downloader { 156 public: 157 FaultyDownloader() {} 158 virtual ~FaultyDownloader() {} 159 160 // Downloader implementation. 161 virtual void Download(const std::string& url, 162 scoped_ptr<Callback> downloaded) { 163 (*downloaded)(false, url, make_scoped_ptr(new std::string("garbage"))); 164 } 165 }; 166 167 TEST_F(RetrieverTest, FaultyDownloader) { 168 Retriever bad_retriever(FakeDownloader::kFakeDataUrl, 169 scoped_ptr<Downloader>(new FaultyDownloader), 170 scoped_ptr<Storage>(new FakeStorage)); 171 bad_retriever.Retrieve(kKey, BuildCallback()); 172 173 EXPECT_FALSE(success_); 174 EXPECT_EQ(kKey, key_); 175 EXPECT_TRUE(data_.empty()); 176 } 177 178 TEST_F(RetrieverTest, FaultyDownloaderFallback) { 179 Retriever bad_retriever(FakeDownloader::kFakeDataUrl, 180 scoped_ptr<Downloader>(new FaultyDownloader), 181 scoped_ptr<Storage>(new FakeStorage)); 182 const char kFallbackDataKey[] = "data/US"; 183 bad_retriever.Retrieve(kFallbackDataKey, BuildCallback()); 184 185 EXPECT_TRUE(success_); 186 EXPECT_EQ(kFallbackDataKey, key_); 187 EXPECT_FALSE(data_.empty()); 188 EXPECT_NE(kEmptyData, data_); 189 } 190 191 TEST_F(RetrieverTest, NoChecksumAndTimestampWillRedownload) { 192 storage_->Put(kKey, make_scoped_ptr(new std::string(kEmptyData))); 193 retriever_->Retrieve(kKey, BuildCallback()); 194 EXPECT_TRUE(success_); 195 EXPECT_EQ(kKey, key_); 196 EXPECT_FALSE(data_.empty()); 197 EXPECT_NE(kEmptyData, data_); 198 } 199 200 TEST_F(RetrieverTest, ChecksumAndTimestampWillNotRedownload) { 201 storage_->Put(kKey, 202 Wrap(kEmptyData, kEmptyDataChecksum, TimeToString(time(NULL)))); 203 retriever_->Retrieve(kKey, BuildCallback()); 204 EXPECT_TRUE(success_); 205 EXPECT_EQ(kKey, key_); 206 EXPECT_EQ(kEmptyData, data_); 207 } 208 209 TEST_F(RetrieverTest, OldTimestampWillRedownload) { 210 storage_->Put(kKey, Wrap(kEmptyData, kEmptyDataChecksum, "0")); 211 retriever_->Retrieve(kKey, BuildCallback()); 212 EXPECT_TRUE(success_); 213 EXPECT_EQ(kKey, key_); 214 EXPECT_FALSE(data_.empty()); 215 EXPECT_NE(kEmptyData, data_); 216 } 217 218 TEST_F(RetrieverTest, JunkDataRedownloads) { 219 ResetRetriever(std::string(FakeDownloader::kFakeDataUrl)); 220 storage_->Put(kKey, 221 Wrap(kEmptyData, kEmptyDataChecksum, TimeToString(time(NULL)))); 222 reject_empty_data_ = true; 223 retriever_->Retrieve(kKey, BuildCallback()); 224 EXPECT_TRUE(success_); 225 EXPECT_EQ(kKey, key_); 226 EXPECT_FALSE(data_.empty()); 227 EXPECT_NE(kEmptyData, data_); 228 229 // After verifying it's correct, it's saved in storage. 230 EXPECT_EQ(data_, storage_->SynchronousGet(kKey).substr(0, data_.size())); 231 } 232 233 TEST_F(RetrieverTest, JunkDataIsntStored) { 234 // Data the retriever accepts is stored in |storage_|. 235 ResetRetriever("test:///"); 236 const std::string not_a_key("foobar"); 237 retriever_->Retrieve(not_a_key, BuildCallback()); 238 EXPECT_TRUE(success_); 239 EXPECT_EQ(not_a_key, key_); 240 EXPECT_FALSE(data_.empty()); 241 EXPECT_EQ(kEmptyData, data_); 242 EXPECT_EQ( 243 kEmptyData, 244 storage_->SynchronousGet(not_a_key).substr(0, sizeof kEmptyData - 1)); 245 246 // Try again, but this time reject the data. 247 reject_empty_data_ = true; 248 ResetRetriever("test:///"); 249 EXPECT_EQ("", storage_->SynchronousGet(not_a_key)); 250 retriever_->Retrieve(not_a_key, BuildCallback()); 251 252 // Falls back to the fallback, which doesn't have data for Canada. 253 EXPECT_FALSE(success_); 254 EXPECT_EQ(not_a_key, key_); 255 EXPECT_TRUE(data_.empty()); 256 257 // Since the retriever is rejecting empty data, it shouldn't be stored. 258 EXPECT_EQ("", storage_->SynchronousGet(not_a_key)); 259 } 260 261 TEST_F(RetrieverTest, OldTimestampOkIfDownloadFails) { 262 storage_ = new FakeStorage; 263 Retriever bad_retriever(FakeDownloader::kFakeDataUrl, 264 scoped_ptr<Downloader>(new FaultyDownloader), 265 scoped_ptr<Storage>(storage_)); 266 storage_->Put(kKey, Wrap(kEmptyData, kEmptyDataChecksum, "0")); 267 bad_retriever.Retrieve(kKey, BuildCallback()); 268 EXPECT_TRUE(success_); 269 EXPECT_EQ(kKey, key_); 270 EXPECT_EQ(kEmptyData, data_); 271 } 272 273 TEST_F(RetrieverTest, WrongChecksumWillRedownload) { 274 static const char kNonEmptyData[] = "{\"non-empty\": \"data\"}"; 275 storage_->Put( 276 kKey, 277 Wrap(kNonEmptyData, kEmptyDataChecksum, TimeToString(time(NULL)))); 278 retriever_->Retrieve(kKey, BuildCallback()); 279 EXPECT_TRUE(success_); 280 EXPECT_EQ(kKey, key_); 281 EXPECT_FALSE(data_.empty()); 282 EXPECT_NE(kNonEmptyData, data_); 283 } 284 285 // The downloader that doesn't get back to you. 286 class HangingDownloader : public Downloader { 287 public: 288 HangingDownloader() {} 289 virtual ~HangingDownloader() {} 290 291 // Downloader implementation. 292 virtual void Download(const std::string& url, 293 scoped_ptr<Callback> downloaded) {} 294 }; 295 296 TEST_F(RetrieverTest, RequestsDontStack) { 297 Retriever slow_retriever(FakeDownloader::kFakeDataUrl, 298 scoped_ptr<Downloader>(new HangingDownloader), 299 scoped_ptr<Storage>(new FakeStorage)); 300 301 slow_retriever.Retrieve(kKey, BuildCallback()); 302 EXPECT_FALSE(success_); 303 EXPECT_TRUE(key_.empty()); 304 305 EXPECT_NO_FATAL_FAILURE(slow_retriever.Retrieve(kKey, BuildCallback())); 306 } 307 308 TEST_F(RetrieverTest, GetUrlForKey) { 309 ResetRetriever("test:///"); 310 EXPECT_EQ("test:///", GetUrlForKey("")); 311 EXPECT_EQ("test:///data", GetUrlForKey("data")); 312 EXPECT_EQ("test:///data/US", GetUrlForKey("data/US")); 313 EXPECT_EQ("test:///data/CA--fr", GetUrlForKey("data/CA--fr")); 314 } 315 316 TEST_F(RetrieverTest, GetKeyForUrl) { 317 ResetRetriever("test:///"); 318 EXPECT_EQ("", GetKeyForUrl("test://")); 319 EXPECT_EQ("", GetKeyForUrl("http://www.google.com/")); 320 EXPECT_EQ("", GetKeyForUrl("")); 321 EXPECT_EQ("", GetKeyForUrl("test:///")); 322 EXPECT_EQ("data", GetKeyForUrl("test:///data")); 323 EXPECT_EQ("data/US", GetKeyForUrl("test:///data/US")); 324 EXPECT_EQ("data/CA--fr", GetKeyForUrl("test:///data/CA--fr")); 325 } 326 327 TEST_F(RetrieverTest, NullCallbackNoCrash) { 328 ASSERT_NO_FATAL_FAILURE( 329 retriever_->Retrieve(kKey, scoped_ptr<Retriever::Callback>())); 330 ASSERT_NO_FATAL_FAILURE( 331 retriever_->Retrieve(kKey, scoped_ptr<Retriever::Callback>())); 332 } 333 334 } // namespace addressinput 335 } // namespace i18n 336