diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 5387560ef0..720d16d34c 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -434,6 +434,8 @@ 8563A03C1F9288D600F04442 /* BrowserChromeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8563A03B1F9288D600F04442 /* BrowserChromeManager.swift */; }; 8565A34B1FC8D96B00239327 /* LaunchTabNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8565A34A1FC8D96B00239327 /* LaunchTabNotification.swift */; }; 8565A34D1FC8DFE400239327 /* LaunchTabNotificationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8565A34C1FC8DFE400239327 /* LaunchTabNotificationTests.swift */; }; + 857229882BBEE74100E2E802 /* AppRatingPromptDatabaseMigrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 857229872BBEE74100E2E802 /* AppRatingPromptDatabaseMigrationTests.swift */; }; + 8572298A2BBEF0C800E2E802 /* AppRatingPrompt_v1 in Resources */ = {isa = PBXBuildFile; fileRef = 857229892BBEF0C800E2E802 /* AppRatingPrompt_v1 */; }; 8577A1C5255D2C0D00D43FCD /* HitTestingToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8577A1C4255D2C0D00D43FCD /* HitTestingToolbar.swift */; }; 857EEB752095FFAC008A005C /* HomeRowInstructionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 857EEB742095FFAC008A005C /* HomeRowInstructionsViewController.swift */; }; 858479C92B8792D800D156C1 /* HistoryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 858479C82B8792D800D156C1 /* HistoryManager.swift */; }; @@ -1537,6 +1539,7 @@ 8544C37A250B823600A0FE73 /* UserText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserText.swift; sourceTree = ""; }; 8546A5492A672959003929BF /* MainViewController+Email.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainViewController+Email.swift"; sourceTree = ""; }; 85480CB229226B1E007E8F13 /* CrashCollectionExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashCollectionExtensionTests.swift; sourceTree = ""; }; + 85481A6A2BA46AFB00F9EFB0 /* AppRatingPrompt 2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "AppRatingPrompt 2.xcdatamodel"; sourceTree = ""; }; 85482D882462DCD100EDEDD1 /* OpenAction.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = OpenAction.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 85482D8C2462DCD100EDEDD1 /* ActionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionViewController.swift; sourceTree = ""; }; 85482D8F2462DCD100EDEDD1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; @@ -1556,6 +1559,8 @@ 8563A03B1F9288D600F04442 /* BrowserChromeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserChromeManager.swift; sourceTree = ""; }; 8565A34A1FC8D96B00239327 /* LaunchTabNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchTabNotification.swift; sourceTree = ""; }; 8565A34C1FC8DFE400239327 /* LaunchTabNotificationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchTabNotificationTests.swift; sourceTree = ""; }; + 857229872BBEE74100E2E802 /* AppRatingPromptDatabaseMigrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRatingPromptDatabaseMigrationTests.swift; sourceTree = ""; }; + 857229892BBEF0C800E2E802 /* AppRatingPrompt_v1 */ = {isa = PBXFileReference; lastKnownFileType = folder; path = AppRatingPrompt_v1; sourceTree = ""; }; 8577A1C4255D2C0D00D43FCD /* HitTestingToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HitTestingToolbar.swift; sourceTree = ""; }; 857EEB742095FFAC008A005C /* HomeRowInstructionsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeRowInstructionsViewController.swift; sourceTree = ""; }; 858479C82B8792D800D156C1 /* HistoryManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryManager.swift; sourceTree = ""; }; @@ -3637,8 +3642,10 @@ 83134D7F20E2E013006CE65D /* Feedback */ = { isa = PBXGroup; children = ( + 857229892BBEF0C800E2E802 /* AppRatingPrompt_v1 */, 8528AE7D212EF5FF00D0BD74 /* AppRatingPromptTests.swift */, 8528AE82212FF91A00D0BD74 /* AppRatingPromptStorageTests.swift */, + 857229872BBEE74100E2E802 /* AppRatingPromptDatabaseMigrationTests.swift */, ); name = Feedback; sourceTree = ""; @@ -6274,6 +6281,7 @@ files = ( EA39B7E2268A1A35000C62CD /* privacy-reference-tests in Resources */, F17843E91F36226700390DCD /* MockFiles in Resources */, + 8572298A2BBEF0C800E2E802 /* AppRatingPrompt_v1 in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -7155,6 +7163,7 @@ B6AD9E3728D4510A0019CDE9 /* ContentBlockingUpdatingTests.swift in Sources */, C14882E427F20D9A00D59F0C /* BookmarksImporterTests.swift in Sources */, 8588026A24E424EE00C24AB6 /* AppWidthObserverTests.swift in Sources */, + 857229882BBEE74100E2E802 /* AppRatingPromptDatabaseMigrationTests.swift in Sources */, 8588026624E420BD00C24AB6 /* LargeOmniBarStateTests.swift in Sources */, 85AFA1212B45D14F0028A504 /* BookmarksMigrationAssertionTests.swift in Sources */, EE0153EB2A6FF970002A8B26 /* NetworkProtectionRootViewModelTests.swift in Sources */, @@ -10379,9 +10388,10 @@ 8528AE7F212F15D600D0BD74 /* AppRatingPrompt.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + 85481A6A2BA46AFB00F9EFB0 /* AppRatingPrompt 2.xcdatamodel */, 8528AE80212F15D600D0BD74 /* AppRatingPrompt.xcdatamodel */, ); - currentVersion = 8528AE80212F15D600D0BD74 /* AppRatingPrompt.xcdatamodel */; + currentVersion = 85481A6A2BA46AFB00F9EFB0 /* AppRatingPrompt 2.xcdatamodel */; path = AppRatingPrompt.xcdatamodeld; sourceTree = ""; versionGroupType = wrapper.xcdatamodel; diff --git a/DuckDuckGo/AppRatingPrompt.swift b/DuckDuckGo/AppRatingPrompt.swift index 203cf9c864..3dc2d7aa74 100644 --- a/DuckDuckGo/AppRatingPrompt.swift +++ b/DuckDuckGo/AppRatingPrompt.swift @@ -23,6 +23,8 @@ import CoreData protocol AppRatingPromptStorage { + var firstShown: Date? { get set } + var lastAccess: Date? { get set } var uniqueAccessDays: Int? { get set } @@ -35,11 +37,17 @@ class AppRatingPrompt { var storage: AppRatingPromptStorage + var uniqueAccessDays: Int { + storage.uniqueAccessDays ?? 0 + } + init(storage: AppRatingPromptStorage = AppRatingPromptCoreDataStorage()) { self.storage = storage } func registerUsage(onDate date: Date = Date()) { + guard storage.lastShown == nil else { return } + if !date.isSameDay(storage.lastAccess), let currentUniqueAccessDays = storage.uniqueAccessDays { storage.uniqueAccessDays = currentUniqueAccessDays + 1 } @@ -47,17 +55,39 @@ class AppRatingPrompt { } func shouldPrompt(onDate date: Date = Date()) -> Bool { - return [3, 7].contains(storage.uniqueAccessDays) && !date.isSameDay(storage.lastShown) + // To keep the database migration "lightweight" we just need to check if lastShown has been set yet. + // If it has then this user won't see any more prompts, which is preferable to seeing too many or too frequently. + if uniqueAccessDays >= 3 && storage.firstShown == nil && storage.lastShown == nil { + return true + } else if uniqueAccessDays >= 4 && storage.lastShown == nil { + return true + } + return false } func shown(onDate date: Date = Date()) { - storage.lastShown = date + if storage.firstShown == nil { + storage.firstShown = date + storage.uniqueAccessDays = 0 + } else if storage.lastShown == nil { + storage.lastShown = date + } } } class AppRatingPromptCoreDataStorage: AppRatingPromptStorage { + var firstShown: Date? { + get { + return ratingPromptEntity()?.firstShown + } + set { + ratingPromptEntity()?.firstShown = newValue + try? context.save() + } + } + var lastAccess: Date? { get { return ratingPromptEntity()?.lastAccess diff --git a/DuckDuckGo/AppRatingPrompt.xcdatamodeld/.xccurrentversion b/DuckDuckGo/AppRatingPrompt.xcdatamodeld/.xccurrentversion new file mode 100644 index 0000000000..99ecc96d71 --- /dev/null +++ b/DuckDuckGo/AppRatingPrompt.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + AppRatingPrompt 2.xcdatamodel + + diff --git a/DuckDuckGo/AppRatingPrompt.xcdatamodeld/AppRatingPrompt 2.xcdatamodel/contents b/DuckDuckGo/AppRatingPrompt.xcdatamodeld/AppRatingPrompt 2.xcdatamodel/contents new file mode 100644 index 0000000000..2493c81ebd --- /dev/null +++ b/DuckDuckGo/AppRatingPrompt.xcdatamodeld/AppRatingPrompt 2.xcdatamodel/contents @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index 5429c52948..43f2d05d91 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -1082,8 +1082,8 @@ extension TabViewController: WKNavigationDelegate { appRatingPrompt.registerUsage() if let scene = self.view.window?.windowScene, - appRatingPrompt.shouldPrompt(), - webView.url?.isDuckDuckGoSearch == true { + webView.url?.isDuckDuckGoSearch == true, + appRatingPrompt.shouldPrompt() { SKStoreReviewController.requestReview(in: scene) appRatingPrompt.shown() } diff --git a/DuckDuckGoTests/AppRatingPromptDatabaseMigrationTests.swift b/DuckDuckGoTests/AppRatingPromptDatabaseMigrationTests.swift new file mode 100644 index 0000000000..0220d1d30a --- /dev/null +++ b/DuckDuckGoTests/AppRatingPromptDatabaseMigrationTests.swift @@ -0,0 +1,77 @@ +// +// AppRatingPromptDatabaseMigrationTests.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 +import CoreData +import Persistence + +// If making future changes take a copy of the v2 momd and Database file like here and update / add tests as appropiate. +class AppRatingPromptDatabaseMigrationTests: XCTestCase { + + func testExpectedNumberOfModelVersionsInLatestModel() throws { + guard let modelURL = Bundle.main.url(forResource: "AppRatingPrompt", withExtension: "momd") else { + XCTFail("Error loading model URL") + return + } + let modelVersions = try FileManager.default.contentsOfDirectory(at: modelURL, includingPropertiesForKeys: nil, options: []) + .filter { $0.lastPathComponent.hasSuffix(".mom") } + XCTAssertEqual(2, modelVersions.count) + } + + func testMigrationFromV1toLatest() { + + guard let baseURL = Bundle(for: (type(of: self))).url(forResource: "AppRatingPrompt_v1", withExtension: nil) else { + XCTFail("could not get base url") + return + } + + guard let latestModel = CoreDataDatabase.loadModel(from: .main, named: "AppRatingPrompt") else { + XCTFail("could not load latest model") + return + } + + let storeURL = baseURL.appendingPathComponent("Database.sqlite") + + do { + let options = [NSMigratePersistentStoresAutomaticallyOption: true, + NSInferMappingModelAutomaticallyOption: true] + + // Run the migration + let newCoordinator = NSPersistentStoreCoordinator(managedObjectModel: latestModel) + let newStore = try newCoordinator.addPersistentStore(ofType: NSSQLiteStoreType, + configurationName: nil, + at: storeURL, + options: options) + + // Check the data exists + let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType) + context.persistentStoreCoordinator = newCoordinator + let fetchRequest = NSFetchRequest(entityName: "AppRatingPromptEntity") + let count = try context.count(for: fetchRequest) + XCTAssertGreaterThan(count, 0, "Migration failed, no entities found.") + + } catch { + XCTFail("Migration failed with error: \(error)") + } + + } + +} diff --git a/DuckDuckGoTests/AppRatingPromptTests.swift b/DuckDuckGoTests/AppRatingPromptTests.swift index 899034329a..f2a3186f97 100644 --- a/DuckDuckGoTests/AppRatingPromptTests.swift +++ b/DuckDuckGoTests/AppRatingPromptTests.swift @@ -29,98 +29,92 @@ class AppRatingPromptTests: XCTestCase { stub = AppRatingPromptStorageStub() } - func testWhenUserAlreadyShownThenDoesntShowAgainOnSameDay() { - - let stub = AppRatingPromptStorageStub() - - let appRatingPrompt = AppRatingPrompt(storage: stub) - appRatingPrompt.registerUsage(onDate: Date().inDays(fromNow: 0)) - appRatingPrompt.registerUsage(onDate: Date().inDays(fromNow: 1)) - appRatingPrompt.registerUsage(onDate: Date().inDays(fromNow: 2)) - - appRatingPrompt.shown(onDate: Date().inDays(fromNow: 2)) - - XCTAssertFalse(appRatingPrompt.shouldPrompt(onDate: Date().inDays(fromNow: 2))) - - } - - func testWhenUserAccessSeventhDayAfterSkippingSomeThenShouldPrompt() { - - let appRatingPrompt = AppRatingPrompt(storage: stub) - - appRatingPrompt.registerUsage(onDate: Date().inDays(fromNow: 0)) - appRatingPrompt.registerUsage(onDate: Date().inDays(fromNow: 1)) - appRatingPrompt.registerUsage(onDate: Date().inDays(fromNow: 2)) - appRatingPrompt.registerUsage(onDate: Date().inDays(fromNow: 3)) - - appRatingPrompt.registerUsage(onDate: Date().inDays(fromNow: 5)) - appRatingPrompt.registerUsage(onDate: Date().inDays(fromNow: 6)) - - appRatingPrompt.registerUsage(onDate: Date().inDays(fromNow: 7)) + func testPromptScenarios() { + + struct Scenario { + let currentUsageDay: Int + let firstShown: Date? + let lastShown: Date? + let shouldPrompt: Bool + } + + let scenarios = [ + Scenario(currentUsageDay: 0, firstShown: nil, lastShown: nil, shouldPrompt: false), + Scenario(currentUsageDay: 1, firstShown: nil, lastShown: nil, shouldPrompt: false), + Scenario(currentUsageDay: 2, firstShown: nil, lastShown: nil, shouldPrompt: false), + + // This is the first day that we should see first prompt + Scenario(currentUsageDay: 3, firstShown: nil, lastShown: nil, shouldPrompt: true), + + // But if not shown on that day it might happen later + Scenario(currentUsageDay: 4, firstShown: nil, lastShown: nil, shouldPrompt: true), + Scenario(currentUsageDay: 5, firstShown: nil, lastShown: nil, shouldPrompt: true), + Scenario(currentUsageDay: 6, firstShown: nil, lastShown: nil, shouldPrompt: true), + + // Showing it resets current usage day, so if first shown is set we shouldn't show the prompt again until later + Scenario(currentUsageDay: 0, firstShown: Date(), lastShown: nil, shouldPrompt: false), + Scenario(currentUsageDay: 1, firstShown: Date(), lastShown: nil, shouldPrompt: false), + Scenario(currentUsageDay: 2, firstShown: Date(), lastShown: nil, shouldPrompt: false), + Scenario(currentUsageDay: 3, firstShown: Date(), lastShown: nil, shouldPrompt: false), + + // This is the first day that we should see second prompt + Scenario(currentUsageDay: 4, firstShown: Date(), lastShown: nil, shouldPrompt: true), + + // But if not shown on that day it might happen later + Scenario(currentUsageDay: 5, firstShown: Date(), lastShown: nil, shouldPrompt: true), + Scenario(currentUsageDay: 6, firstShown: Date(), lastShown: nil, shouldPrompt: true), + Scenario(currentUsageDay: 7, firstShown: Date(), lastShown: nil, shouldPrompt: true), + + // Once last shown is set then we wouldn't show again + Scenario(currentUsageDay: 2, firstShown: Date(), lastShown: Date(), shouldPrompt: false), + Scenario(currentUsageDay: 3, firstShown: Date(), lastShown: Date(), shouldPrompt: false), + Scenario(currentUsageDay: 4, firstShown: Date(), lastShown: Date(), shouldPrompt: false), + Scenario(currentUsageDay: 5, firstShown: Date(), lastShown: Date(), shouldPrompt: false), + + // This scenario means the user migrated their database + Scenario(currentUsageDay: 3, firstShown: nil, lastShown: Date(), shouldPrompt: false), + Scenario(currentUsageDay: 4, firstShown: nil, lastShown: Date(), shouldPrompt: false), + Scenario(currentUsageDay: 5, firstShown: nil, lastShown: Date(), shouldPrompt: false), + ] + + for scenario in scenarios { + + let stub = AppRatingPromptStorageStub() + stub.firstShown = scenario.firstShown + stub.lastShown = scenario.lastShown + stub.uniqueAccessDays = scenario.currentUsageDay + + let appRatingPrompt = AppRatingPrompt(storage: stub) + XCTAssertEqual(scenario.shouldPrompt, appRatingPrompt.shouldPrompt(), "\(scenario)") + + } - XCTAssertTrue(appRatingPrompt.shouldPrompt(onDate: Date().inDays(fromNow: 7))) - } - func testWhenUserAccessFourthDayAfterSkippingSomeThenShouldNotPrompt() { - + func testWhenAppPromptIsShownThenUsageDaysIsReset() { + let stub = AppRatingPromptStorageStub() let appRatingPrompt = AppRatingPrompt(storage: stub) + appRatingPrompt.registerUsage(onDate: Date().inDays(fromNow: 0)) appRatingPrompt.registerUsage(onDate: Date().inDays(fromNow: 1)) appRatingPrompt.registerUsage(onDate: Date().inDays(fromNow: 2)) appRatingPrompt.registerUsage(onDate: Date().inDays(fromNow: 6)) - XCTAssertFalse(appRatingPrompt.shouldPrompt(onDate: Date().inDays(fromNow: 6))) - } + appRatingPrompt.shown() - func testWhenUserAccessThirdDayAfterSkippingOneThenShouldPrompt() { - - let stub = AppRatingPromptStorageStub() - - let appRatingPrompt = AppRatingPrompt(storage: stub) - appRatingPrompt.registerUsage(onDate: Date().inDays(fromNow: 0)) - appRatingPrompt.registerUsage(onDate: Date().inDays(fromNow: 1)) - appRatingPrompt.registerUsage(onDate: Date().inDays(fromNow: 3)) - XCTAssertTrue(appRatingPrompt.shouldPrompt(onDate: Date().inDays(fromNow: 3))) - + XCTAssertEqual(0, stub.uniqueAccessDays) } - func testWhenUserAccessThirdDayThenShouldPrompt() { + func testWhenRegisterUsageOnUniqueDaysThenIncrementsUsageCounter() { let stub = AppRatingPromptStorageStub() - let appRatingPrompt = AppRatingPrompt(storage: stub) - appRatingPrompt.registerUsage(onDate: Date().inDays(fromNow: 0)) - appRatingPrompt.registerUsage(onDate: Date().inDays(fromNow: 1)) - appRatingPrompt.registerUsage(onDate: Date().inDays(fromNow: 2)) - XCTAssertTrue(appRatingPrompt.shouldPrompt(onDate: Date().inDays(fromNow: 2))) - } - func testWhenUniqueAccessDaysIsNilDueToFetchErrorThenShouldNotPrompt() { - let storage = AppRatingPromptStorageStub() - storage.uniqueAccessDays = nil - let appRatingPrompt = AppRatingPrompt(storage: storage) appRatingPrompt.registerUsage(onDate: Date().inDays(fromNow: 0)) appRatingPrompt.registerUsage(onDate: Date().inDays(fromNow: 1)) appRatingPrompt.registerUsage(onDate: Date().inDays(fromNow: 2)) - XCTAssertFalse(appRatingPrompt.shouldPrompt(onDate: Date().inDays(fromNow: 2))) - } - - func testWhenUserAccessSecondUniqueDayThenShouldNotPrompt() { - - let stub = AppRatingPromptStorageStub() - let appRatingPrompt = AppRatingPrompt(storage: stub) - _ = appRatingPrompt.shouldPrompt(onDate: Date().inDays(fromNow: 0)) - XCTAssertFalse(appRatingPrompt.shouldPrompt(onDate: Date().inDays(fromNow: 1))) - - } - - func testWhenUserAccessSecondTimeOnFirstDayThenShouldNotPrompt() { + appRatingPrompt.registerUsage(onDate: Date().inDays(fromNow: 6)) - let stub = AppRatingPromptStorageStub() - let appRatingPrompt = AppRatingPrompt(storage: stub) - _ = appRatingPrompt.shouldPrompt(onDate: Date().inDays(fromNow: 0)) - XCTAssertFalse(appRatingPrompt.shouldPrompt(onDate: Date().inDays(fromNow: 0))) - + XCTAssertEqual(4, stub.uniqueAccessDays) } func testWhenUserAccessFirstDayThenShouldNotPrompt() { @@ -130,6 +124,8 @@ class AppRatingPromptTests: XCTestCase { private class AppRatingPromptStorageStub: AppRatingPromptStorage { + var firstShown: Date? + var lastAccess: Date? var uniqueAccessDays: Int? = 0 diff --git a/DuckDuckGoTests/AppRatingPrompt_v1/Database.sqlite b/DuckDuckGoTests/AppRatingPrompt_v1/Database.sqlite new file mode 100644 index 0000000000..3d7e0ea97c Binary files /dev/null and b/DuckDuckGoTests/AppRatingPrompt_v1/Database.sqlite differ diff --git a/DuckDuckGoTests/AppRatingPrompt_v1/Database.sqlite-shm b/DuckDuckGoTests/AppRatingPrompt_v1/Database.sqlite-shm new file mode 100644 index 0000000000..c875ef51eb Binary files /dev/null and b/DuckDuckGoTests/AppRatingPrompt_v1/Database.sqlite-shm differ diff --git a/DuckDuckGoTests/AppRatingPrompt_v1/Database.sqlite-wal b/DuckDuckGoTests/AppRatingPrompt_v1/Database.sqlite-wal new file mode 100644 index 0000000000..73a6273159 Binary files /dev/null and b/DuckDuckGoTests/AppRatingPrompt_v1/Database.sqlite-wal differ