1 // Copyright 2015 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 "components/offline_pages/core/offline_page_metadata_store.h"
6
7 #include <stdint.h>
8 #include <memory>
9 #include <set>
10 #include <string>
11 #include <utility>
12
13 #include "base/bind.h"
14 #include "base/files/file_path.h"
15 #include "base/files/scoped_temp_dir.h"
16 #include "base/memory/ref_counted.h"
17 #include "base/strings/string_number_conversions.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "base/test/bind.h"
20 #include "base/test/test_mock_time_task_runner.h"
21 #include "base/threading/thread_task_runner_handle.h"
22 #include "components/offline_pages/core/client_namespace_constants.h"
23 #include "components/offline_pages/core/model/offline_page_item_generator.h"
24 #include "components/offline_pages/core/offline_clock.h"
25 #include "components/offline_pages/core/offline_page_item.h"
26 #include "components/offline_pages/core/offline_page_model.h"
27 #include "components/offline_pages/core/offline_page_visuals.h"
28 #include "components/offline_pages/core/offline_store_utils.h"
29 #include "sql/database.h"
30 #include "sql/meta_table.h"
31 #include "sql/statement.h"
32 #include "sql/transaction.h"
33 #include "testing/gtest/include/gtest/gtest.h"
34
35 namespace offline_pages {
36
37 namespace {
38 using InitializationStatus = SqlStoreBase::InitializationStatus;
39 using OfflinePageSet = std::set<OfflinePageItem>;
40
41 #define OFFLINE_PAGES_TABLE_V1 "offlinepages_v1"
42
43 const char kTestClientNamespace[] = "CLIENT_NAMESPACE";
44 const char kTestURL[] = "https://example.com";
45 const char kOriginalTestURL[] = "https://example.com/foo";
46 const ClientId kTestClientId1(kTestClientNamespace, "1234");
47 const ClientId kTestClientId2(kTestClientNamespace, "5678");
48 const base::FilePath::CharType kFilePath[] =
49 FILE_PATH_LITERAL("/offline_pages/example_com.mhtml");
50 int64_t kFileSize = 234567LL;
51 int64_t kOfflineId = 12345LL;
52 const char kTestRequestOrigin[] = "request.origin";
53 int64_t kTestSystemDownloadId = 42LL;
54 const char kTestDigest[] = "test-digest";
55 const base::Time kVisualsExpiration = store_utils::FromDatabaseTime(42);
56 const char kTestSnippet[] = "test snippet";
57 const char kTestAttribution[] = "test attribution";
58
TestVisuals()59 OfflinePageVisuals TestVisuals() {
60 return {1, base::Time(), "abc", "123"};
61 }
62
63 // Build a store with outdated schema to simulate the upgrading process.
BuildTestStoreWithSchemaFromM52(const base::FilePath & file)64 void BuildTestStoreWithSchemaFromM52(const base::FilePath& file) {
65 sql::Database connection;
66 ASSERT_TRUE(
67 connection.Open(file.Append(FILE_PATH_LITERAL("OfflinePages.db"))));
68 ASSERT_TRUE(connection.is_open());
69 ASSERT_TRUE(connection.BeginTransaction());
70 ASSERT_TRUE(connection.Execute("CREATE TABLE " OFFLINE_PAGES_TABLE_V1
71 "(offline_id INTEGER PRIMARY KEY NOT NULL, "
72 "creation_time INTEGER NOT NULL, "
73 "file_size INTEGER NOT NULL, "
74 "version INTEGER NOT NULL, "
75 "last_access_time INTEGER NOT NULL, "
76 "access_count INTEGER NOT NULL, "
77 "status INTEGER NOT NULL DEFAULT 0, "
78 "user_initiated INTEGER, "
79 "client_namespace VARCHAR NOT NULL, "
80 "client_id VARCHAR NOT NULL, "
81 "online_url VARCHAR NOT NULL, "
82 "offline_url VARCHAR NOT NULL DEFAULT '', "
83 "file_path VARCHAR NOT NULL "
84 ")"));
85 ASSERT_TRUE(connection.CommitTransaction());
86 sql::Statement statement(connection.GetUniqueStatement(
87 "INSERT INTO " OFFLINE_PAGES_TABLE_V1
88 "(offline_id, creation_time, file_size, version, "
89 "last_access_time, access_count, client_namespace, "
90 "client_id, online_url, file_path) "
91 "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"));
92 statement.BindInt64(0, kOfflineId);
93 statement.BindInt(1, 0);
94 statement.BindInt64(2, kFileSize);
95 statement.BindInt(3, 0);
96 statement.BindInt(4, 0);
97 statement.BindInt(5, 1);
98 statement.BindCString(6, kTestClientNamespace);
99 statement.BindString(7, kTestClientId2.id);
100 statement.BindCString(8, kTestURL);
101 statement.BindString(9, base::FilePath(kFilePath).MaybeAsASCII());
102 ASSERT_TRUE(statement.Run());
103 ASSERT_TRUE(connection.DoesTableExist(OFFLINE_PAGES_TABLE_V1));
104 ASSERT_FALSE(
105 connection.DoesColumnExist(OFFLINE_PAGES_TABLE_V1, "expiration_time"));
106 }
107
BuildTestStoreWithSchemaFromM53(const base::FilePath & file)108 void BuildTestStoreWithSchemaFromM53(const base::FilePath& file) {
109 sql::Database connection;
110 ASSERT_TRUE(
111 connection.Open(file.Append(FILE_PATH_LITERAL("OfflinePages.db"))));
112 ASSERT_TRUE(connection.is_open());
113 ASSERT_TRUE(connection.BeginTransaction());
114 ASSERT_TRUE(connection.Execute("CREATE TABLE " OFFLINE_PAGES_TABLE_V1
115 "(offline_id INTEGER PRIMARY KEY NOT NULL, "
116 "creation_time INTEGER NOT NULL, "
117 "file_size INTEGER NOT NULL, "
118 "version INTEGER NOT NULL, "
119 "last_access_time INTEGER NOT NULL, "
120 "access_count INTEGER NOT NULL, "
121 "status INTEGER NOT NULL DEFAULT 0, "
122 "user_initiated INTEGER, "
123 "expiration_time INTEGER NOT NULL DEFAULT 0, "
124 "client_namespace VARCHAR NOT NULL, "
125 "client_id VARCHAR NOT NULL, "
126 "online_url VARCHAR NOT NULL, "
127 "offline_url VARCHAR NOT NULL DEFAULT '', "
128 "file_path VARCHAR NOT NULL "
129 ")"));
130 ASSERT_TRUE(connection.CommitTransaction());
131 sql::Statement statement(connection.GetUniqueStatement(
132 "INSERT INTO " OFFLINE_PAGES_TABLE_V1
133 "(offline_id, creation_time, file_size, version, "
134 "last_access_time, access_count, client_namespace, "
135 "client_id, online_url, file_path, expiration_time) "
136 "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"));
137 statement.BindInt64(0, kOfflineId);
138 statement.BindInt(1, 0);
139 statement.BindInt64(2, kFileSize);
140 statement.BindInt(3, 0);
141 statement.BindInt(4, 0);
142 statement.BindInt(5, 1);
143 statement.BindCString(6, kTestClientNamespace);
144 statement.BindString(7, kTestClientId2.id);
145 statement.BindCString(8, kTestURL);
146 statement.BindString(9, base::FilePath(kFilePath).MaybeAsASCII());
147 statement.BindInt64(10, store_utils::ToDatabaseTime(OfflineTimeNow()));
148 ASSERT_TRUE(statement.Run());
149 ASSERT_TRUE(connection.DoesTableExist(OFFLINE_PAGES_TABLE_V1));
150 ASSERT_FALSE(connection.DoesColumnExist(OFFLINE_PAGES_TABLE_V1, "title"));
151 }
152
BuildTestStoreWithSchemaFromM54(const base::FilePath & file)153 void BuildTestStoreWithSchemaFromM54(const base::FilePath& file) {
154 sql::Database connection;
155 ASSERT_TRUE(
156 connection.Open(file.Append(FILE_PATH_LITERAL("OfflinePages.db"))));
157 ASSERT_TRUE(connection.is_open());
158 ASSERT_TRUE(connection.BeginTransaction());
159 ASSERT_TRUE(connection.Execute("CREATE TABLE " OFFLINE_PAGES_TABLE_V1
160 "(offline_id INTEGER PRIMARY KEY NOT NULL, "
161 "creation_time INTEGER NOT NULL, "
162 "file_size INTEGER NOT NULL, "
163 "version INTEGER NOT NULL, "
164 "last_access_time INTEGER NOT NULL, "
165 "access_count INTEGER NOT NULL, "
166 "status INTEGER NOT NULL DEFAULT 0, "
167 "user_initiated INTEGER, "
168 "expiration_time INTEGER NOT NULL DEFAULT 0, "
169 "client_namespace VARCHAR NOT NULL, "
170 "client_id VARCHAR NOT NULL, "
171 "online_url VARCHAR NOT NULL, "
172 "offline_url VARCHAR NOT NULL DEFAULT '', "
173 "file_path VARCHAR NOT NULL, "
174 "title VARCHAR NOT NULL DEFAULT ''"
175 ")"));
176 ASSERT_TRUE(connection.CommitTransaction());
177 sql::Statement statement(connection.GetUniqueStatement(
178 "INSERT INTO " OFFLINE_PAGES_TABLE_V1
179 "(offline_id, creation_time, file_size, version, "
180 "last_access_time, access_count, client_namespace, "
181 "client_id, online_url, file_path, expiration_time, title) "
182 "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"));
183 statement.BindInt64(0, kOfflineId);
184 statement.BindInt(1, 0);
185 statement.BindInt64(2, kFileSize);
186 statement.BindInt(3, 0);
187 statement.BindInt(4, 0);
188 statement.BindInt(5, 1);
189 statement.BindCString(6, kTestClientNamespace);
190 statement.BindString(7, kTestClientId2.id);
191 statement.BindCString(8, kTestURL);
192 statement.BindString(9, base::FilePath(kFilePath).MaybeAsASCII());
193 statement.BindInt64(10, store_utils::ToDatabaseTime(OfflineTimeNow()));
194 statement.BindString16(11, base::UTF8ToUTF16("Test title"));
195 ASSERT_TRUE(statement.Run());
196 ASSERT_TRUE(connection.DoesTableExist(OFFLINE_PAGES_TABLE_V1));
197 ASSERT_TRUE(connection.DoesColumnExist(OFFLINE_PAGES_TABLE_V1, "version"));
198 ASSERT_TRUE(connection.DoesColumnExist(OFFLINE_PAGES_TABLE_V1, "status"));
199 ASSERT_TRUE(
200 connection.DoesColumnExist(OFFLINE_PAGES_TABLE_V1, "user_initiated"));
201 ASSERT_TRUE(
202 connection.DoesColumnExist(OFFLINE_PAGES_TABLE_V1, "offline_url"));
203 }
204
BuildTestStoreWithSchemaFromM55(const base::FilePath & file)205 void BuildTestStoreWithSchemaFromM55(const base::FilePath& file) {
206 sql::Database connection;
207 ASSERT_TRUE(
208 connection.Open(file.Append(FILE_PATH_LITERAL("OfflinePages.db"))));
209 ASSERT_TRUE(connection.is_open());
210 ASSERT_TRUE(connection.BeginTransaction());
211 ASSERT_TRUE(connection.Execute("CREATE TABLE " OFFLINE_PAGES_TABLE_V1
212 "(offline_id INTEGER PRIMARY KEY NOT NULL, "
213 "creation_time INTEGER NOT NULL, "
214 "file_size INTEGER NOT NULL, "
215 "last_access_time INTEGER NOT NULL, "
216 "access_count INTEGER NOT NULL, "
217 "expiration_time INTEGER NOT NULL DEFAULT 0, "
218 "client_namespace VARCHAR NOT NULL, "
219 "client_id VARCHAR NOT NULL, "
220 "online_url VARCHAR NOT NULL, "
221 "file_path VARCHAR NOT NULL, "
222 "title VARCHAR NOT NULL DEFAULT ''"
223 ")"));
224 ASSERT_TRUE(connection.CommitTransaction());
225 sql::Statement statement(connection.GetUniqueStatement(
226 "INSERT INTO " OFFLINE_PAGES_TABLE_V1
227 "(offline_id, creation_time, file_size, "
228 "last_access_time, access_count, client_namespace, "
229 "client_id, online_url, file_path, expiration_time, title) "
230 "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"));
231 statement.BindInt64(0, kOfflineId);
232 statement.BindInt(1, 0);
233 statement.BindInt64(2, kFileSize);
234 statement.BindInt(3, 0);
235 statement.BindInt(4, 1);
236 statement.BindCString(5, kTestClientNamespace);
237 statement.BindString(6, kTestClientId2.id);
238 statement.BindCString(7, kTestURL);
239 statement.BindString(8, base::FilePath(kFilePath).MaybeAsASCII());
240 statement.BindInt64(9, store_utils::ToDatabaseTime(OfflineTimeNow()));
241 statement.BindString16(10, base::UTF8ToUTF16("Test title"));
242 ASSERT_TRUE(statement.Run());
243 ASSERT_TRUE(connection.DoesTableExist(OFFLINE_PAGES_TABLE_V1));
244 ASSERT_TRUE(connection.DoesColumnExist(OFFLINE_PAGES_TABLE_V1, "title"));
245 ASSERT_FALSE(
246 connection.DoesColumnExist(OFFLINE_PAGES_TABLE_V1, "original_url"));
247 }
248
BuildTestStoreWithSchemaFromM56(const base::FilePath & file)249 void BuildTestStoreWithSchemaFromM56(const base::FilePath& file) {
250 sql::Database connection;
251 ASSERT_TRUE(
252 connection.Open(file.Append(FILE_PATH_LITERAL("OfflinePages.db"))));
253 ASSERT_TRUE(connection.is_open());
254 ASSERT_TRUE(connection.BeginTransaction());
255 ASSERT_TRUE(connection.Execute("CREATE TABLE " OFFLINE_PAGES_TABLE_V1
256 "(offline_id INTEGER PRIMARY KEY NOT NULL, "
257 "creation_time INTEGER NOT NULL, "
258 "file_size INTEGER NOT NULL, "
259 "last_access_time INTEGER NOT NULL, "
260 "access_count INTEGER NOT NULL, "
261 "expiration_time INTEGER NOT NULL DEFAULT 0, "
262 "client_namespace VARCHAR NOT NULL, "
263 "client_id VARCHAR NOT NULL, "
264 "online_url VARCHAR NOT NULL, "
265 "file_path VARCHAR NOT NULL, "
266 "title VARCHAR NOT NULL DEFAULT '', "
267 "original_url VARCHAR NOT NULL DEFAULT ''"
268 ")"));
269 ASSERT_TRUE(connection.CommitTransaction());
270 sql::Statement statement(connection.GetUniqueStatement(
271 "INSERT INTO " OFFLINE_PAGES_TABLE_V1
272 "(offline_id, creation_time, file_size, "
273 "last_access_time, access_count, client_namespace, "
274 "client_id, online_url, file_path, expiration_time, title, original_url) "
275 "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"));
276 statement.BindInt64(0, kOfflineId);
277 statement.BindInt(1, 0);
278 statement.BindInt64(2, kFileSize);
279 statement.BindInt(3, 0);
280 statement.BindInt(4, 1);
281 statement.BindCString(5, kTestClientNamespace);
282 statement.BindString(6, kTestClientId2.id);
283 statement.BindCString(7, kTestURL);
284 statement.BindString(8, base::FilePath(kFilePath).MaybeAsASCII());
285 statement.BindInt64(9, store_utils::ToDatabaseTime(OfflineTimeNow()));
286 statement.BindString16(10, base::UTF8ToUTF16("Test title"));
287 statement.BindCString(11, kOriginalTestURL);
288 ASSERT_TRUE(statement.Run());
289 ASSERT_TRUE(connection.DoesTableExist(OFFLINE_PAGES_TABLE_V1));
290 ASSERT_TRUE(
291 connection.DoesColumnExist(OFFLINE_PAGES_TABLE_V1, "expiration_time"));
292 }
293
BuildTestStoreWithSchemaFromM57(const base::FilePath & file)294 void BuildTestStoreWithSchemaFromM57(const base::FilePath& file) {
295 sql::Database connection;
296 ASSERT_TRUE(
297 connection.Open(file.Append(FILE_PATH_LITERAL("OfflinePages.db"))));
298 ASSERT_TRUE(connection.is_open());
299 ASSERT_TRUE(connection.BeginTransaction());
300 ASSERT_TRUE(connection.Execute("CREATE TABLE " OFFLINE_PAGES_TABLE_V1
301 "(offline_id INTEGER PRIMARY KEY NOT NULL,"
302 " creation_time INTEGER NOT NULL,"
303 " file_size INTEGER NOT NULL,"
304 " last_access_time INTEGER NOT NULL,"
305 " access_count INTEGER NOT NULL,"
306 " client_namespace VARCHAR NOT NULL,"
307 " client_id VARCHAR NOT NULL,"
308 " online_url VARCHAR NOT NULL,"
309 " file_path VARCHAR NOT NULL,"
310 " title VARCHAR NOT NULL DEFAULT '',"
311 " original_url VARCHAR NOT NULL DEFAULT ''"
312 ")"));
313 ASSERT_TRUE(connection.CommitTransaction());
314 sql::Statement statement(connection.GetUniqueStatement(
315 "INSERT INTO " OFFLINE_PAGES_TABLE_V1
316 "(offline_id, creation_time, file_size, "
317 "last_access_time, access_count, client_namespace, "
318 "client_id, online_url, file_path, title, original_url) "
319 "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"));
320 statement.BindInt64(0, kOfflineId);
321 statement.BindInt(1, 0);
322 statement.BindInt64(2, kFileSize);
323 statement.BindInt(3, 0);
324 statement.BindInt(4, 1);
325 statement.BindCString(5, kTestClientNamespace);
326 statement.BindString(6, kTestClientId2.id);
327 statement.BindCString(7, kTestURL);
328 statement.BindString(8, base::FilePath(kFilePath).MaybeAsASCII());
329 statement.BindString16(9, base::UTF8ToUTF16("Test title"));
330 statement.BindCString(10, kOriginalTestURL);
331 ASSERT_TRUE(statement.Run());
332 ASSERT_TRUE(connection.DoesTableExist(OFFLINE_PAGES_TABLE_V1));
333 ASSERT_FALSE(
334 connection.DoesColumnExist(OFFLINE_PAGES_TABLE_V1, "request_origin"));
335 }
336
BuildTestStoreWithSchemaFromM61(const base::FilePath & file)337 void BuildTestStoreWithSchemaFromM61(const base::FilePath& file) {
338 sql::Database connection;
339 ASSERT_TRUE(
340 connection.Open(file.Append(FILE_PATH_LITERAL("OfflinePages.db"))));
341 ASSERT_TRUE(connection.is_open());
342 ASSERT_TRUE(connection.BeginTransaction());
343 ASSERT_TRUE(connection.Execute("CREATE TABLE " OFFLINE_PAGES_TABLE_V1
344 "(offline_id INTEGER PRIMARY KEY NOT NULL,"
345 " creation_time INTEGER NOT NULL,"
346 " file_size INTEGER NOT NULL,"
347 " last_access_time INTEGER NOT NULL,"
348 " access_count INTEGER NOT NULL,"
349 " client_namespace VARCHAR NOT NULL,"
350 " client_id VARCHAR NOT NULL,"
351 " online_url VARCHAR NOT NULL,"
352 " file_path VARCHAR NOT NULL,"
353 " title VARCHAR NOT NULL DEFAULT '',"
354 " original_url VARCHAR NOT NULL DEFAULT '',"
355 " request_origin VARCHAR NOT NULL DEFAULT ''"
356 ")"));
357 ASSERT_TRUE(connection.CommitTransaction());
358 sql::Statement statement(connection.GetUniqueStatement(
359 "INSERT INTO " OFFLINE_PAGES_TABLE_V1
360 "(offline_id, creation_time, file_size, "
361 "last_access_time, access_count, client_namespace, "
362 "client_id, online_url, file_path, title, original_url, "
363 "request_origin) "
364 "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"));
365 statement.BindInt64(0, kOfflineId);
366 statement.BindInt(1, 0);
367 statement.BindInt64(2, kFileSize);
368 statement.BindInt(3, 0);
369 statement.BindInt(4, 1);
370 statement.BindCString(5, kTestClientNamespace);
371 statement.BindString(6, kTestClientId2.id);
372 statement.BindCString(7, kTestURL);
373 statement.BindString(8, base::FilePath(kFilePath).MaybeAsASCII());
374 statement.BindString16(9, base::UTF8ToUTF16("Test title"));
375 statement.BindCString(10, kOriginalTestURL);
376 statement.BindString(11, kTestRequestOrigin);
377 ASSERT_TRUE(statement.Run());
378 ASSERT_TRUE(connection.DoesTableExist(OFFLINE_PAGES_TABLE_V1));
379 ASSERT_FALSE(connection.DoesColumnExist(OFFLINE_PAGES_TABLE_V1, "digest"));
380 }
381
InjectItemInM62Store(sql::Database * db,const OfflinePageItem & item)382 void InjectItemInM62Store(sql::Database* db, const OfflinePageItem& item) {
383 ASSERT_TRUE(db->BeginTransaction());
384 sql::Statement statement(db->GetUniqueStatement(
385 "INSERT INTO " OFFLINE_PAGES_TABLE_V1
386 "(offline_id, creation_time, file_size, "
387 "last_access_time, access_count, client_namespace, "
388 "client_id, online_url, file_path, title, original_url, "
389 "request_origin, system_download_id, file_missing_time, "
390 "digest) "
391 "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"));
392 statement.BindInt64(0, item.offline_id);
393 statement.BindInt64(1, store_utils::ToDatabaseTime(item.creation_time));
394 statement.BindInt64(2, item.file_size);
395 statement.BindInt64(3, store_utils::ToDatabaseTime(item.last_access_time));
396 statement.BindInt(4, item.access_count);
397 statement.BindString(5, item.client_id.name_space);
398 statement.BindString(6, item.client_id.id);
399 statement.BindString(7, item.url.spec());
400 statement.BindString(8, store_utils::ToDatabaseFilePath(item.file_path));
401 statement.BindString16(9, item.title);
402 statement.BindString(10, item.original_url_if_different.spec());
403 statement.BindString(11, item.request_origin);
404 statement.BindInt64(12, item.system_download_id);
405 statement.BindInt64(13, store_utils::ToDatabaseTime(item.file_missing_time));
406 statement.BindString(14, item.digest);
407 ASSERT_TRUE(statement.Run());
408 ASSERT_TRUE(db->CommitTransaction());
409 }
410
BuildTestStoreWithSchemaFromM62(const base::FilePath & file)411 void BuildTestStoreWithSchemaFromM62(const base::FilePath& file) {
412 sql::Database connection;
413 ASSERT_TRUE(
414 connection.Open(file.Append(FILE_PATH_LITERAL("OfflinePages.db"))));
415 ASSERT_TRUE(connection.is_open());
416 ASSERT_TRUE(connection.BeginTransaction());
417 ASSERT_TRUE(
418 connection.Execute("CREATE TABLE " OFFLINE_PAGES_TABLE_V1
419 "(offline_id INTEGER PRIMARY KEY NOT NULL,"
420 " creation_time INTEGER NOT NULL,"
421 " file_size INTEGER NOT NULL,"
422 " last_access_time INTEGER NOT NULL,"
423 " access_count INTEGER NOT NULL,"
424 " system_download_id INTEGER NOT NULL DEFAULT 0,"
425 " file_missing_time INTEGER NOT NULL DEFAULT 0,"
426 " upgrade_attempt INTEGER NOT NULL DEFAULT 0,"
427 " client_namespace VARCHAR NOT NULL,"
428 " client_id VARCHAR NOT NULL,"
429 " online_url VARCHAR NOT NULL,"
430 " file_path VARCHAR NOT NULL,"
431 " title VARCHAR NOT NULL DEFAULT '',"
432 " original_url VARCHAR NOT NULL DEFAULT '',"
433 " request_origin VARCHAR NOT NULL DEFAULT '',"
434 " digest VARCHAR NOT NULL DEFAULT ''"
435 ")"));
436 ASSERT_TRUE(connection.CommitTransaction());
437
438 OfflinePageItemGenerator generator;
439 generator.SetNamespace(kTestClientNamespace);
440 generator.SetId(kTestClientId2.id);
441 generator.SetUrl(GURL(kTestURL));
442 generator.SetRequestOrigin(kTestRequestOrigin);
443 generator.SetFileSize(kFileSize);
444 OfflinePageItem test_item = generator.CreateItem();
445 test_item.offline_id = kOfflineId;
446 test_item.file_path = base::FilePath(kFilePath);
447 InjectItemInM62Store(&connection, test_item);
448 }
449
BuildTestStoreWithSchemaVersion1(const base::FilePath & file)450 void BuildTestStoreWithSchemaVersion1(const base::FilePath& file) {
451 BuildTestStoreWithSchemaFromM62(file);
452 sql::Database connection;
453 ASSERT_TRUE(
454 connection.Open(file.Append(FILE_PATH_LITERAL("OfflinePages.db"))));
455 ASSERT_TRUE(connection.is_open());
456 ASSERT_TRUE(connection.BeginTransaction());
457 sql::MetaTable meta_table;
458 ASSERT_TRUE(meta_table.Init(&connection, 1, 1));
459 ASSERT_TRUE(connection.CommitTransaction());
460
461 OfflinePageItemGenerator generator;
462 generator.SetUrl(GURL(kTestURL));
463 generator.SetRequestOrigin(kTestRequestOrigin);
464 generator.SetFileSize(kFileSize);
465
466 generator.SetNamespace(kAsyncNamespace);
467 InjectItemInM62Store(&connection, generator.CreateItem());
468 generator.SetNamespace(kDownloadNamespace);
469 InjectItemInM62Store(&connection, generator.CreateItem());
470 generator.SetNamespace(kBrowserActionsNamespace);
471 InjectItemInM62Store(&connection, generator.CreateItem());
472 generator.SetNamespace(kNTPSuggestionsNamespace);
473 InjectItemInM62Store(&connection, generator.CreateItem());
474 }
475
BuildTestStoreWithSchemaVersion2(const base::FilePath & file)476 void BuildTestStoreWithSchemaVersion2(const base::FilePath& file) {
477 BuildTestStoreWithSchemaVersion1(file);
478 sql::Database db;
479 ASSERT_TRUE(db.Open(file.Append(FILE_PATH_LITERAL("OfflinePages.db"))));
480 sql::MetaTable meta_table;
481 ASSERT_TRUE(
482 meta_table.Init(&db, 2, OfflinePageMetadataStore::kCompatibleVersion));
483 }
484
InsertVisualsVersion3(sql::Database * db,const OfflinePageVisuals & visuals)485 bool InsertVisualsVersion3(sql::Database* db,
486 const OfflinePageVisuals& visuals) {
487 static const char kInsertVisualsSql[] =
488 "INSERT INTO page_thumbnails"
489 " (offline_id,expiration,thumbnail) VALUES (?,?,?)";
490 sql::Statement statement(
491 db->GetCachedStatement(SQL_FROM_HERE, kInsertVisualsSql));
492 statement.BindInt64(0, visuals.offline_id);
493 statement.BindInt64(1, store_utils::ToDatabaseTime(visuals.expiration));
494 statement.BindBlob(2, visuals.thumbnail.data(), visuals.thumbnail.size());
495 return statement.Run();
496 }
497
BuildTestStoreWithSchemaVersion3(const base::FilePath & file)498 void BuildTestStoreWithSchemaVersion3(const base::FilePath& file) {
499 BuildTestStoreWithSchemaVersion2(file);
500 sql::Database db;
501 ASSERT_TRUE(db.Open(file.Append(FILE_PATH_LITERAL("OfflinePages.db"))));
502 sql::MetaTable meta_table;
503 ASSERT_TRUE(
504 meta_table.Init(&db, 3, OfflinePageMetadataStore::kCompatibleVersion));
505
506 static const char kSql[] =
507 "CREATE TABLE page_thumbnails"
508 " (offline_id INTEGER PRIMARY KEY NOT NULL,"
509 " expiration INTEGER NOT NULL,"
510 " thumbnail BLOB NOT NULL"
511 ");";
512 ASSERT_TRUE(db.Execute(kSql));
513 ASSERT_TRUE(InsertVisualsVersion3(&db, TestVisuals()));
514 }
515
516 // Create an offline page item from a SQL result. Expects complete rows with
517 // all columns present.
MakeOfflinePageItem(sql::Statement * statement)518 OfflinePageItem MakeOfflinePageItem(sql::Statement* statement) {
519 int64_t id = statement->ColumnInt64(0);
520 base::Time creation_time =
521 store_utils::FromDatabaseTime(statement->ColumnInt64(1));
522 int64_t file_size = statement->ColumnInt64(2);
523 base::Time last_access_time =
524 store_utils::FromDatabaseTime(statement->ColumnInt64(3));
525 int access_count = statement->ColumnInt(4);
526 int64_t system_download_id = statement->ColumnInt64(5);
527 base::Time file_missing_time =
528 store_utils::FromDatabaseTime(statement->ColumnInt64(6));
529 // Column 7 is deprecated 'upgrade_attempt'.
530 ClientId client_id(statement->ColumnString(8), statement->ColumnString(9));
531 GURL url(statement->ColumnString(10));
532 base::FilePath path(
533 store_utils::FromDatabaseFilePath(statement->ColumnString(11)));
534 base::string16 title = statement->ColumnString16(12);
535 GURL original_url(statement->ColumnString(13));
536 std::string request_origin = statement->ColumnString(14);
537 std::string digest = statement->ColumnString(15);
538 std::string snippet = statement->ColumnString(16);
539 std::string attribution = statement->ColumnString(17);
540
541 OfflinePageItem item(url, id, client_id, path, file_size, creation_time);
542 item.last_access_time = last_access_time;
543 item.access_count = access_count;
544 item.title = title;
545 item.original_url_if_different = original_url;
546 item.request_origin = request_origin;
547 item.system_download_id = system_download_id;
548 item.file_missing_time = file_missing_time;
549 item.digest = digest;
550 item.snippet = snippet;
551 item.attribution = attribution;
552 return item;
553 }
554
GetOfflinePagesSync(sql::Database * db)555 std::vector<OfflinePageItem> GetOfflinePagesSync(sql::Database* db) {
556 std::vector<OfflinePageItem> result;
557
558 static const char kSql[] = "SELECT * FROM " OFFLINE_PAGES_TABLE_V1;
559 sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql));
560
561 while (statement.Step())
562 result.push_back(MakeOfflinePageItem(&statement));
563
564 if (!statement.Succeeded()) {
565 result.clear();
566 }
567 return result;
568 }
569
570 class OfflinePageMetadataStoreTest : public testing::Test {
571 public:
OfflinePageMetadataStoreTest()572 OfflinePageMetadataStoreTest()
573 : task_runner_(new base::TestMockTimeTaskRunner),
574 task_runner_handle_(task_runner_) {
575 EXPECT_TRUE(temp_directory_.CreateUniqueTempDir());
576 }
~OfflinePageMetadataStoreTest()577 ~OfflinePageMetadataStoreTest() override {}
578
579 protected:
TearDown()580 void TearDown() override {
581 // Wait for all the pieces of the store to delete itself properly.
582 PumpLoop();
583 }
584
BuildStore()585 std::unique_ptr<OfflinePageMetadataStore> BuildStore() {
586 auto store = std::make_unique<OfflinePageMetadataStore>(
587 base::ThreadTaskRunnerHandle::Get(), TempPath());
588 PumpLoop();
589 return store;
590 }
591
PumpLoop()592 void PumpLoop() { task_runner_->RunUntilIdle(); }
FastForwardBy(base::TimeDelta delta)593 void FastForwardBy(base::TimeDelta delta) {
594 task_runner_->FastForwardBy(delta);
595 }
596
task_runner() const597 base::TestMockTimeTaskRunner* task_runner() const {
598 return task_runner_.get();
599 }
TempPath() const600 base::FilePath TempPath() const { return temp_directory_.GetPath(); }
601
CheckThatStoreHasOneItem(OfflinePageMetadataStore * store)602 OfflinePageItem CheckThatStoreHasOneItem(OfflinePageMetadataStore* store) {
603 std::vector<OfflinePageItem> pages = GetOfflinePages(store);
604 EXPECT_EQ(1U, pages.size());
605 return pages[0];
606 }
607
CheckThatOfflinePageCanBeSaved(std::unique_ptr<OfflinePageMetadataStore> store)608 void CheckThatOfflinePageCanBeSaved(
609 std::unique_ptr<OfflinePageMetadataStore> store) {
610 size_t store_size = GetOfflinePages(store.get()).size();
611 OfflinePageItem offline_page(GURL(kTestURL), 1234LL, kTestClientId1,
612 base::FilePath(kFilePath), kFileSize);
613 offline_page.title = base::UTF8ToUTF16("a title");
614 offline_page.original_url_if_different = GURL(kOriginalTestURL);
615 offline_page.system_download_id = kTestSystemDownloadId;
616 offline_page.digest = kTestDigest;
617 offline_page.snippet = kTestSnippet;
618 offline_page.attribution = kTestAttribution;
619
620 EXPECT_EQ(ItemActionStatus::SUCCESS,
621 AddOfflinePage(store.get(), offline_page));
622
623 // Close the store first to ensure file lock is removed.
624 store.reset();
625 store = BuildStore();
626 std::vector<OfflinePageItem> pages = GetOfflinePages(store.get());
627 ASSERT_EQ(store_size + 1, pages.size());
628 if (store_size > 0 && pages[0].offline_id != offline_page.offline_id) {
629 std::swap(pages[0], pages[1]);
630 }
631 EXPECT_EQ(offline_page, pages[0]);
632 }
633
CheckThatPageVisualsCanBeSaved(OfflinePageMetadataStore * store)634 void CheckThatPageVisualsCanBeSaved(OfflinePageMetadataStore* store) {
635 OfflinePageVisuals visuals;
636 visuals.offline_id = kOfflineId;
637 visuals.expiration = kVisualsExpiration;
638 visuals.thumbnail = "content";
639 visuals.favicon = "favicon";
640
641 std::vector<OfflinePageVisuals> visuals_vector_before = GetVisuals(store);
642
643 AddVisuals(store, visuals);
644 std::vector<OfflinePageVisuals> visuals_vector = GetVisuals(store);
645 EXPECT_EQ(visuals_vector_before.size() + 1, visuals_vector.size());
646 EXPECT_EQ(visuals, visuals_vector.back());
647 }
648
VerifyMetaVersions()649 void VerifyMetaVersions() {
650 sql::Database connection;
651 ASSERT_TRUE(connection.Open(temp_directory_.GetPath().Append(
652 FILE_PATH_LITERAL("OfflinePages.db"))));
653 ASSERT_TRUE(connection.is_open());
654 EXPECT_TRUE(sql::MetaTable::DoesTableExist(&connection));
655 sql::MetaTable meta_table;
656 EXPECT_TRUE(meta_table.Init(&connection, 1, 1));
657
658 EXPECT_EQ(OfflinePageMetadataStore::kCurrentVersion,
659 meta_table.GetVersionNumber());
660 EXPECT_EQ(OfflinePageMetadataStore::kCompatibleVersion,
661 meta_table.GetCompatibleVersionNumber());
662 }
663
LoadAndCheckStore()664 void LoadAndCheckStore() {
665 auto store = std::make_unique<OfflinePageMetadataStore>(
666 base::ThreadTaskRunnerHandle::Get(), TempPath());
667 OfflinePageItem item = CheckThatStoreHasOneItem(store.get());
668 CheckThatPageVisualsCanBeSaved(store.get());
669 CheckThatOfflinePageCanBeSaved(std::move(store));
670 VerifyMetaVersions();
671 }
672
LoadAndCheckStoreFromMetaVersion1AndUp()673 void LoadAndCheckStoreFromMetaVersion1AndUp() {
674 // At meta version 1, more items were added to the database for testing,
675 // which necessitates different checks.
676 auto store = std::make_unique<OfflinePageMetadataStore>(
677 base::ThreadTaskRunnerHandle::Get(), TempPath());
678 std::vector<OfflinePageItem> pages = GetOfflinePages(store.get());
679 EXPECT_EQ(5U, pages.size());
680
681 CheckThatPageVisualsCanBeSaved((OfflinePageMetadataStore*)store.get());
682 CheckThatOfflinePageCanBeSaved(std::move(store));
683 VerifyMetaVersions();
684 }
685
LoadAndCheckStoreFromMetaVersion3AndUp()686 void LoadAndCheckStoreFromMetaVersion3AndUp() {
687 auto store = std::make_unique<OfflinePageMetadataStore>(
688 base::ThreadTaskRunnerHandle::Get(), TempPath());
689 std::vector<OfflinePageItem> pages = GetOfflinePages(store.get());
690 EXPECT_EQ(5U, pages.size());
691
692 std::vector<OfflinePageVisuals> visuals_vector = GetVisuals(store.get());
693 EXPECT_EQ(1U, visuals_vector.size());
694
695 OfflinePageVisuals visuals_v3 = TestVisuals();
696 visuals_v3.favicon = std::string();
697 EXPECT_EQ(visuals_v3, visuals_vector.back());
698
699 CheckThatPageVisualsCanBeSaved(store.get());
700 CheckThatOfflinePageCanBeSaved(std::move(store));
701 VerifyMetaVersions();
702 }
703
704 template <typename T>
ExecuteSync(OfflinePageMetadataStore * store,base::OnceCallback<T (sql::Database *)> run_callback,T default_value)705 T ExecuteSync(OfflinePageMetadataStore* store,
706 base::OnceCallback<T(sql::Database*)> run_callback,
707 T default_value) {
708 bool called = false;
709 T result;
710 auto result_callback = base::BindLambdaForTesting([&](T async_result) {
711 result = std::move(async_result);
712 called = true;
713 });
714 store->Execute<T>(std::move(run_callback), result_callback, default_value);
715 PumpLoop();
716 EXPECT_TRUE(called);
717 return result;
718 }
719
GetOfflinePagesAsync(OfflinePageMetadataStore * store,base::OnceCallback<void (std::vector<OfflinePageItem>)> callback)720 void GetOfflinePagesAsync(
721 OfflinePageMetadataStore* store,
722 base::OnceCallback<void(std::vector<OfflinePageItem>)> callback) {
723 auto run_callback = base::BindOnce(&GetOfflinePagesSync);
724 store->Execute<std::vector<OfflinePageItem>>(std::move(run_callback),
725 std::move(callback), {});
726 }
727
GetOfflinePages(OfflinePageMetadataStore * store)728 std::vector<OfflinePageItem> GetOfflinePages(
729 OfflinePageMetadataStore* store) {
730 return ExecuteSync<std::vector<OfflinePageItem>>(
731 store, base::BindOnce(&GetOfflinePagesSync), {});
732 }
733
GetOfflinePageSet(OfflinePageMetadataStore * store)734 OfflinePageSet GetOfflinePageSet(OfflinePageMetadataStore* store) {
735 std::vector<OfflinePageItem> items = GetOfflinePages(store);
736 auto page_set = OfflinePageSet(items.begin(), items.end());
737 CHECK_EQ(page_set.size(), items.size());
738 return page_set;
739 }
740
AddOfflinePage(OfflinePageMetadataStore * store,const OfflinePageItem & item)741 ItemActionStatus AddOfflinePage(OfflinePageMetadataStore* store,
742 const OfflinePageItem& item) {
743 auto result_callback = base::BindLambdaForTesting([&](sql::Database* db) {
744 // Using 'INSERT OR FAIL' or 'INSERT OR ABORT' in the query below
745 // causes debug builds to DLOG.
746 static const char kSql[] =
747 "INSERT OR IGNORE INTO " OFFLINE_PAGES_TABLE_V1
748 " (offline_id,online_url,client_namespace,client_id,"
749 "file_path,"
750 "file_size,creation_time,last_access_time,access_count,"
751 "title,original_url,request_origin,system_download_id,"
752 "file_missing_time,digest,snippet,attribution)"
753 " VALUES "
754 "(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
755
756 sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql));
757 statement.BindInt64(0, item.offline_id);
758 statement.BindString(1, item.url.spec());
759 statement.BindString(2, item.client_id.name_space);
760 statement.BindString(3, item.client_id.id);
761 statement.BindString(4, store_utils::ToDatabaseFilePath(item.file_path));
762 statement.BindInt64(5, item.file_size);
763 statement.BindInt64(6, store_utils::ToDatabaseTime(item.creation_time));
764 statement.BindInt64(7,
765 store_utils::ToDatabaseTime(item.last_access_time));
766 statement.BindInt(8, item.access_count);
767 statement.BindString16(9, item.title);
768 statement.BindString(10, item.original_url_if_different.spec());
769 statement.BindString(11, item.request_origin);
770 statement.BindInt64(12, item.system_download_id);
771 statement.BindInt64(13,
772 store_utils::ToDatabaseTime(item.file_missing_time));
773 statement.BindString(14, item.digest);
774 statement.BindString(15, item.snippet);
775 statement.BindString(16, item.attribution);
776
777 if (!statement.Run())
778 return ItemActionStatus::STORE_ERROR;
779 if (db->GetLastChangeCount() == 0)
780 return ItemActionStatus::ALREADY_EXISTS;
781 return ItemActionStatus::SUCCESS;
782 });
783 return ExecuteSync<ItemActionStatus>(store, result_callback,
784 ItemActionStatus::SUCCESS);
785 }
786
GetVisuals(OfflinePageMetadataStore * store)787 std::vector<OfflinePageVisuals> GetVisuals(OfflinePageMetadataStore* store) {
788 std::vector<OfflinePageVisuals> visuals_vector;
789 auto run_callback = base::BindLambdaForTesting([&](sql::Database* db) {
790 static const char kSql[] = "SELECT * FROM page_thumbnails";
791 sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql));
792
793 while (statement.Step()) {
794 OfflinePageVisuals visuals;
795 visuals.offline_id = statement.ColumnInt64(0);
796 visuals.expiration =
797 store_utils::FromDatabaseTime(statement.ColumnInt64(1));
798 statement.ColumnBlobAsString(2, &visuals.thumbnail);
799 statement.ColumnBlobAsString(3, &visuals.favicon);
800 visuals_vector.push_back(std::move(visuals));
801 }
802
803 EXPECT_TRUE(statement.Succeeded());
804 return visuals_vector;
805 });
806 return ExecuteSync<std::vector<OfflinePageVisuals>>(store, run_callback,
807 {});
808 }
809
AddVisuals(OfflinePageMetadataStore * store,const OfflinePageVisuals & visuals)810 void AddVisuals(OfflinePageMetadataStore* store,
811 const OfflinePageVisuals& visuals) {
812 std::vector<OfflinePageVisuals> visuals_vector;
813 auto run_callback = base::BindLambdaForTesting([&](sql::Database* db) {
814 static const char kSql[] =
815 "INSERT INTO page_thumbnails"
816 " (offline_id,expiration,thumbnail,favicon) VALUES (?,?,?,?)";
817 sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql));
818
819 statement.BindInt64(0, visuals.offline_id);
820 statement.BindInt64(1, store_utils::ToDatabaseTime(visuals.expiration));
821 statement.BindString(2, visuals.thumbnail);
822 statement.BindString(3, visuals.favicon);
823 EXPECT_TRUE(statement.Run());
824 return visuals_vector;
825 });
826 ExecuteSync<std::vector<OfflinePageVisuals>>(store, run_callback, {});
827 }
828
829 protected:
830 base::ScopedTempDir temp_directory_;
831 scoped_refptr<base::TestMockTimeTaskRunner> task_runner_;
832 base::ThreadTaskRunnerHandle task_runner_handle_;
833 };
834
835 // Loads empty store and makes sure that there are no offline pages stored in
836 // it.
TEST_F(OfflinePageMetadataStoreTest,LoadEmptyStore)837 TEST_F(OfflinePageMetadataStoreTest, LoadEmptyStore) {
838 std::unique_ptr<OfflinePageMetadataStore> store(BuildStore());
839 EXPECT_EQ(0U, GetOfflinePages(store.get()).size());
840 }
841
TEST_F(OfflinePageMetadataStoreTest,GetOfflinePagesFromInvalidStore)842 TEST_F(OfflinePageMetadataStoreTest, GetOfflinePagesFromInvalidStore) {
843 std::unique_ptr<OfflinePageMetadataStore> store(BuildStore());
844
845 // Because execute method is self-healing this part of the test expects a
846 // positive results now.
847 store->SetInitializationStatusForTesting(
848 InitializationStatus::kNotInitialized, false);
849 EXPECT_EQ(OfflinePageSet(), GetOfflinePageSet(store.get()));
850 EXPECT_EQ(InitializationStatus::kSuccess,
851 store->initialization_status_for_testing());
852
853 store->SetInitializationStatusForTesting(InitializationStatus::kFailure,
854 false);
855 EXPECT_EQ(OfflinePageSet(), GetOfflinePageSet(store.get()));
856 EXPECT_EQ(InitializationStatus::kFailure,
857 store->initialization_status_for_testing());
858
859 store->SetInitializationStatusForTesting(InitializationStatus::kSuccess,
860 true);
861 EXPECT_EQ(OfflinePageSet(), GetOfflinePageSet(store.get()));
862
863 store->SetInitializationStatusForTesting(
864 InitializationStatus::kNotInitialized, true);
865 EXPECT_EQ(OfflinePageSet(), GetOfflinePageSet(store.get()));
866
867 store->SetInitializationStatusForTesting(InitializationStatus::kFailure,
868 false);
869 EXPECT_EQ(OfflinePageSet(), GetOfflinePageSet(store.get()));
870 }
871
872 // Loads a store which has an outdated schema.
873 // These tests would crash if it's not handling correctly when we're loading
874 // old version stores.
TEST_F(OfflinePageMetadataStoreTest,LoadVersion52Store)875 TEST_F(OfflinePageMetadataStoreTest, LoadVersion52Store) {
876 BuildTestStoreWithSchemaFromM52(TempPath());
877 LoadAndCheckStore();
878 }
TEST_F(OfflinePageMetadataStoreTest,LoadVersion53Store)879 TEST_F(OfflinePageMetadataStoreTest, LoadVersion53Store) {
880 BuildTestStoreWithSchemaFromM53(TempPath());
881 LoadAndCheckStore();
882 }
TEST_F(OfflinePageMetadataStoreTest,LoadVersion54Store)883 TEST_F(OfflinePageMetadataStoreTest, LoadVersion54Store) {
884 BuildTestStoreWithSchemaFromM54(TempPath());
885 LoadAndCheckStore();
886 }
TEST_F(OfflinePageMetadataStoreTest,LoadVersion55Store)887 TEST_F(OfflinePageMetadataStoreTest, LoadVersion55Store) {
888 BuildTestStoreWithSchemaFromM55(TempPath());
889 LoadAndCheckStore();
890 }
TEST_F(OfflinePageMetadataStoreTest,LoadVersion56Store)891 TEST_F(OfflinePageMetadataStoreTest, LoadVersion56Store) {
892 BuildTestStoreWithSchemaFromM56(TempPath());
893 LoadAndCheckStore();
894 }
TEST_F(OfflinePageMetadataStoreTest,LoadVersion57Store)895 TEST_F(OfflinePageMetadataStoreTest, LoadVersion57Store) {
896 BuildTestStoreWithSchemaFromM57(TempPath());
897 LoadAndCheckStore();
898 }
TEST_F(OfflinePageMetadataStoreTest,LoadVersion61Store)899 TEST_F(OfflinePageMetadataStoreTest, LoadVersion61Store) {
900 BuildTestStoreWithSchemaFromM61(TempPath());
901 LoadAndCheckStore();
902 }
TEST_F(OfflinePageMetadataStoreTest,LoadVersion62Store)903 TEST_F(OfflinePageMetadataStoreTest, LoadVersion62Store) {
904 BuildTestStoreWithSchemaFromM62(TempPath());
905 LoadAndCheckStore();
906 }
907
TEST_F(OfflinePageMetadataStoreTest,LoadStoreWithMetaVersion1)908 TEST_F(OfflinePageMetadataStoreTest, LoadStoreWithMetaVersion1) {
909 BuildTestStoreWithSchemaVersion1(TempPath());
910 LoadAndCheckStoreFromMetaVersion1AndUp();
911 }
912
TEST_F(OfflinePageMetadataStoreTest,LoadStoreWithMetaVersion2)913 TEST_F(OfflinePageMetadataStoreTest, LoadStoreWithMetaVersion2) {
914 BuildTestStoreWithSchemaVersion2(TempPath());
915 LoadAndCheckStoreFromMetaVersion1AndUp();
916 }
917
TEST_F(OfflinePageMetadataStoreTest,LoadStoreWithMetaVersion3)918 TEST_F(OfflinePageMetadataStoreTest, LoadStoreWithMetaVersion3) {
919 BuildTestStoreWithSchemaVersion3(TempPath());
920 LoadAndCheckStoreFromMetaVersion3AndUp();
921 }
922
923 // Adds metadata of an offline page into a store and then opens the store
924 // again to make sure that stored metadata survives store restarts.
TEST_F(OfflinePageMetadataStoreTest,AddOfflinePage)925 TEST_F(OfflinePageMetadataStoreTest, AddOfflinePage) {
926 CheckThatOfflinePageCanBeSaved(BuildStore());
927 }
928
TEST_F(OfflinePageMetadataStoreTest,AddSameOfflinePageTwice)929 TEST_F(OfflinePageMetadataStoreTest, AddSameOfflinePageTwice) {
930 std::unique_ptr<OfflinePageMetadataStore> store(BuildStore());
931
932 OfflinePageItem offline_page(GURL(kTestURL), 1234LL, kTestClientId1,
933 base::FilePath(kFilePath), kFileSize);
934 offline_page.title = base::UTF8ToUTF16("a title");
935
936 EXPECT_EQ(ItemActionStatus::SUCCESS,
937 AddOfflinePage(store.get(), offline_page));
938
939 EXPECT_EQ(ItemActionStatus::ALREADY_EXISTS,
940 AddOfflinePage(store.get(), offline_page));
941 }
942
943 // Adds metadata of multiple offline pages into a store and removes some.
TEST_F(OfflinePageMetadataStoreTest,AddRemoveMultipleOfflinePages)944 TEST_F(OfflinePageMetadataStoreTest, AddRemoveMultipleOfflinePages) {
945 std::unique_ptr<OfflinePageMetadataStore> store(BuildStore());
946
947 // Add an offline page.
948 OfflinePageItem offline_page_1(GURL(kTestURL), 12345LL, kTestClientId1,
949 base::FilePath(kFilePath), kFileSize);
950 EXPECT_EQ(ItemActionStatus::SUCCESS,
951 AddOfflinePage(store.get(), offline_page_1));
952
953 // Add anther offline page.
954 base::FilePath file_path_2 =
955 base::FilePath(FILE_PATH_LITERAL("//other.page.com.mhtml"));
956 OfflinePageItem offline_page_2(GURL("https://other.page.com"), 5678LL,
957 kTestClientId2, file_path_2, 12345,
958 OfflineTimeNow());
959 offline_page_2.request_origin = kTestRequestOrigin;
960 offline_page_2.original_url_if_different = GURL("https://example.com/bar");
961 offline_page_2.system_download_id = kTestSystemDownloadId;
962 offline_page_2.digest = kTestDigest;
963
964 EXPECT_EQ(ItemActionStatus::SUCCESS,
965 AddOfflinePage(store.get(), offline_page_2));
966
967 // Check all pages are in the store.
968 EXPECT_EQ(OfflinePageSet({offline_page_1, offline_page_2}),
969 GetOfflinePageSet(store.get()));
970
971 // Close and reload the store.
972 store.reset();
973 store = BuildStore();
974 EXPECT_EQ(OfflinePageSet({offline_page_1, offline_page_2}),
975 GetOfflinePageSet(store.get()));
976 }
977
TEST_F(OfflinePageMetadataStoreTest,StoreCloses)978 TEST_F(OfflinePageMetadataStoreTest, StoreCloses) {
979 std::unique_ptr<OfflinePageMetadataStore> store(BuildStore());
980 GetOfflinePages(store.get());
981
982 EXPECT_TRUE(task_runner()->HasPendingTask());
983 EXPECT_LT(base::TimeDelta(), task_runner()->NextPendingTaskDelay());
984
985 FastForwardBy(OfflinePageMetadataStore::kClosingDelay);
986 PumpLoop();
987 EXPECT_EQ(StoreState::NOT_LOADED, store->GetStateForTesting());
988
989 // Ensure that next call to the store will actually reinitialize it.
990 EXPECT_EQ(0U, GetOfflinePages(store.get()).size());
991 EXPECT_EQ(StoreState::LOADED, store->GetStateForTesting());
992 }
993
TEST_F(OfflinePageMetadataStoreTest,MultiplePendingCalls)994 TEST_F(OfflinePageMetadataStoreTest, MultiplePendingCalls) {
995 auto store = std::make_unique<OfflinePageMetadataStore>(
996 base::ThreadTaskRunnerHandle::Get(), TempPath());
997 EXPECT_FALSE(task_runner()->HasPendingTask());
998 EXPECT_EQ(StoreState::NOT_LOADED, store->GetStateForTesting());
999
1000 // First call flips the state to initializing.
1001 // Subsequent calls should be pending until store is initialized.
1002 int callback_count = 0;
1003 auto get_complete =
1004 base::BindLambdaForTesting([&](std::vector<OfflinePageItem> pages) {
1005 ++callback_count;
1006 EXPECT_TRUE(pages.empty());
1007 });
1008 GetOfflinePagesAsync(store.get(), get_complete);
1009 EXPECT_EQ(StoreState::INITIALIZING, store->GetStateForTesting());
1010
1011 GetOfflinePagesAsync(store.get(), get_complete);
1012 EXPECT_EQ(0U, GetOfflinePages(store.get()).size());
1013 EXPECT_EQ(StoreState::LOADED, store->GetStateForTesting());
1014 EXPECT_EQ(2, callback_count);
1015 }
1016
1017 } // namespace
1018 } // namespace offline_pages
1019