From 919b602e41092838c930805d8249561477670658 Mon Sep 17 00:00:00 2001 From: amddg44 Date: Thu, 23 May 2024 21:44:12 +0200 Subject: [PATCH] Autofill engagement KPIs for pixel reporting (BSK update) (#2885) Task/Issue URL: https://app.asana.com/0/72649045549333/1207357107981852/f Tech Design URL: CC: Description: Update to use BSK for Autofill engagement KPIs for pixel reporting --- Core/StatisticsLoader.swift | 4 - DuckDuckGo.xcodeproj/project.pbxproj | 10 +- .../xcshareddata/swiftpm/Package.resolved | 4 +- DuckDuckGo/AppDelegate.swift | 26 ++- DuckDuckGo/AutofillDebugViewController.swift | 12 +- .../AutofillLoginDetailsViewModel.swift | 1 + DuckDuckGo/AutofillPixelReporter.swift | 178 ---------------- DuckDuckGo/Debug.storyboard | 27 ++- DuckDuckGo/TabViewController.swift | 5 +- .../AutofillPixelReporterTests.swift | 193 ------------------ DuckDuckGoTests/MockSecureVault.swift | 8 + 11 files changed, 66 insertions(+), 402 deletions(-) delete mode 100644 DuckDuckGo/AutofillPixelReporter.swift delete mode 100644 DuckDuckGoTests/AutofillPixelReporterTests.swift diff --git a/Core/StatisticsLoader.swift b/Core/StatisticsLoader.swift index 6d4bae6c06..60b5fe14dc 100644 --- a/Core/StatisticsLoader.swift +++ b/Core/StatisticsLoader.swift @@ -141,7 +141,3 @@ public class StatisticsLoader { } } } - -extension NSNotification.Name { - public static let searchDAU: NSNotification.Name = Notification.Name(rawValue: "com.duckduckgo.notification.searchDAU") -} diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 3e7d7f8df5..a3c09f03cb 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -671,8 +671,6 @@ C12726EE2A5FF88C00215B02 /* EmailSignupPromptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12726ED2A5FF88C00215B02 /* EmailSignupPromptView.swift */; }; C12726F02A5FF89900215B02 /* EmailSignupPromptViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12726EF2A5FF89900215B02 /* EmailSignupPromptViewModel.swift */; }; C12726F22A5FF8CB00215B02 /* EmailSignupPromptViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12726F12A5FF8CB00215B02 /* EmailSignupPromptViewController.swift */; }; - C12B6E7A2BED639500050D93 /* AutofillPixelReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12B6E792BED639500050D93 /* AutofillPixelReporter.swift */; }; - C12B6E7C2BED69C100050D93 /* AutofillPixelReporterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12B6E7B2BED69C100050D93 /* AutofillPixelReporterTests.swift */; }; C136C7542BEB8CFC00ACC3B0 /* PasswordsSurveyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C136C7532BEB8CFC00ACC3B0 /* PasswordsSurveyView.swift */; }; C13B32D22A0E750700A59236 /* AutofillSettingStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13B32D12A0E750700A59236 /* AutofillSettingStatus.swift */; }; C13F3F682B7F88100083BE40 /* AuthConfirmationPromptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13F3F672B7F88100083BE40 /* AuthConfirmationPromptView.swift */; }; @@ -2291,8 +2289,6 @@ C12726ED2A5FF88C00215B02 /* EmailSignupPromptView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailSignupPromptView.swift; sourceTree = ""; }; C12726EF2A5FF89900215B02 /* EmailSignupPromptViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailSignupPromptViewModel.swift; sourceTree = ""; }; C12726F12A5FF8CB00215B02 /* EmailSignupPromptViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailSignupPromptViewController.swift; sourceTree = ""; }; - C12B6E792BED639500050D93 /* AutofillPixelReporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillPixelReporter.swift; sourceTree = ""; }; - C12B6E7B2BED69C100050D93 /* AutofillPixelReporterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillPixelReporterTests.swift; sourceTree = ""; }; C136C7532BEB8CFC00ACC3B0 /* PasswordsSurveyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordsSurveyView.swift; sourceTree = ""; }; C13B32D12A0E750700A59236 /* AutofillSettingStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillSettingStatus.swift; sourceTree = ""; }; C13F3F672B7F88100083BE40 /* AuthConfirmationPromptView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthConfirmationPromptView.swift; sourceTree = ""; }; @@ -5425,7 +5421,6 @@ F40F843528C938370081AE75 /* AutofillLoginListViewModelTests.swift */, C1D21E2E293A599C006E5A05 /* AutofillLoginSessionTests.swift */, C1CDA31D2AFBF811006D1476 /* AutofillNeverPromptWebsitesManagerTests.swift */, - C12B6E7B2BED69C100050D93 /* AutofillPixelReporterTests.swift */, ); name = Autofill; sourceTree = ""; @@ -5437,7 +5432,6 @@ F4147353283BF834004AA7A5 /* AutofillContentScopeFeatureToggles.swift */, C1D21E2C293A5965006E5A05 /* AutofillLoginSession.swift */, C1CDA3152AFB9C7F006D1476 /* AutofillNeverPromptWebsitesManager.swift */, - C12B6E792BED639500050D93 /* AutofillPixelReporter.swift */, C13B32D12A0E750700A59236 /* AutofillSettingStatus.swift */, 319A370F28299A850079FBCE /* PasswordHider.swift */, C136C7532BEB8CFC00ACC3B0 /* PasswordsSurveyView.swift */, @@ -6400,7 +6394,6 @@ BDFF03222BA3D8E200F324C9 /* NetworkProtectionFeatureVisibility.swift in Sources */, B652DEFD287BE67400C12A9C /* UserScripts.swift in Sources */, 1D200C992BA3176D00108701 /* SettingsOthersView.swift in Sources */, - C12B6E7A2BED639500050D93 /* AutofillPixelReporter.swift in Sources */, 31DD208427395A5A008FB313 /* VoiceSearchHelper.swift in Sources */, 9874F9EE2187AFCE00CAF33D /* Themable.swift in Sources */, F44D279E27F331BB0037F371 /* AutofillLoginPromptViewModel.swift in Sources */, @@ -6915,7 +6908,6 @@ 9F2510142BF5809E0096DB16 /* SubscriptionFlowViewModelTests.swift in Sources */, F189AED71F18F6DE001EBAE1 /* TabTests.swift in Sources */, F13B4BFB1F18E3D900814661 /* TabsModelPersistenceExtensionTests.swift in Sources */, - C12B6E7C2BED69C100050D93 /* AutofillPixelReporterTests.swift in Sources */, CB48D3372B90DF2000631D8B /* UserBehaviorMonitorTests.swift in Sources */, 8528AE7E212EF5FF00D0BD74 /* AppRatingPromptTests.swift in Sources */, 981FED692201FE69008488D7 /* AutoClearSettingsScreenTests.swift in Sources */, @@ -9838,7 +9830,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 146.1.0; + version = 146.2.0; }; }; 9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 052ab810a3..d769834abe 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "65f3ccadd0118bcef112e3f5fff03e491b3261cd", - "version" : "146.1.0" + "revision" : "e1e436422bc167933baa0f90838958f2ac7119f3", + "version" : "146.2.0" } }, { diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 8aefc88800..ad73b26ac3 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -81,7 +81,7 @@ import WebKit private let crashCollection = CrashCollection(platform: .iOS, log: .generalLog) private var crashReportUploaderOnboarding: CrashCollectionOnboarding? - private let autofillPixelReporter = AutofillPixelReporter() + private var autofillPixelReporter: AutofillPixelReporter? // MARK: lifecycle @@ -340,6 +340,8 @@ import WebKit AppDependencyProvider.shared.userBehaviorMonitor.handleAction(.reopenApp) + setUpAutofillPixelReporter() + return true } @@ -681,8 +683,6 @@ import WebKit } AppDependencyProvider.shared.userBehaviorMonitor.handleAction(.reopenApp) - - autofillPixelReporter.checkIfOnboardedUser() } func applicationDidEnterBackground(_ application: UIApplication) { @@ -911,6 +911,26 @@ import WebKit return window?.rootViewController as? MainViewController } + private func setUpAutofillPixelReporter() { + autofillPixelReporter = AutofillPixelReporter( + userDefaults: .standard, + eventMapping: EventMapping {event, _, params, _ in + switch event { + case .autofillActiveUser: + Pixel.fire(pixel: .autofillActiveUser) + case .autofillEnabledUser: + Pixel.fire(pixel: .autofillEnabledUser) + case .autofillOnboardedUser: + Pixel.fire(pixel: .autofillOnboardedUser) + case .autofillLoginsStacked: + Pixel.fire(pixel: .autofillLoginsStacked, withAdditionalParameters: params ?? [:]) + default: + break + } + }, + installDate: StatisticsUserDefaults().installDate ?? Date()) + } + @MainActor func refreshShortcuts() async { #if NETWORK_PROTECTION diff --git a/DuckDuckGo/AutofillDebugViewController.swift b/DuckDuckGo/AutofillDebugViewController.swift index 1045f07941..2e369e1e79 100644 --- a/DuckDuckGo/AutofillDebugViewController.swift +++ b/DuckDuckGo/AutofillDebugViewController.swift @@ -20,6 +20,7 @@ import UIKit import BrowserServicesKit import Core +import Common class AutofillDebugViewController: UITableViewController { @@ -27,7 +28,8 @@ class AutofillDebugViewController: UITableViewController { case toggleAutofillDebugScript = 201 case resetEmailProtectionInContextSignUp = 202 case resetDaysSinceInstalledTo0 = 203 - case toggleAutofillSurvey = 204 + case resetAutofillData = 204 + case toggleAutofillSurvey = 205 } let defaults = AppUserDefaults() @@ -46,6 +48,14 @@ class AutofillDebugViewController: UITableViewController { defaults.autofillDebugScriptEnabled.toggle() cell.accessoryType = defaults.autofillDebugScriptEnabled ? .checkmark : .none NotificationCenter.default.post(Notification(name: AppUserDefaults.Notifications.autofillDebugScriptToggled)) + } else if cell.tag == Row.resetAutofillData.rawValue { + let secureVault = try? AutofillSecureVaultFactory.makeVault(reporter: SecureVaultReporter.shared) + try? secureVault?.deleteAllWebsiteCredentials() + let autofillPixelReporter = AutofillPixelReporter( + userDefaults: .standard, + eventMapping: EventMapping { _, _, _, _ in }) + autofillPixelReporter.resetStoreDefaults() + ActionMessageView.present(message: "Autofill Data reset") } else if cell.tag == Row.toggleAutofillSurvey.rawValue { defaults.autofillSurveyEnabled = true ActionMessageView.present(message: "Passwords Survey enabled") diff --git a/DuckDuckGo/AutofillLoginDetailsViewModel.swift b/DuckDuckGo/AutofillLoginDetailsViewModel.swift index 3a189e4fa9..c55c9a7270 100644 --- a/DuckDuckGo/AutofillLoginDetailsViewModel.swift +++ b/DuckDuckGo/AutofillLoginDetailsViewModel.swift @@ -341,6 +341,7 @@ final class AutofillLoginDetailsViewModel: ObservableObject { self.updateData(with: newCredential.account) } + NotificationCenter.default.post(name: .autofillSaveEvent, object: nil) } catch let error { handleSecureVaultError(error) } diff --git a/DuckDuckGo/AutofillPixelReporter.swift b/DuckDuckGo/AutofillPixelReporter.swift deleted file mode 100644 index 4cd08757f0..0000000000 --- a/DuckDuckGo/AutofillPixelReporter.swift +++ /dev/null @@ -1,178 +0,0 @@ -// -// AutofillPixelReporter.swift -// DuckDuckGo -// -// Copyright © 2024 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation -import Core -import BrowserServicesKit - -final class AutofillPixelReporter { - - @UserDefaultsWrapper(key: .autofillSearchDauDate, defaultValue: .distantPast) - var autofillSearchDauDate: Date - - @UserDefaultsWrapper(key: .autofillFillDate, defaultValue: .distantPast) - var autofillFillDate: Date - - @UserDefaultsWrapper(key: .autofillOnboardedUser, defaultValue: false) - var autofillOnboardedUser: Bool - - private let statisticsStorage: StatisticsStore - private var secureVault: (any AutofillSecureVault)? - - enum EventType { - case fill - case searchDAU - } - - init(statisticsStorage: StatisticsStore = StatisticsUserDefaults(), secureVault: (any AutofillSecureVault)? = nil) { - self.statisticsStorage = statisticsStorage - self.secureVault = secureVault - - createNotificationObservers() - - checkIfOnboardedUser() - } - - private func createNotificationObservers() { - NotificationCenter.default.addObserver(self, selector: #selector(didReceiveSearchDAU), name: .searchDAU, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(didReceiveFillEvent), name: .autofillFillEvent, object: nil) - } - - private func vault() -> (any AutofillSecureVault)? { - if secureVault == nil { - secureVault = try? AutofillSecureVaultFactory.makeVault(reporter: SecureVaultReporter.shared) - } - return secureVault - } - - @objc - private func didReceiveSearchDAU() { - guard !Date().isSameDay(autofillSearchDauDate) else { - return - } - - autofillSearchDauDate = Date() - - firePixels(pixelsToFireFor(.searchDAU)) - } - - @objc - private func didReceiveFillEvent() { - guard !Date().isSameDay(autofillFillDate) else { - return - } - - autofillFillDate = Date() - - firePixels(pixelsToFireFor(.fill)) - } - - func checkIfOnboardedUser() { - guard !autofillOnboardedUser else { return } - - if shouldFireOnboardedUserPixel() { - firePixels([.autofillOnboardedUser]) - } - } - - func pixelsToFireFor(_ type: EventType) -> [Pixel.Event] { - var pixelsToFire: [Pixel.Event] = [] - - if shouldFireActiveUserPixel() { - pixelsToFire.append(.autofillActiveUser) - pixelsToFire.append(.autofillLoginsStacked) - } - - switch type { - case .searchDAU: - if shouldFireEnabledUserPixel() { - pixelsToFire.append(.autofillEnabledUser) - } - default: - break - } - - return pixelsToFire - } - - private func firePixels(_ pixels: [Pixel.Event]) { - for pixel in pixels { - switch pixel { - case .autofillLoginsStacked: - if let count = try? vault()?.accountsCount() { - DailyPixel.fire(pixel: pixel, withAdditionalParameters: [PixelParameters.countBucket: accountsBucketNameFrom(count: count)]) - } - default: - DailyPixel.fire(pixel: pixel) - } - } - } - - public func accountsBucketNameFrom(count: Int) -> String { - if count == 0 { - return "none" - } else if count < 4 { - return "few" - } else if count < 11 { - return "some" - } else if count < 50 { - return "many" - } else { - return "lots" - } - } - - private func shouldFireActiveUserPixel() -> Bool { - let today = Date() - if today.isSameDay(autofillSearchDauDate) && today.isSameDay(autofillFillDate) { - return true - } - return false - } - - private func shouldFireEnabledUserPixel() -> Bool { - if Date().isSameDay(autofillSearchDauDate), let count = try? vault()?.accountsCount(), count >= 10 { - return true - } - return false - } - - func shouldFireOnboardedUserPixel() -> Bool { - guard !autofillOnboardedUser, let installDate = statisticsStorage.installDate, UIApplication.shared.isProtectedDataAvailable else { - return false - } - - let pastWeek = Date().addingTimeInterval(.days(-7)) - - if installDate >= pastWeek { - if let count = try? vault()?.accountsCount(), count > 0 { - autofillOnboardedUser = true - return true - } - } else { - autofillOnboardedUser = true - } - return false - } - -} - -extension NSNotification.Name { - static let autofillFillEvent: NSNotification.Name = Notification.Name(rawValue: "com.duckduckgo.notification.autofillFillEvent") -} diff --git a/DuckDuckGo/Debug.storyboard b/DuckDuckGo/Debug.storyboard index 3f9a833419..c4c3e1eeae 100644 --- a/DuckDuckGo/Debug.storyboard +++ b/DuckDuckGo/Debug.storyboard @@ -380,6 +380,15 @@ + + + + + + + + + @@ -387,7 +396,7 @@ - + @@ -396,7 +405,7 @@ - + @@ -916,17 +925,17 @@ - + - + - + - + diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index edc7bda16e..fee12e4e7d 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -2518,9 +2518,6 @@ extension TabViewController: SecureVaultManagerDelegate { promptUserWithGeneratedPassword password: String, completionHandler: @escaping (Bool) -> Void) { let passwordGenerationPromptViewController = PasswordGenerationPromptViewController(generatedPassword: password) { useGeneratedPassword in - if useGeneratedPassword { - NotificationCenter.default.post(name: .autofillFillEvent, object: nil) - } completionHandler(useGeneratedPassword) } @@ -2661,6 +2658,8 @@ extension TabViewController: SaveLoginViewControllerDelegate { with: AutofillSecureVaultFactory) confirmSavedCredentialsFor(credentialID: credentialID, message: message) syncService.scheduler.notifyDataChanged() + + NotificationCenter.default.post(name: .autofillSaveEvent, object: nil) } catch { os_log("%: failed to store credentials %s", type: .error, #function, error.localizedDescription) } diff --git a/DuckDuckGoTests/AutofillPixelReporterTests.swift b/DuckDuckGoTests/AutofillPixelReporterTests.swift deleted file mode 100644 index 0c915d5b18..0000000000 --- a/DuckDuckGoTests/AutofillPixelReporterTests.swift +++ /dev/null @@ -1,193 +0,0 @@ -// -// AutofillPixelReporterTests.swift -// DuckDuckGo -// -// Copyright © 2024 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import XCTest -@testable import DuckDuckGo -@testable import Core -@testable import BrowserServicesKit - -final class AutofillPixelReporterTests: XCTestCase { - - private var statisticsStorage: MockStatisticsStore! - private let vault = (try? MockSecureVaultFactory.makeVault(reporter: nil))! - private var autofillPixelReporter: AutofillPixelReporter! - - override func setUpWithError() throws { - statisticsStorage = MockStatisticsStore() - - setupUserDefault(with: #file) - UserDefaults.app.removeObject(forKey: UserDefaultsWrapper.Key.autofillSearchDauDate.rawValue) - UserDefaults.app.removeObject(forKey: UserDefaultsWrapper.Key.autofillFillDate.rawValue) - UserDefaults.app.removeObject(forKey: UserDefaultsWrapper.Key.autofillOnboardedUser.rawValue) - - autofillPixelReporter = AutofillPixelReporter(statisticsStorage: statisticsStorage, - secureVault: vault) - } - - override func tearDownWithError() throws { - statisticsStorage = nil - autofillPixelReporter = nil - } - - func testWhenUserSearchDauIsNotTodayAndAutofillDateIsNotTodayAndEventTypeIsSearchDauThenNoPixelsWillBeFired() { - autofillPixelReporter.autofillSearchDauDate = Date().addingTimeInterval(.days(-2)) - autofillPixelReporter.autofillFillDate = Date().addingTimeInterval(.days(-2)) - - XCTAssertTrue(autofillPixelReporter.pixelsToFireFor(.searchDAU).isEmpty) - } - - func testWhenUserSearchDauIsNotTodayAndAutofillDateIsTodayAndEventTypeIsSearchDauThenNoPixelsWillBeFired() { - autofillPixelReporter.autofillSearchDauDate = Date().addingTimeInterval(.days(-2)) - autofillPixelReporter.autofillFillDate = Date() - - XCTAssertTrue(autofillPixelReporter.pixelsToFireFor(.searchDAU).isEmpty) - } - - func testWhenUserSearchDauIsNotTodayAndEventTypeIsFillThenNoPixelsWillBeFired() { - autofillPixelReporter.autofillSearchDauDate = Date().addingTimeInterval(-2 * 60 * 60 * 24) - autofillPixelReporter.autofillFillDate = Date().addingTimeInterval(-2 * 60 * 60 * 24) - - XCTAssertTrue(autofillPixelReporter.pixelsToFireFor(.fill).isEmpty) - } - - func testWhenUserSearchDauIsTodayAndAutofillDateIsTodayAndEventTypeIsSearchDauAndAccountsCountIsLessThanTenThenTwoPixelWillBeFired() { - autofillPixelReporter.autofillSearchDauDate = Date() - autofillPixelReporter.autofillFillDate = Date() - createAccountsInVault(count: 4) - - XCTAssertEqual(autofillPixelReporter.pixelsToFireFor(.searchDAU).count, 2) - } - - func testWhenUserSearchDauIsTodayAndAutofillDateIsTodayAndEventTypeIsSearchDauAndAccountsCountIsTenThenThreePixelsWillBeFired() { - autofillPixelReporter.autofillSearchDauDate = Date() - autofillPixelReporter.autofillFillDate = Date() - createAccountsInVault(count: 10) - - XCTAssertEqual(autofillPixelReporter.pixelsToFireFor(.searchDAU).count, 3) - } - - func testWhenUserSearchDauIsTodayAndAutofillDateIsTodayAndEventTypeIsSearchDauAndAccountsCountIsGreaterThanTenThenThreePixelsWillBeFired() { - autofillPixelReporter.autofillSearchDauDate = Date() - autofillPixelReporter.autofillFillDate = Date() - createAccountsInVault(count: 15) - - XCTAssertEqual(autofillPixelReporter.pixelsToFireFor(.searchDAU).count, 3) - } - - func testWhenUserSearchDauIsTodayAndAutofillDateIsNotTodayAndEventTypeIsSearchDauAndAccountsCountIsLessThanTenThenNoPixelsWillBeFired() { - autofillPixelReporter.autofillSearchDauDate = Date() - autofillPixelReporter.autofillFillDate = Date().addingTimeInterval(-2 * 60 * 60 * 24) - createAccountsInVault(count: 4) - - XCTAssertTrue(autofillPixelReporter.pixelsToFireFor(.searchDAU).isEmpty) - } - - func testWhenUserSearchDauIsTodayAndAutofillDateIsNotTodayAndEventTypeIsSearchDauAndAccountsCountIsTenThenOnePixelWillBeFired() { - autofillPixelReporter.autofillSearchDauDate = Date() - autofillPixelReporter.autofillFillDate = Date().addingTimeInterval(-2 * 60 * 60 * 24) - createAccountsInVault(count: 10) - - XCTAssertEqual(autofillPixelReporter.pixelsToFireFor(.searchDAU).count, 1) - } - - func testWhenUserSearchDauIsTodayAndAutofillDateIsNotTodayAndEventTypeIsSearchDauAndAccountsCountIsGreaterThanTenThenOnePixelWillBeFired() { - autofillPixelReporter.autofillSearchDauDate = Date() - autofillPixelReporter.autofillFillDate = Date().addingTimeInterval(-2 * 60 * 60 * 24) - createAccountsInVault(count: 15) - - XCTAssertEqual(autofillPixelReporter.pixelsToFireFor(.searchDAU).count, 1) - } - - func testWhenUserIsNotOnboardedAndInstallDateIsNilThenOnboardedUserPixelShouldNotBeFired() { - autofillPixelReporter.autofillOnboardedUser = false - statisticsStorage.installDate = nil - - XCTAssertFalse(autofillPixelReporter.shouldFireOnboardedUserPixel()) - } - - func testWhenUserIsNotOnboardedAndInstallDateIsTodayThenOnboardedUserPixelShouldNotBeFired() { - autofillPixelReporter.autofillOnboardedUser = false - statisticsStorage.installDate = Date() - - XCTAssertFalse(autofillPixelReporter.shouldFireOnboardedUserPixel()) - } - - func testWhenUserIsNotOnboardedAndInstallDateIsYesterdayAndAccountsCountIsZeroThenOnboardedUserPixelShouldNotBeFired() { - autofillPixelReporter.autofillOnboardedUser = false - statisticsStorage.installDate = Date().addingTimeInterval(.days(-1)) - createAccountsInVault(count: 0) - - XCTAssertFalse(autofillPixelReporter.shouldFireOnboardedUserPixel()) - } - - func testWhenUserIsNotOnboardedAndInstallDateIsYesterdayAndAccountsCountIsGreaterThanZeroThenOnboardedUserPixelShouldBeFiredAndAutofillOnboardedUserShouldBeTrue() { - autofillPixelReporter.autofillOnboardedUser = false - statisticsStorage.installDate = Date().addingTimeInterval(.days(-1)) - createAccountsInVault(count: 4) - - XCTAssertTrue(autofillPixelReporter.shouldFireOnboardedUserPixel()) - XCTAssertTrue(autofillPixelReporter.autofillOnboardedUser) - } - - func testWhenUserIsNotOnboardedAndInstallDateIsLessThanSevenDaysAgoAndAccountsCountIsZeroThenOnboardedUserPixelShouldNotBeFired() { - autofillPixelReporter.autofillOnboardedUser = false - statisticsStorage.installDate = Date().addingTimeInterval(.days(-4)) - createAccountsInVault(count: 0) - - XCTAssertFalse(autofillPixelReporter.shouldFireOnboardedUserPixel()) - } - - func testWhenUserIsNotOnboardedAndInstallDateIsLessThanSevenDaysAgoAndAccountsCountIsGreaterThanZeroThenOnboardedUserPixelShouldBeFiredAndAutofillOnboardedUserShouldBeTrue() { - autofillPixelReporter.autofillOnboardedUser = false - statisticsStorage.installDate = Date().addingTimeInterval(.days(-4)) - createAccountsInVault(count: 4) - - XCTAssertTrue(autofillPixelReporter.shouldFireOnboardedUserPixel()) - XCTAssertTrue(autofillPixelReporter.autofillOnboardedUser) - } - - func testWhenUserIsNotOnboardedAndInstallDateIsMoreThanSevenDaysAgoThenOnboardedUserPixelShouldNotBeFiredAndAutofillOnboardedUserShouldBeTrue() { - autofillPixelReporter.autofillOnboardedUser = false - statisticsStorage.installDate = Date().addingTimeInterval(.days(-8)) - - XCTAssertFalse(autofillPixelReporter.shouldFireOnboardedUserPixel()) - XCTAssertTrue(autofillPixelReporter.autofillOnboardedUser) - } - - func testWhenUserIsOnboardedThenOnboardedUserPixelShouldNotBeFired() { - autofillPixelReporter.autofillOnboardedUser = true - - XCTAssertFalse(autofillPixelReporter.shouldFireOnboardedUserPixel()) - } - - private func createAccountsInVault(count: Int) { - try? vault.deleteAllWebsiteCredentials() - - for i in 0..: AutofillSecureVault { return storedCards } + func creditCardsCount() throws -> Int { + return storedCards.count + } + func creditCardFor(id: Int64) throws -> SecureVaultModels.CreditCard? { return storedCards.first { $0.id == id } } @@ -384,6 +388,10 @@ class MockDatabaseProvider: AutofillDatabaseProvider { return Array(_creditCards.values) } + func creditCardsCount() throws -> Int { + return _creditCards.count + } + func creditCardForCardId(_ cardId: Int64) throws -> SecureVaultModels.CreditCard? { return _creditCards[cardId] }