Use Cron for periodic backup refresh

This commit is contained in:
Max Radermacher
2025-11-21 15:05:14 -06:00
committed by GitHub
parent 2efe1f932e
commit a66d6e6923
12 changed files with 116 additions and 225 deletions

View File

@@ -33,8 +33,6 @@
04AB61C62E5E37A800405699 /* PollRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04AB61C52E5E37A400405699 /* PollRecord.swift */; };
04AB61C82E5E399700405699 /* PollOptionRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04AB61C72E5E399400405699 /* PollOptionRecord.swift */; };
04AB61CA2E5E449100405699 /* PollManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04AB61C92E5E448A00405699 /* PollManagerTest.swift */; };
04B975462E43A4AF00E20364 /* BackupRefreshManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B975452E43A4AA00E20364 /* BackupRefreshManager.swift */; };
04B975482E43BFE600E20364 /* BackupRefreshManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B975472E43BFE000E20364 /* BackupRefreshManagerTest.swift */; };
04BBBE902E259A6900E914B1 /* InactivePrimaryDeviceReminderMegaphone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04BBBE8F2E259A5F00E914B1 /* InactivePrimaryDeviceReminderMegaphone.swift */; };
04BBBE922E26C92D00E914B1 /* InactivePrimaryDeviceStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04BBBE912E26C92300E914B1 /* InactivePrimaryDeviceStore.swift */; };
04BBBE942E26F00900E914B1 /* InactivePrimaryDeviceStoreTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04BBBE932E26F00000E914B1 /* InactivePrimaryDeviceStoreTest.swift */; };
@@ -633,6 +631,8 @@
50423CA42BBF427900DCB8F5 /* StaleProfileFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50423CA32BBF427900DCB8F5 /* StaleProfileFetcher.swift */; };
504271B62BB4C54500E33C01 /* SystemContact.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504271B52BB4C54500E33C01 /* SystemContact.swift */; };
5042EAA3287F96FB00C9B19F /* VisibleBadgeResolverTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5042EAA2287F96FB00C9B19F /* VisibleBadgeResolverTest.swift */; };
50438A8E2ECBBDF600FCB28F /* BackupRefreshManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B975452E43A4AA00E20364 /* BackupRefreshManager.swift */; };
50438A8F2ECBBE1D00FCB28F /* BackupRefreshManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B975472E43BFE000E20364 /* BackupRefreshManagerTest.swift */; };
5045F44229E0DB7100058E5F /* LaunchJobs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 349C3636233D198300D52012 /* LaunchJobs.swift */; };
50468F2529EDD46500948E02 /* ParamParserTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EF7FC2107DDEE0007EBF7 /* ParamParserTest.swift */; };
50468F2929EE130A00948E02 /* InteractionStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50468F2829EE130A00948E02 /* InteractionStore.swift */; };
@@ -12568,7 +12568,6 @@
D923DF9B2DC135A000CDAFC3 /* BackupIdService.swift */,
D95D9D2F2E56866200855DE4 /* BackupKeyService.swift */,
D98ACAA82E0DA71700FA497F /* BackupPlanManager.swift */,
04B975452E43A4AA00E20364 /* BackupRefreshManager.swift */,
D9388C902DA4751F0048D4F9 /* BackupSettingsStore.swift */,
);
path = Settings;
@@ -12599,6 +12598,7 @@
D93FA5BE2DE77E440013879E /* BackupEnablingManager.swift */,
D93BDD932E43063B00779BD8 /* BackupKeepKeySafeSheet.swift */,
D98CA2B22DF2450E0060370E /* BackupRecordKeyViewController.swift */,
04B975452E43A4AA00E20364 /* BackupRefreshManager.swift */,
D932C0EA2E13AD3600FEF9C3 /* BackupSettingsAttachmentDownloadTracker.swift */,
D93964B52E038C7B00094117 /* BackupSettingsAttachmentUploadTracker.swift */,
D951F5302D9B236100C5EBF3 /* BackupSettingsViewController.swift */,
@@ -12901,6 +12901,7 @@
D9A952442E0A1C3600C3D1F7 /* Backups */ = {
isa = PBXGroup;
children = (
04B975472E43BFE000E20364 /* BackupRefreshManagerTest.swift */,
D962FC422E149EC7004DB623 /* BackupSettingsAttachmentDownloadTrackerTest.swift */,
D9324F7B2E1492820095D104 /* BackupSettingsAttachmentTrackerTest.swift */,
D9A952452E0A1C3C00C3D1F7 /* BackupSettingsAttachmentUploadTrackerTest.swift */,
@@ -12968,7 +12969,6 @@
D9F027A12E2071D0002994C1 /* BackupDisablingManagerTest.swift */,
041A5F082E05D3AC00FAED05 /* BackupEnablementReminderMegaphoneTests.swift */,
047A6DCF2E00B5640048EDF4 /* BackupKeyReminderMegaphoneTests.swift */,
04B975472E43BFE000E20364 /* BackupRefreshManagerTest.swift */,
04E66D432E00AB3A0059DBAC /* BackupSettingsStoreTests.swift */,
D9A36B922C7FEDA100CEC0E7 /* LineByLineStringDiff.swift */,
);
@@ -16881,6 +16881,7 @@
D98CA2AD2DF14A890060370E /* BackupOnboardingKeyIntroViewController.swift in Sources */,
B9A53B912CF507FB0000578B /* BackupProgressModal.swift in Sources */,
D98CA2B32DF245140060370E /* BackupRecordKeyViewController.swift in Sources */,
50438A8E2ECBBDF600FCB28F /* BackupRefreshManager.swift in Sources */,
0480F0002E57C51A006CBB29 /* BackupsEnabledNotificationMegaphone.swift in Sources */,
D932C0EB2E13AD3F00FEF9C3 /* BackupSettingsAttachmentDownloadTracker.swift in Sources */,
D93964B62E038C7B00094117 /* BackupSettingsAttachmentUploadTracker.swift in Sources */,
@@ -17588,6 +17589,7 @@
F9B93CE028E246D900B3F8A0 /* AppDelegateTest.swift in Sources */,
F9B3A92F293554090071EB95 /* ASWebAuthenticationSessionUtilTest.swift in Sources */,
50B6BCB42AEC58250010FB3B /* AuthorMergeHelperBuilderTest.swift in Sources */,
50438A8F2ECBBE1D00FCB28F /* BackupRefreshManagerTest.swift in Sources */,
D962FC432E149ECF004DB623 /* BackupSettingsAttachmentDownloadTrackerTest.swift in Sources */,
D9324F7C2E1492920095D104 /* BackupSettingsAttachmentTrackerTest.swift in Sources */,
D9A952462E0A1C4200C3D1F7 /* BackupSettingsAttachmentUploadTrackerTest.swift in Sources */,
@@ -17851,7 +17853,6 @@
D97054202CFE4D0200AC7954 /* BackupPaymentProcessor.swift in Sources */,
D98ACAA92E0DA71B00FA497F /* BackupPlanManager.swift in Sources */,
66B127FE2E3D352E006FE598 /* BackupPurpose.swift in Sources */,
04B975462E43A4AF00E20364 /* BackupRefreshManager.swift in Sources */,
C14391132BD1C0DF00ED6FCB /* BackupRequestManager.swift in Sources */,
C17310A72BD9D45400B7A151 /* BackupServiceAuth.swift in Sources */,
D9388C912DA475230048D4F9 /* BackupSettingsStore.swift in Sources */,
@@ -18924,7 +18925,6 @@
04AA85BC2E0EFC5C0051C0A7 /* BackupEnablementReminderMegaphoneTests.swift in Sources */,
047A6DD02E00B5720048EDF4 /* BackupKeyReminderMegaphoneTests.swift in Sources */,
66A1F4EB2E07CEA50095DE4B /* BackupListMediaManagerTests.swift in Sources */,
04B975482E43BFE600E20364 /* BackupRefreshManagerTest.swift in Sources */,
04E66D452E00AB6A0059DBAC /* BackupSettingsStoreTests.swift in Sources */,
F9426283289B1B5600460798 /* BlockingManagerTests.swift in Sources */,
D9C42C2F2B6C60600086B142 /* CallRecordDeleteManagerTest.swift in Sources */,

View File

@@ -756,6 +756,28 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
operation: { try await ViewOnceMessages.expireIfNecessary() },
)
let tsAccountManager = DependenciesBridge.shared.tsAccountManager
let backupRefreshManager = BackupRefreshManager(
accountKeyStore: DependenciesBridge.shared.accountKeyStore,
backupRequestManager: DependenciesBridge.shared.backupRequestManager,
backupSettingsStore: BackupSettingsStore(),
db: DependenciesBridge.shared.db,
networkManager: SSKEnvironment.shared.networkManagerRef,
)
cron.schedulePeriodically(
uniqueKey: .refreshBackup,
approximateInterval: BackupRefreshManager.backupRefreshTimeInterval,
mustBeRegistered: true,
mustBeConnected: true,
operation: {
guard let localIdentifiers = tsAccountManager.localIdentifiersWithMaybeSneakyTransaction else {
throw OWSAssertionError("never registered")
}
try await backupRefreshManager.refreshBackup(localIdentifiers: localIdentifiers)
},
)
// Note that this does much more than set a flag; it will also run all deferred blocks.
appReadiness.setAppIsReadyUIStillPending()
@@ -770,7 +792,6 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
appContext.appUserDefaults().removeObject(forKey: Constants.appLaunchesAttemptedKey)
}
let tsAccountManager = DependenciesBridge.shared.tsAccountManager
let recipientDatabaseTable = DependenciesBridge.shared.recipientDatabaseTable
let tsRegistrationState: TSRegistrationState = SSKEnvironment.shared.databaseStorageRef.read { tx in
let registrationState = tsAccountManager.registrationState(tx: tx)

View File

@@ -159,7 +159,6 @@ public class AppEnvironment: NSObject {
let accountEntropyPoolManager = DependenciesBridge.shared.accountEntropyPoolManager
let backupDisablingManager = DependenciesBridge.shared.backupDisablingManager
let backupIdService = DependenciesBridge.shared.backupIdService
let backupRefreshManager = DependenciesBridge.shared.backupRefreshManager
let backupSubscriptionManager = DependenciesBridge.shared.backupSubscriptionManager
let backupTestFlightEntitlementManager = DependenciesBridge.shared.backupTestFlightEntitlementManager
let callRecordStore = DependenciesBridge.shared.callRecordStore
@@ -195,12 +194,10 @@ public class AppEnvironment: NSObject {
let (
isRegisteredPrimaryDevice,
isRegistered,
localIdentifiers
) = db.read { tx in
(
tsAccountManager.registrationState(tx: tx).isRegisteredPrimaryDevice,
tsAccountManager.registrationState(tx: tx).isRegistered,
tsAccountManager.localIdentifiers(tx: tx),
)
}
@@ -247,25 +244,6 @@ public class AppEnvironment: NSObject {
}
}
// Things that should run on only registered devices, both linked & primary.
if isRegistered {
Task {
guard let localIdentifiers else {
owsFailDebug("Registered but no local identifiers")
return
}
do {
try await backupRefreshManager.refreshBackupIfNeeded(
localIdentifiers: localIdentifiers,
auth: .implicit()
)
} catch {
owsFailDebug("Failed to refresh backup \(error)")
}
}
}
Task {
await db.awaitableWrite { tx in
masterKeySyncManager.runStartupJobs(tx: tx)

View File

@@ -4,20 +4,20 @@
//
import LibSignalClient
import SignalServiceKit
// MARK: -
public final class BackupRefreshManager {
final class BackupRefreshManager {
private let accountKeyStore: AccountKeyStore
private let api: NetworkAPI
private let backupRequestManager: BackupRequestManager
private let backupSettingsStore: BackupSettingsStore
private let db: DB
private let dateProvider: DateProvider
// Backups may expire after 30 days. Do a refresh every few days
// to account for if the app isn't opened often.
private let backupRefreshTimeInterval: TimeInterval = 3 * .day
static let backupRefreshTimeInterval: TimeInterval = 3 * .day
init(
accountKeyStore: AccountKeyStore,
@@ -25,24 +25,12 @@ public final class BackupRefreshManager {
backupSettingsStore: BackupSettingsStore,
db: DB,
networkManager: NetworkManagerProtocol,
dateProvider: @escaping DateProvider
) {
self.accountKeyStore = accountKeyStore
self.api = NetworkAPI(networkManager: networkManager)
self.backupRequestManager = backupRequestManager
self.backupSettingsStore = backupSettingsStore
self.db = db
self.dateProvider = dateProvider
}
private func needsBackupRefresh() -> Bool {
db.read { tx in
let maxDaysAgo = dateProvider().addingTimeInterval(-1 * backupRefreshTimeInterval)
let backupPlan = backupSettingsStore.backupPlan(tx: tx)
let lastBackupRefreshDate = backupSettingsStore.lastBackupRefreshDate(tx: tx) ?? Date.distantPast
return backupPlan != .disabled && lastBackupRefreshDate < maxDaysAgo
}
}
private func rootBackupKeys(localIdentifiers: LocalIdentifiers) async throws -> (MessageRootBackupKey, MediaRootBackupKey) {
@@ -59,32 +47,29 @@ public final class BackupRefreshManager {
}
}
public func refreshBackupIfNeeded(
localIdentifiers: LocalIdentifiers,
auth: ChatServiceAuth
) async throws {
guard needsBackupRefresh() else {
func refreshBackup(localIdentifiers: LocalIdentifiers) async throws {
let backupPlan = db.read(block: backupSettingsStore.backupPlan(tx:))
switch backupPlan {
case .disabled:
return
case .disabling, .free, .paid, .paidExpiringSoon, .paidAsTester:
break
}
let (messageBackupKey, mediaBackupKey) = try await rootBackupKeys(localIdentifiers: localIdentifiers)
let messageBackupAuth = try await backupRequestManager.fetchBackupServiceAuth(
for: messageBackupKey,
localAci: localIdentifiers.aci,
auth: auth
auth: .implicit(),
)
try await api.refreshBackup(auth: messageBackupAuth)
let mediaBackupAuth = try await backupRequestManager.fetchBackupServiceAuth(
for: mediaBackupKey,
localAci: localIdentifiers.aci,
auth: auth
auth: .implicit(),
)
try await api.refreshBackup(auth: mediaBackupAuth)
await db.awaitableWrite { tx in
backupSettingsStore.setLastBackupRefreshDate(dateProvider(), tx: tx)
}
}
// MARK: -

View File

@@ -0,0 +1,66 @@
//
// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
@testable import Signal
@testable import SignalServiceKit
import Testing
@MainActor
struct BackupRefreshManagerTest {
private let backupSettingsStore: BackupSettingsStore
private let db: InMemoryDB
private let accountKeyStore: AccountKeyStore
private let mockNetworkManager: MockNetworkManager
private let mockBackupRefreshManager: BackupRefreshManager
init() {
self.backupSettingsStore = BackupSettingsStore()
self.db = InMemoryDB()
self.accountKeyStore = AccountKeyStore(
backupSettingsStore: backupSettingsStore,
)
self.mockNetworkManager = MockNetworkManager()
self.mockBackupRefreshManager = BackupRefreshManager(
accountKeyStore: accountKeyStore,
backupRequestManager: BackupRequestManagerMock(),
backupSettingsStore: backupSettingsStore,
db: db,
networkManager: mockNetworkManager,
)
}
var refreshSuccessResponse: (TSRequest, NetworkManager.RetryPolicy) async throws -> HTTPResponse = { request, _ in
if request.url.absoluteString.hasSuffix("v1/archives") {
return HTTPResponse(requestUrl: request.url, status: 204, headers: HttpHeaders(), bodyData: Data())
}
throw OWSAssertionError("")
}
@Test
func testRefresh() async throws {
// Set up.
db.write { tx in
backupSettingsStore.setBackupPlan(.free, tx: tx)
accountKeyStore.setAccountEntropyPool(AccountEntropyPool(), tx: tx)
}
mockNetworkManager.asyncRequestHandlers.append(refreshSuccessResponse)
mockNetworkManager.asyncRequestHandlers.append(refreshSuccessResponse)
try await mockBackupRefreshManager.refreshBackup(localIdentifiers: LocalIdentifiers.forUnitTests)
#expect(mockNetworkManager.asyncRequestHandlers.isEmpty)
}
@Test
func testDisabledBackupIgnoresRefresh() async throws {
// Set up.
db.write { tx in
backupSettingsStore.setBackupPlan(.disabled, tx: tx)
accountKeyStore.setAccountEntropyPool(AccountEntropyPool(), tx: tx)
}
try await mockBackupRefreshManager.refreshBackup(localIdentifiers: LocalIdentifiers.forUnitTests)
}
}

View File

@@ -150,7 +150,7 @@ public protocol BackupRequestManager {
}
extension BackupRequestManager {
func fetchBackupServiceAuth(
public func fetchBackupServiceAuth(
for key: BackupKeyMaterial,
localAci: Aci,
auth: ChatServiceAuth,

View File

@@ -72,7 +72,6 @@ public struct BackupSettingsStore {
static let shouldOptimizeLocalStorage = "shouldOptimizeLocalStorage"
static let lastRecoveryKeyReminderDate = "lastBackupKeyReminderDate"
static let haveSetBackupID = "haveSetBackupID"
static let lastBackupRefreshDate = "lastBackupRefreshDate"
static let lastBackupEnabledDetails = "lastBackupEnabledDetails"
static let backgroundBackupErrorCount = "backgroundBackupErrorCount"
@@ -81,10 +80,12 @@ public struct BackupSettingsStore {
private let kvStore: KeyValueStore
private let errorStateStore: KeyValueStore
private let refreshBackupStore: CronStore
public init() {
kvStore = KeyValueStore(collection: "BackupSettingsStore")
errorStateStore = KeyValueStore(collection: "BackupSettingsErrorStateStore")
refreshBackupStore = CronStore(uniqueKey: .refreshBackup)
}
// MARK: -
@@ -234,7 +235,7 @@ public struct BackupSettingsStore {
setFirstBackupDate(date, tx: tx)
}
setLastBackupRefreshDate(date, tx: tx)
refreshBackupStore.setMostRecentDate(Date(), jitter: 0, tx: tx)
// We did a backup, so clear all error state.
errorStateStore.removeAll(transaction: tx)
@@ -248,7 +249,7 @@ public struct BackupSettingsStore {
kvStore.removeValue(forKey: Keys.lastBackupDate, transaction: tx)
kvStore.removeValue(forKey: Keys.lastBackupFileSizeBytes, transaction: tx)
kvStore.removeValue(forKey: Keys.lastBackupSizeBytes, transaction: tx)
setLastBackupRefreshDate(nil, tx: tx)
refreshBackupStore.setMostRecentDate(.distantPast, jitter: 0, tx: tx)
tx.addSyncCompletion {
NotificationCenter.default.postOnMainThread(name: .lastBackupDetailsDidChange, object: nil)
@@ -419,20 +420,6 @@ public struct BackupSettingsStore {
public func setHaveSetBackupID(haveSetBackupID: Bool, tx: DBWriteTransaction) {
kvStore.setBool(haveSetBackupID, key: Keys.haveSetBackupID, transaction: tx)
}
// MARK: -
public func lastBackupRefreshDate(tx: DBReadTransaction) -> Date? {
return kvStore.getDate(Keys.lastBackupRefreshDate, transaction: tx)
}
public func setLastBackupRefreshDate(_ lastBackupRefreshDate: Date?, tx: DBWriteTransaction) {
if let lastBackupRefreshDate {
kvStore.setDate(lastBackupRefreshDate, key: Keys.lastBackupRefreshDate, transaction: tx)
} else {
kvStore.removeValue(forKey: Keys.lastBackupRefreshDate, transaction: tx)
}
}
}
fileprivate extension BackupPlan {

View File

@@ -1047,15 +1047,6 @@ extension AppSetup.GlobalsContinuation {
storyRecipientStore: storyRecipientStore
)
let backupRefreshManager = BackupRefreshManager(
accountKeyStore: accountKeyStore,
backupRequestManager: backupRequestManager,
backupSettingsStore: backupSettingsStore,
db: db,
networkManager: networkManager,
dateProvider: dateProvider
)
let accountEntropyPoolManager = AccountEntropyPoolManagerImpl(
accountAttributesUpdater: accountAttributesUpdater,
accountKeyStore: accountKeyStore,
@@ -1654,7 +1645,6 @@ extension AppSetup.GlobalsContinuation {
backupKeyService: backupKeyService,
backupListMediaManager: backupListMediaManager,
backupListMediaStore: backupListMediaStore,
backupRefreshManager: backupRefreshManager,
backupRequestManager: backupRequestManager,
backupPlanManager: backupPlanManager,
backupSubscriptionManager: backupSubscriptionManager,

View File

@@ -79,9 +79,8 @@ public class DependenciesBridge {
public let backupKeyService: BackupKeyService
public let backupListMediaManager: BackupListMediaManager
public let backupListMediaStore: BackupListMediaStore
public let backupRefreshManager: BackupRefreshManager
public let backupRequestManager: BackupRequestManager
public let backupPlanManager: BackupPlanManager
public let backupRequestManager: BackupRequestManager
public let backupSubscriptionManager: BackupSubscriptionManager
public let backupTestFlightEntitlementManager: BackupTestFlightEntitlementManager
public let badgeCountFetcher: BadgeCountFetcher
@@ -218,7 +217,6 @@ public class DependenciesBridge {
backupKeyService: BackupKeyService,
backupListMediaManager: BackupListMediaManager,
backupListMediaStore: BackupListMediaStore,
backupRefreshManager: BackupRefreshManager,
backupRequestManager: BackupRequestManager,
backupPlanManager: BackupPlanManager,
backupSubscriptionManager: BackupSubscriptionManager,
@@ -356,7 +354,6 @@ public class DependenciesBridge {
self.backupKeyService = backupKeyService
self.backupListMediaManager = backupListMediaManager
self.backupListMediaStore = backupListMediaStore
self.backupRefreshManager = backupRefreshManager
self.backupRequestManager = backupRequestManager
self.backupPlanManager = backupPlanManager
self.backupSubscriptionManager = backupSubscriptionManager

View File

@@ -74,6 +74,7 @@ public class Cron {
case fetchStaleGroup
case fetchStaleProfiles
case fetchSubscriptionConfig
case refreshBackup
case updateAttributes
}

View File

@@ -1,134 +0,0 @@
//
// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Testing
@testable import SignalServiceKit
@MainActor
struct BackupRefreshManagerTest {
private let backupSettingsStore: BackupSettingsStore
private let db: InMemoryDB
private let accountKeyStore: AccountKeyStore
private let mockNetworkManager: MockNetworkManager
private let mockBackupRefreshManager: BackupRefreshManager
init() {
self.backupSettingsStore = BackupSettingsStore()
self.db = InMemoryDB()
self.accountKeyStore = AccountKeyStore(
backupSettingsStore: backupSettingsStore,
)
self.mockNetworkManager = MockNetworkManager()
self.mockBackupRefreshManager = BackupRefreshManager(
accountKeyStore: accountKeyStore,
backupRequestManager: BackupRequestManagerMock(),
backupSettingsStore: backupSettingsStore,
db: db,
networkManager: mockNetworkManager,
dateProvider: { Date() }
)
}
var refreshSuccessResponse: (TSRequest, NetworkManager.RetryPolicy) async throws -> HTTPResponse = { request, _ in
if request.url.absoluteString.hasSuffix("v1/archives") {
return HTTPResponse(requestUrl: request.url, status: 204, headers: HttpHeaders(), bodyData: Data())
}
throw OWSAssertionError("")
}
@Test
func testMissingBackupTriggersRefresh() async throws {
let testStart = Date()
// Set up.
db.write { tx in
backupSettingsStore.setBackupPlan(.free, tx: tx)
accountKeyStore.setAccountEntropyPool(AccountEntropyPool(), tx: tx)
#expect(backupSettingsStore.lastBackupRefreshDate(tx: tx) == nil)
}
mockNetworkManager.asyncRequestHandlers.append(refreshSuccessResponse)
mockNetworkManager.asyncRequestHandlers.append(refreshSuccessResponse)
try await mockBackupRefreshManager.refreshBackupIfNeeded(localIdentifiers: LocalIdentifiers.forUnitTests, auth: .implicit())
db.read { tx in
#expect(
backupSettingsStore.lastBackupRefreshDate(tx: tx) ?? Date.distantPast > testStart,
"Refresh date should update"
)
}
}
@Test
func testOldBackupTriggersRefresh() async throws {
let thirtyDaysAgo = Date(timeIntervalSinceNow: -5 * .day)
let testStart = Date()
// Set up.
db.write { tx in
backupSettingsStore.setBackupPlan(.free, tx: tx)
accountKeyStore.setAccountEntropyPool(AccountEntropyPool(), tx: tx)
backupSettingsStore.setLastBackupRefreshDate(thirtyDaysAgo, tx: tx)
}
mockNetworkManager.asyncRequestHandlers.append(refreshSuccessResponse)
mockNetworkManager.asyncRequestHandlers.append(refreshSuccessResponse)
try await mockBackupRefreshManager.refreshBackupIfNeeded(localIdentifiers: LocalIdentifiers.forUnitTests, auth: .implicit())
db.read { tx in
#expect(
backupSettingsStore.lastBackupRefreshDate(tx: tx) ?? Date.distantPast > testStart,
"Refresh date should update"
)
}
}
@Test
func testRecentBackupIgnoresRefresh() async throws {
let oneDayAgo = Date(timeIntervalSinceNow: -1 * .day)
// Set up.
db.write { tx in
backupSettingsStore.setBackupPlan(.free, tx: tx)
backupSettingsStore.setLastBackupRefreshDate(oneDayAgo, tx: tx)
accountKeyStore.setAccountEntropyPool(AccountEntropyPool(), tx: tx)
}
mockNetworkManager.asyncRequestHandlers.append(refreshSuccessResponse)
mockNetworkManager.asyncRequestHandlers.append(refreshSuccessResponse)
try await mockBackupRefreshManager.refreshBackupIfNeeded(localIdentifiers: LocalIdentifiers.forUnitTests, auth: .implicit())
db.read { tx in
#expect(
(backupSettingsStore.lastBackupRefreshDate(tx: tx) ?? Date.distantPast).timeIntervalSince1970 == oneDayAgo.timeIntervalSince1970,
"Refresh date should not update since last backup was too recent"
)
}
}
@Test
func testDisabledBackupIgnoresRefresh() async throws {
let testStart = Date()
// Set up.
db.write { tx in
backupSettingsStore.setBackupPlan(.disabled, tx: tx)
accountKeyStore.setAccountEntropyPool(AccountEntropyPool(), tx: tx)
#expect(backupSettingsStore.lastBackupRefreshDate(tx: tx) == nil)
}
mockNetworkManager.asyncRequestHandlers.append(refreshSuccessResponse)
mockNetworkManager.asyncRequestHandlers.append(refreshSuccessResponse)
try await mockBackupRefreshManager.refreshBackupIfNeeded(localIdentifiers: LocalIdentifiers.forUnitTests, auth: .implicit())
db.read { tx in
#expect(
backupSettingsStore.lastBackupRefreshDate(tx: tx) ?? Date.distantPast < testStart,
"Refresh date should not update since backups is disabled"
)
}
}
}

View File

@@ -52,18 +52,18 @@ class BackupSettingsStoreTests: XCTestCase {
func testBackupUpdatesRefreshDate() throws {
var lastBackupRefresh = db.read { tx in
backupSettingsStore.lastBackupRefreshDate(tx: tx)
CronStore(uniqueKey: .refreshBackup).mostRecentDate(tx: tx)
}
XCTAssertNil(lastBackupRefresh, "Last backup should not be set")
XCTAssertEqual(lastBackupRefresh, .distantPast, "Last backup should not be set")
db.write { tx in
backupSettingsStore.setLastBackupDate(Date(), tx: tx)
}
lastBackupRefresh = db.read { tx in
backupSettingsStore.lastBackupRefreshDate(tx: tx)
CronStore(uniqueKey: .refreshBackup).mostRecentDate(tx: tx)
}
XCTAssertNotNil(lastBackupRefresh, "Last backup should be set")
XCTAssertNotEqual(lastBackupRefresh, .distantPast, "Last backup should be set")
}
}