Skip to content

Commit 7068a83

Browse files
authored
WMSDK-552: New public method refreshNotificationPermissionStatus (#615)
* WMSDK-552: Add `refreshNotificationPermissionStatus` and deprecate old API - Added new public method `refreshNotificationPermissionStatus()` to re-check system push notification authorization and send update to backend. - Marked `notificationsRequestAuthorization(granted:)` as deprecated and redirected it to the new method. - Updated internal implementation to use unified `checkNotificationStatus()` logic. * WMSDK-552: Fix versioning tests for `refreshNotificationPermissionStatus` - Use `CyclicUNAuthorizationStatusProvider` registration to specific test only - Split versioning test into separate tests for deprecated and new methods - Added helper methods with proper file/line parameters for better error reporting * WMSDK-552: Example - new `refreshNotificationPermissionStatus` method
1 parent 28b9de9 commit 7068a83

File tree

5 files changed

+97
-29
lines changed

5 files changed

+97
-29
lines changed

Example/Example/AppDelegate.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ final class AppDelegate: MindboxAppDelegate {
7676
if let error = error {
7777
print("NotificationsRequestAuthorization failed with error: \(error.localizedDescription)")
7878
}
79-
Mindbox.shared.notificationsRequestAuthorization(granted: granted)
79+
Mindbox.shared.refreshNotificationPermissionStatus()
8080
}
8181
}
8282
}

Mindbox/CoreController/CoreController.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,11 @@ final class CoreController {
8080
}
8181
}
8282

83-
func checkNotificationStatus(granted: Bool? = nil,
84-
completion: (() -> Void)? = nil) {
83+
func checkNotificationStatus(completion: (() -> Void)? = nil) {
8584
controllerQueue.async {
8685
defer { DispatchQueue.main.async { completion?() } }
8786

88-
let isNotificationsEnabled = granted ?? self.notificationStatus()
87+
let isNotificationsEnabled = self.notificationStatus()
8988
guard self.persistenceStorage.isNotificationsEnabled != isNotificationsEnabled else {
9089
return
9190
}

Mindbox/Mindbox.swift

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,9 +181,26 @@ public class Mindbox: NSObject {
181181
}
182182
}
183183

184-
/// Use this method to notify Mindbox for notification request authorization changes.
184+
/// Deprecated. Use ``Mindbox/Mindbox/refreshNotificationPermissionStatus()`` instead.
185+
///
186+
/// This method is kept for backward compatibility. The `granted` argument is ignored.
187+
/// The SDK reads the current system authorization status and, if it differs
188+
/// from the last known value, sends an update to the backend.
189+
@available(*, deprecated, message: "Use refreshNotificationPermissionStatus() instead.", renamed: "refreshNotificationPermissionStatus()")
185190
public func notificationsRequestAuthorization(granted: Bool) {
186-
coreController?.checkNotificationStatus(granted: granted)
191+
coreController?.checkNotificationStatus()
192+
}
193+
194+
/// Checks the current system authorization status for push notifications
195+
/// and reports any changes to Mindbox.
196+
///
197+
/// The SDK retrieves the current `UNAuthorizationStatus` from
198+
/// `UNUserNotificationCenter`, compares it with the last known value,
199+
/// and, if it has changed, sends the update to the backend.
200+
///
201+
/// - Important: This method does **not** prompt the system permission alert.
202+
public func refreshNotificationPermissionStatus() {
203+
coreController?.checkNotificationStatus()
187204
}
188205

189206
/**

MindboxTests/Mock/MockUNAuthorizationStatusProvider.swift

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
//
88

99
import Foundation
10-
import UIKit
10+
import UserNotifications
1111
@testable import Mindbox
1212

13-
class MockUNAuthorizationStatusProvider: UNAuthorizationStatusProviding {
13+
final class MockUNAuthorizationStatusProvider: UNAuthorizationStatusProviding {
1414

1515
func getStatus(result: @escaping (Bool) -> Void) {
1616
result(status.rawValue == UNAuthorizationStatus.authorized.rawValue)
@@ -22,3 +22,22 @@ class MockUNAuthorizationStatusProvider: UNAuthorizationStatusProviding {
2222
self.status = status
2323
}
2424
}
25+
26+
final class CyclicUNAuthorizationStatusProvider: UNAuthorizationStatusProviding {
27+
private let sequence: [UNAuthorizationStatus]
28+
private var index = 0
29+
30+
init(sequence: [UNAuthorizationStatus]) {
31+
precondition(!sequence.isEmpty, "Sequence must not be empty")
32+
self.sequence = sequence
33+
}
34+
35+
func getStatus(result: @escaping (Bool) -> Void) {
36+
let current = sequence[index % sequence.count]
37+
index += 1
38+
39+
var granted: [UNAuthorizationStatus] = [.authorized]
40+
if #available(iOS 12.0, *) { granted.append(.provisional) }
41+
result(granted.contains(current))
42+
}
43+
}

MindboxTests/Versioning/VersioningTestCase.swift

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -78,37 +78,33 @@ class VersioningTestCase: XCTestCase {
7878
}
7979
}
8080

81-
func testInfoUpdateVersioningByRequestAuthorization() {
81+
@available(*, deprecated, message: "Suppress `deprecated` notificationsRequestAuthorization(granted:) warning")
82+
func testInfoUpdateVersioningByDeprecatedMethod() {
83+
setupCyclicStatusProvider()
8284
initConfiguration(delay: .default)
8385

8486
self.guaranteedDeliveryManager.canScheduleOperations = false
8587
let infoUpdateLimit = 50
8688

87-
makeMockSequentialCall(limit: infoUpdateLimit) { index in
88-
Mindbox.shared.notificationsRequestAuthorization(granted: index % 2 == 0)
89+
makeMockSequentialCall(limit: infoUpdateLimit) { _ in
90+
Mindbox.shared.notificationsRequestAuthorization(granted: true)
8991
}
9092

91-
delay(of: .default)
93+
verifyVersioning(limit: infoUpdateLimit)
94+
}
95+
96+
func testInfoUpdateVersioningByRefreshNotificationPermissionStatus() {
97+
setupCyclicStatusProvider()
98+
initConfiguration(delay: .default)
9299

93-
do {
94-
let events = try self.databaseRepository.query(fetchLimit: infoUpdateLimit)
95-
XCTAssertNotEqual(events.count, 0)
96-
XCTAssertEqual(events.count, infoUpdateLimit)
100+
self.guaranteedDeliveryManager.canScheduleOperations = false
101+
let infoUpdateLimit = 50
97102

98-
events.forEach({
99-
XCTAssertTrue($0.type == .infoUpdated)
100-
})
101-
events
102-
.sorted { $0.dateTimeOffset > $1.dateTimeOffset }
103-
.compactMap { BodyDecoder<MobileApplicationInfoUpdated>(decodable: $0.body)?.body }
104-
.enumerated()
105-
.makeIterator()
106-
.forEach { offset, element in
107-
XCTAssertEqual(offset + 1, element.version, "Element version mismatch at offset \(offset + 1)")
108-
}
109-
} catch {
110-
XCTFail(error.localizedDescription)
103+
makeMockSequentialCall(limit: infoUpdateLimit) { _ in
104+
Mindbox.shared.refreshNotificationPermissionStatus()
111105
}
106+
107+
verifyVersioning(limit: infoUpdateLimit)
112108
}
113109
}
114110

@@ -227,4 +223,41 @@ private extension VersioningTestCase {
227223

228224
wait(for: [delayExpectation], timeout: of.timeInterval * 2)
229225
}
226+
227+
/// Sets up CyclicUNAuthorizationStatusProvider for versioning tests.
228+
/// Must use .container scope (singleton) to preserve state between calls.
229+
func setupCyclicStatusProvider() {
230+
MBInject.container.register(
231+
UNAuthorizationStatusProviding.self,
232+
scope: .container
233+
) {
234+
let seq: [UNAuthorizationStatus] = [.authorized, .denied]
235+
return CyclicUNAuthorizationStatusProvider(sequence: seq)
236+
}
237+
}
238+
239+
/// Verifies that events are properly versioned with sequential version numbers.
240+
func verifyVersioning(limit: Int, file: StaticString = #file, line: UInt = #line) {
241+
delay(of: .default)
242+
243+
do {
244+
let events = try self.databaseRepository.query(fetchLimit: limit)
245+
XCTAssertNotEqual(events.count, 0, file: file, line: line)
246+
XCTAssertEqual(events.count, limit, file: file, line: line)
247+
248+
events.forEach({
249+
XCTAssertTrue($0.type == .infoUpdated, file: file, line: line)
250+
})
251+
events
252+
.sorted { $0.dateTimeOffset > $1.dateTimeOffset }
253+
.compactMap { BodyDecoder<MobileApplicationInfoUpdated>(decodable: $0.body)?.body }
254+
.enumerated()
255+
.makeIterator()
256+
.forEach { offset, element in
257+
XCTAssertEqual(offset + 1, element.version, "Element version mismatch at offset \(offset + 1)", file: file, line: line)
258+
}
259+
} catch {
260+
XCTFail(error.localizedDescription, file: file, line: line)
261+
}
262+
}
230263
}

0 commit comments

Comments
 (0)