offline_page_metadata_store_unittest.cc 43 KB

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