TM-SGNL-iOS/Signal/test/util/APNSRotationStoreTest.swift
TeleMessage developers dde0620daf initial commit
2025-05-03 12:28:28 -07:00

313 lines
11 KiB
Swift

//
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import XCTest
@testable import SignalServiceKit
final class APNSRotationStoreTest: SignalBaseTest {
lazy var messageFactory = IncomingMessageFactory()
override func setUp() {
super.setUp()
let remoteConfigManager = SSKEnvironment.shared.remoteConfigManagerRef as! StubbableRemoteConfigManager
remoteConfigManager.cachedConfig = RemoteConfig(
clockSkew: 0,
isEnabledFlags: ["ios.enableAutoAPNSRotation": true],
valueFlags: [:],
timeGatedFlags: [:]
)
}
override func tearDown() {
super.tearDown()
APNSRotationStore.nowMs = { return Date().ows_millisecondsSince1970 }
}
func testHasNoPushToken() {
// Make sure we don't have an APNS token
SSKEnvironment.shared.preferencesRef.removeAllValues()
let now = Date().ows_millisecondsSince1970
// Make sure we are otherwise eligible to rotate, so
// that we know it is the lack of an APNS token that stopped it.
write { transaction in
// Mark as checked in the past so that we'd be eligible after an app update.
APNSRotationStore.nowMs = {
return now
- APNSRotationStore.Constants.appVersionBakeTimeMs
- 1
}
APNSRotationStore.setAppVersionTimeForAPNSRotationIfNeeded(transaction: transaction)
// Fake a missed message so we'd rotate.
self.createIncomingMessage(
receivedTimestamp: now - kMinuteInMs,
transaction: transaction
)
}
read {
APNSRotationStore.nowMs = { now }
// No rotation because there's no APNS token.
XCTAssertFalse(APNSRotationStore.canRotateAPNSToken(transaction: $0))
}
// Set an APNS token.
write { tx in
SSKEnvironment.shared.preferencesRef.setPushToken("123", tx: tx)
}
read {
// Nothing changed but the token, but we should now rotate.
XCTAssert(APNSRotationStore.canRotateAPNSToken(transaction: $0))
}
}
func testHasWorkingPushToken() {
let now = Date().ows_millisecondsSince1970
// Make sure we are otherwise eligible to rotate, so
// that we know it is having a good APNS token that stopped it.
// Set an APNS token.
write { tx in
SSKEnvironment.shared.preferencesRef.setPushToken("123", tx: tx)
}
write { transaction in
// Mark as checked in the past so that we'd be eligible after an app update.
APNSRotationStore.nowMs = {
return now
- APNSRotationStore.Constants.appVersionBakeTimeMs
- 1
}
APNSRotationStore.setAppVersionTimeForAPNSRotationIfNeeded(transaction: transaction)
}
read {
APNSRotationStore.nowMs = { now }
// Make sure we need to rotate before marking the token as good.
XCTAssert(APNSRotationStore.canRotateAPNSToken(transaction: $0))
}
// Now mark receiving a push (marking the token as working)
write {
APNSRotationStore.didReceiveAPNSPush(transaction: $0)
}
read {
// The token should now be marked as good and not needing rotation.
XCTAssertFalse(APNSRotationStore.canRotateAPNSToken(transaction: $0))
}
// Change the token!
write { tx in
SSKEnvironment.shared.preferencesRef.setPushToken("abc", tx: tx)
}
read {
// Now we need to rotate again because the token changed but we had missed
// messages.
XCTAssert(APNSRotationStore.canRotateAPNSToken(transaction: $0))
}
}
func testHasWorkingPushTokenFromLongAgo() {
let now = Date().ows_millisecondsSince1970
// Make sure we are otherwise eligible to rotate, so
// that we know the determining factor is the APNS token.
// Set an APNS token.
write { tx in
SSKEnvironment.shared.preferencesRef.setPushToken("123", tx: tx)
}
write { transaction in
// Mark as checked in the past so that we'd be eligible after an app update.
APNSRotationStore.nowMs = {
return now
- APNSRotationStore.Constants.appVersionBakeTimeMs
- 1
}
APNSRotationStore.setAppVersionTimeForAPNSRotationIfNeeded(transaction: transaction)
}
read {
APNSRotationStore.nowMs = { now }
// Make sure we need to rotate before marking the token as good.
XCTAssert(APNSRotationStore.canRotateAPNSToken(transaction: $0))
}
// Now mark receiving a push (marking the token as working)
write {
APNSRotationStore.didReceiveAPNSPush(transaction: $0)
}
read {
// The token should now be marked as good and not needing rotation.
XCTAssertFalse(APNSRotationStore.canRotateAPNSToken(transaction: $0))
}
// Let some time pass, but not too long.
APNSRotationStore.nowMs = { now + APNSRotationStore.Constants.lastKnownWorkingAPNSTokenExpirationTimeMs - 1 }
read {
// Still no need to rotate, it was marked good a short time ago.
XCTAssertFalse(APNSRotationStore.canRotateAPNSToken(transaction: $0))
}
// Now move the time far up, long since we marked the token as good,
// so it can now be rotated.
APNSRotationStore.nowMs = { now + APNSRotationStore.Constants.lastKnownWorkingAPNSTokenExpirationTimeMs + 1 }
read {
// Now we need to rotate because it was marked good too long ago.
XCTAssert(APNSRotationStore.canRotateAPNSToken(transaction: $0))
}
}
func testHasUpdatedRecently() {
// Make sure we have an APNS Token
write { tx in
SSKEnvironment.shared.preferencesRef.setPushToken("123", tx: tx)
}
let now = Date().ows_millisecondsSince1970
// Should not want to rotate, because the NSE hasn't even had a chance
// to write anything.
read {
APNSRotationStore.nowMs = { now }
XCTAssertFalse(APNSRotationStore.canRotateAPNSToken(transaction: $0))
}
write {
// mark the app version.
APNSRotationStore.nowMs = { now }
APNSRotationStore.setAppVersionTimeForAPNSRotationIfNeeded(transaction: $0)
}
read {
// Simulate time having passed
APNSRotationStore.nowMs = { now + APNSRotationStore.Constants.appVersionBakeTimeMs + 1 }
// Now check, we should be eligible to rotate.
XCTAssert(APNSRotationStore.canRotateAPNSToken(transaction: $0))
}
}
func testHasRotatedRecently() {
// Make sure we have an APNS Token
write { tx in
SSKEnvironment.shared.preferencesRef.setPushToken("123", tx: tx)
}
let now = Date().ows_millisecondsSince1970
// Make sure we are otherwise eligible to rotate, so
// that we know it is the the recent rotation that stopped it.
write { transaction in
// Mark as checked in the past so that we'd be eligible after an app update.
APNSRotationStore.nowMs = {
return now
- APNSRotationStore.Constants.appVersionBakeTimeMs
- 1
}
APNSRotationStore.setAppVersionTimeForAPNSRotationIfNeeded(transaction: transaction)
// But make a recent rotation.
APNSRotationStore.nowMs = { now - APNSRotationStore.Constants.minRotationInterval + 1 }
APNSRotationStore.didRotateAPNSToken(transaction: transaction)
}
read {
APNSRotationStore.nowMs = { now }
// No rotation because we just rotated recently.
XCTAssertFalse(APNSRotationStore.canRotateAPNSToken(transaction: $0))
// Move time forward.
APNSRotationStore.nowMs = { now + APNSRotationStore.Constants.minRotationInterval + 1 }
// Now we want to rotate
XCTAssert(APNSRotationStore.canRotateAPNSToken(transaction: $0))
}
}
func testRecentMissedMessages() {
// Make sure we have an APNS Token
write { tx in
SSKEnvironment.shared.preferencesRef.setPushToken("123", tx: tx)
}
let now = Date().ows_millisecondsSince1970
let lastPushTime = now
- APNSRotationStore.Constants.appVersionBakeTimeMs
- 1
write { transaction in
// Mark as checked in the past so that we'd be eligible after an app update.
APNSRotationStore.nowMs = {
return lastPushTime
}
APNSRotationStore.setAppVersionTimeForAPNSRotationIfNeeded(transaction: transaction)
APNSRotationStore.didReceiveAPNSPush(transaction: transaction)
}
// Change the token so its not marked good anymore.
write { tx in
SSKEnvironment.shared.preferencesRef.setPushToken("abc", tx: tx)
}
read {
APNSRotationStore.nowMs = { now }
// We should be eligible for a rotation
XCTAssert(APNSRotationStore.canRotateAPNSToken(transaction: $0))
}
// Insert a message in the past.
write { transaction in
self.createIncomingMessage(
receivedTimestamp: lastPushTime,
transaction: transaction
)
}
// We shouldn't rotate without unprocessed messages.
var onMessagesFlushed = APNSRotationStore.rotateIfNeededOnAppLaunchAndReadiness(performRotation: {
XCTFail("Rotating when we shouldn't!")
})
XCTAssertNotNil(onMessagesFlushed)
onMessagesFlushed?()
// But if we insert some messages on app launch we should rotate!
var didRotate = false
onMessagesFlushed = APNSRotationStore.rotateIfNeededOnAppLaunchAndReadiness(performRotation: {
didRotate = true
})
XCTAssertNotNil(onMessagesFlushed)
XCTAssertFalse(didRotate)
// Insert a message.
write { transaction in
self.createIncomingMessage(
receivedTimestamp: now,
transaction: transaction
)
}
onMessagesFlushed?()
XCTAssert(didRotate)
}
// MARK: - Helpers
private func createIncomingMessage(
receivedTimestamp: UInt64,
transaction: SDSAnyWriteTransaction
) {
let message = self.messageFactory.create(transaction: transaction)
message.replaceReceived(atTimestamp: receivedTimestamp, transaction: transaction)
}
}