/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import Foundation import Shared @testable import Storage import Deferred import SDWebImage import XCTest private let microsecondsPerMinute: UInt64 = 60_000_000 // 1000 * 1000 * 60 private let oneHourInMicroseconds: UInt64 = 60 * microsecondsPerMinute private let oneDayInMicroseconds: UInt64 = 24 * oneHourInMicroseconds class TestSQLiteHistoryRecommendations: XCTestCase { let files = MockFiles() var db: BrowserDB! var prefs: MockProfilePrefs! var history: SQLiteHistory! var bookmarks: MergedSQLiteBookmarks! var metadata: SQLiteMetadata! override func setUp() { super.setUp() db = BrowserDB(filename: "browser.db", schema: BrowserSchema(), files: files) metadata = SQLiteMetadata(db: db) prefs = MockProfilePrefs() history = SQLiteHistory(db: db, prefs: prefs) bookmarks = MergedSQLiteBookmarks(db: db) } override func tearDown() { // Clear out anything we might have changed on disk history.clearHistory().succeeded() db.run("DELETE FROM \(TablePageMetadata)").succeeded() db.run("DELETE FROM \(TableHighlights)").succeeded() db.run("DELETE FROM \(TableActivityStreamBlocklist)").succeeded() SDWebImageManager.shared().imageCache?.clearDisk() SDWebImageManager.shared().imageCache?.clearMemory() super.tearDown() } /* * Verify that we return a non-recent history highlight if: * * 1. We haven't visited the site in the last 30 minutes * 2. We've only visited the site less than or equal to 3 times * 3. The site we visited has a non-empty title * */ func testHistoryHighlights() { let startTime = Date.nowMicroseconds() let oneHourAgo = startTime - oneHourInMicroseconds let fifteenMinutesAgo = startTime - 15 * microsecondsPerMinute /* * Site A: 1 visit, 1 hour ago = highlight * Site B: 1 visits, 15 minutes ago = non-highlight * Site C: 3 visits, 1 hour ago = highlight * Site D: 4 visits, 1 hour ago = non-highlight */ let siteA = Site(url: "http://siteA/", title: "A") let siteB = Site(url: "http://siteB/", title: "B") let siteC = Site(url: "http://siteC/", title: "C") let siteD = Site(url: "http://siteD/", title: "D") let siteVisitA1 = SiteVisit(site: siteA, date: oneHourAgo, type: .link) let siteVisitB1 = SiteVisit(site: siteB, date: fifteenMinutesAgo, type: .link) let siteVisitC1 = SiteVisit(site: siteC, date: oneHourAgo + 1, type: .link) let siteVisitC2 = SiteVisit(site: siteC, date: oneHourAgo + 1000, type: .link) let siteVisitC3 = SiteVisit(site: siteC, date: oneHourAgo + 2000, type: .link) let siteVisitD1 = SiteVisit(site: siteD, date: oneHourAgo, type: .link) let siteVisitD2 = SiteVisit(site: siteD, date: oneHourAgo + 1000, type: .link) let siteVisitD3 = SiteVisit(site: siteD, date: oneHourAgo + 2000, type: .link) let siteVisitD4 = SiteVisit(site: siteD, date: oneHourAgo + 3000, type: .link) history.clearHistory().succeeded() history.addLocalVisit(siteVisitA1).succeeded() history.addLocalVisit(siteVisitB1).succeeded() history.addLocalVisit(siteVisitC1).succeeded() history.addLocalVisit(siteVisitC2).succeeded() history.addLocalVisit(siteVisitC3).succeeded() history.addLocalVisit(siteVisitD1).succeeded() history.addLocalVisit(siteVisitD2).succeeded() history.addLocalVisit(siteVisitD3).succeeded() history.addLocalVisit(siteVisitD4).succeeded() history.repopulate(invalidateTopSites: true, invalidateHighlights: true).succeeded() let highlights = history.getHighlights().value.successValue! XCTAssertEqual(highlights.count, 2) XCTAssertEqual(highlights[0]!.title, "A") XCTAssertEqual(highlights[1]!.title, "C") } /* * Verify that we do not return a highlight if * its domain is in the blacklist * */ func testBlacklistHighlights() { let startTime = Date.nowMicroseconds() let oneHourAgo = startTime - oneHourInMicroseconds let fifteenMinutesAgo = startTime - 15 * microsecondsPerMinute /* * Site A: 1 visit, 1 hour ago = highlight that is on the blacklist * Site B: 1 visits, 15 minutes ago = non-highlight * Site C: 3 visits, 1 hour ago = highlight that is on the blacklist * Site D: 4 visits, 1 hour ago = non-highlight */ let siteA = Site(url: "http://www.google.com", title: "A") let siteB = Site(url: "http://siteB/", title: "B") let siteC = Site(url: "http://www.search.yahoo.com/", title: "C") let siteD = Site(url: "http://siteD/", title: "D") let siteVisitA1 = SiteVisit(site: siteA, date: oneHourAgo, type: .link) let siteVisitB1 = SiteVisit(site: siteB, date: fifteenMinutesAgo, type: .link) let siteVisitC1 = SiteVisit(site: siteC, date: oneHourAgo, type: .link) let siteVisitC2 = SiteVisit(site: siteC, date: oneHourAgo + 1000, type: .link) let siteVisitC3 = SiteVisit(site: siteC, date: oneHourAgo + 2000, type: .link) let siteVisitD1 = SiteVisit(site: siteD, date: oneHourAgo, type: .link) let siteVisitD2 = SiteVisit(site: siteD, date: oneHourAgo + 1000, type: .link) let siteVisitD3 = SiteVisit(site: siteD, date: oneHourAgo + 2000, type: .link) let siteVisitD4 = SiteVisit(site: siteD, date: oneHourAgo + 3000, type: .link) history.clearHistory().succeeded() history.addLocalVisit(siteVisitA1).succeeded() history.addLocalVisit(siteVisitB1).succeeded() history.addLocalVisit(siteVisitC1).succeeded() history.addLocalVisit(siteVisitC2).succeeded() history.addLocalVisit(siteVisitC3).succeeded() history.addLocalVisit(siteVisitD1).succeeded() history.addLocalVisit(siteVisitD2).succeeded() history.addLocalVisit(siteVisitD3).succeeded() history.addLocalVisit(siteVisitD4).succeeded() history.repopulate(invalidateTopSites: true, invalidateHighlights: true).succeeded() let highlights = history.getHighlights().value.successValue! XCTAssertEqual(highlights.count, 0) } /* * Verify that we return the most recent highlight per domain */ func testMostRecentUniqueDomainReturnedInHighlights() { let startTime = Date.nowMicroseconds() let oneHourAgo = startTime - oneHourInMicroseconds let twoHoursAgo = startTime - 2 * oneHourInMicroseconds /* * Site A: 1 visit, 1 hour ago = highlight * Site C: 2 visits, 2 hours ago = highlight with the same domain */ let siteA = Site(url: "http://www.foo.com/", title: "A") let siteC = Site(url: "http://m.foo.com/", title: "C") let siteVisitA1 = SiteVisit(site: siteA, date: oneHourAgo, type: .link) let siteVisitC1 = SiteVisit(site: siteC, date: twoHoursAgo, type: .link) let siteVisitC2 = SiteVisit(site: siteC, date: twoHoursAgo + 1000, type: .link) history.clearHistory().succeeded() history.addLocalVisit(siteVisitA1).succeeded() history.addLocalVisit(siteVisitC1).succeeded() history.addLocalVisit(siteVisitC2).succeeded() history.repopulate(invalidateTopSites: true, invalidateHighlights: true).succeeded() let highlights = history.getHighlights().value.successValue! XCTAssertEqual(highlights.count, 1) XCTAssertEqual(highlights[0]!.title, "A") } func testBookmarkHighlights() { history.clearHistory().succeeded() populateForRecommendationCalculations(history, bookmarks: bookmarks, metadata: metadata, historyCount: 10, bookmarkCount: 10) let sites = history.getRecentBookmarks(5).value.successValue?.asArray() XCTAssertEqual(sites!.count, 5, "5 bookmarks should have been fetched") sites!.forEach { XCTAssertEqual($0.guid, "bookmark-\(sites!.index(of: $0)!)"); XCTAssertEqual($0.metadata?.description, "Test Description") } } func testMetadataReturnedInHighlights() { let startTime = Date.nowMicroseconds() let oneHourAgo = startTime - oneHourInMicroseconds let siteA = Site(url: "http://siteA.com", title: "Site A") let siteB = Site(url: "http://siteB.com/", title: "Site B") let siteC = Site(url: "http://siteC.com/", title: "Site C") let siteVisitA1 = SiteVisit(site: siteA, date: oneHourAgo, type: .link) let siteVisitB1 = SiteVisit(site: siteB, date: oneHourAgo + 1000, type: .link) let siteVisitC1 = SiteVisit(site: siteC, date: oneHourAgo, type: .link) let siteVisitC2 = SiteVisit(site: siteC, date: oneHourAgo + 1000, type: .link) let siteVisitC3 = SiteVisit(site: siteC, date: oneHourAgo + 2000, type: .link) history.clearHistory().succeeded() history.addLocalVisit(siteVisitA1).succeeded() history.addLocalVisit(siteVisitB1).succeeded() history.addLocalVisit(siteVisitC1).succeeded() history.addLocalVisit(siteVisitC2).succeeded() history.addLocalVisit(siteVisitC3).succeeded() // add metadata for 2 of the sites let metadata = SQLiteMetadata(db: db) let pageA = PageMetadata(id: nil, siteURL: siteA.url, mediaURL: "http://image.com", title: siteA.title, description: "Test Description", type: nil, providerName: nil, mediaDataURI: nil, cacheImages: false) metadata.storeMetadata(pageA, forPageURL: siteA.url.asURL!, expireAt: Date.now() + 3000).succeeded() let pageB = PageMetadata(id: nil, siteURL: siteB.url, mediaURL: "http://image.com", title: siteB.title, description: "Test Description", type: nil, providerName: nil, mediaDataURI: nil, cacheImages: false) metadata.storeMetadata(pageB, forPageURL: siteB.url.asURL!, expireAt: Date.now() + 3000).succeeded() let pageC = PageMetadata(id: nil, siteURL: siteC.url, mediaURL: "http://image.com", title: siteC.title, description: "Test Description", type: nil, providerName: nil, mediaDataURI: nil, cacheImages: false) metadata.storeMetadata(pageC, forPageURL: siteC.url.asURL!, expireAt: Date.now() + 3000).succeeded() history.repopulate(invalidateTopSites: true, invalidateHighlights: true).succeeded() let highlights = history.getHighlights().value.successValue! XCTAssertEqual(highlights.count, 3) for highlight in highlights { XCTAssertNotNil(highlight?.metadata) XCTAssertNotNil(highlight?.metadata?.mediaURL) } } func testRemoveHighlightForURL() { let startTime = Date.nowMicroseconds() let oneHourAgo = startTime - oneHourInMicroseconds let siteA = Site(url: "http://siteA/", title: "A") let siteVisitA1 = SiteVisit(site: siteA, date: oneHourAgo, type: .link) history.clearHistory().succeeded() history.addLocalVisit(siteVisitA1).succeeded() history.repopulate(invalidateTopSites: true, invalidateHighlights: true).succeeded() var highlights = history.getHighlights().value.successValue! XCTAssertEqual(highlights.count, 1) XCTAssertEqual(highlights[0]!.title, "A") history.removeHighlightForURL(siteA.url).succeeded() history.repopulate(invalidateTopSites: true, invalidateHighlights: true).succeeded() highlights = history.getHighlights().value.successValue! XCTAssertEqual(highlights.count, 0) } func testClearHighlightsCache() { let startTime = Date.nowMicroseconds() let oneHourAgo = startTime - oneHourInMicroseconds let siteA = Site(url: "http://siteA/", title: "A") let siteVisitA1 = SiteVisit(site: siteA, date: oneHourAgo, type: .link) history.clearHistory().succeeded() history.addLocalVisit(siteVisitA1).succeeded() history.repopulate(invalidateTopSites: true, invalidateHighlights: true).succeeded() let highlights = history.getHighlights().value.successValue! XCTAssertEqual(highlights.count, 1) XCTAssertEqual(highlights[0]!.title, "A") } } class TestSQLiteHistoryRecommendationsPerf: XCTestCase { func testRecommendationPref() { let files = MockFiles() let db = BrowserDB(filename: "browser.db", schema: BrowserSchema(), files: files) let metadata = SQLiteMetadata(db: db) let prefs = MockProfilePrefs() let history = SQLiteHistory(db: db, prefs: prefs) let bookmarks = MergedSQLiteBookmarks(db: db) let count = 500 history.clearHistory().succeeded() populateForRecommendationCalculations(history, bookmarks: bookmarks, metadata: metadata, historyCount: count, bookmarkCount: count) self.measureMetrics([XCTPerformanceMetric_WallClockTime], automaticallyStartMeasuring: true) { for _ in 0...5 { history.repopulate(invalidateTopSites: true, invalidateHighlights: true).succeeded() } self.stopMeasuring() } } } private func populateForRecommendationCalculations(_ history: SQLiteHistory, bookmarks: MergedSQLiteBookmarks, metadata: SQLiteMetadata, historyCount: Int, bookmarkCount: Int) { let baseMillis: UInt64 = baseInstantInMillis - 20000 for i in 0..