@@ -1251,7 +1251,230 @@ class AuthTests: XCTestCase {
12511251 _ = authDelegate
12521252 }
12531253
1254+ func testLogoutUserWithHandlersAutoPushOnDisableDeviceSucceeds( ) {
1255+ let logoutSucceeded = expectation ( description: " logout success handler called " )
1256+ let token = " zeeToken " . data ( using: . utf8) !
1257+ let networkSession = MockNetworkSession ( statusCode: 200 )
1258+ let context = createLogoutTestContext ( autoPushRegistration: true , networkSession: networkSession)
1259+
1260+ context. api. email = AuthTests . email
1261+ context. api. register ( token: token)
1262+
1263+ context. api. logoutUser ( withOnSuccess: { _ in
1264+ logoutSucceeded. fulfill ( )
1265+ } , onFailure: { reason, _ in
1266+ XCTFail ( " logout should not fail: \( reason ?? " nil " ) " )
1267+ } )
1268+
1269+ wait ( for: [ logoutSucceeded] , timeout: testExpectationTimeout)
1270+
1271+ assertLogoutCleanupCompleted ( context)
1272+
1273+ guard let request = networkSession. getRequest ( withEndPoint: Const . Path. disableDevice) ,
1274+ let body = TestUtils . getRequestBody ( request: request) else {
1275+ XCTFail ( " Expected disableDevice request " )
1276+ return
1277+ }
1278+
1279+ TestUtils . validateElementPresent ( withName: JsonKey . token, andValue: token. hexString ( ) , inDictionary: body)
1280+ TestUtils . validateElementPresent ( withName: JsonKey . email, andValue: AuthTests . email, inDictionary: body)
1281+ }
1282+
1283+ func testLogoutUserWithHandlersAutoPushOnDisableDeviceFails( ) {
1284+ let logoutFailed = expectation ( description: " logout failure handler called " )
1285+ let expectedReason = " disable failed "
1286+ let token = " zeeToken " . data ( using: . utf8) !
1287+ let networkSession = MockNetworkSession ( statusCode: 400 ,
1288+ json: [ " msg " : expectedReason] )
1289+ let context = createLogoutTestContext ( autoPushRegistration: true , networkSession: networkSession)
1290+
1291+ context. api. email = AuthTests . email
1292+ context. api. register ( token: token)
1293+
1294+ context. api. logoutUser ( withOnSuccess: { _ in
1295+ XCTFail ( " logout should not succeed " )
1296+ } , onFailure: { reason, data in
1297+ XCTAssertEqual ( reason, expectedReason)
1298+ XCTAssertNotNil ( data)
1299+ logoutFailed. fulfill ( )
1300+ } )
1301+
1302+ wait ( for: [ logoutFailed] , timeout: testExpectationTimeout)
1303+
1304+ assertLogoutCleanupCompleted ( context)
1305+
1306+ guard let request = networkSession. getRequest ( withEndPoint: Const . Path. disableDevice) ,
1307+ let body = TestUtils . getRequestBody ( request: request) else {
1308+ XCTFail ( " Expected disableDevice request " )
1309+ return
1310+ }
1311+
1312+ TestUtils . validateElementPresent ( withName: JsonKey . token, andValue: token. hexString ( ) , inDictionary: body)
1313+ TestUtils . validateElementPresent ( withName: JsonKey . email, andValue: AuthTests . email, inDictionary: body)
1314+ }
1315+
1316+ func testLogoutUserWithHandlersAutoPushOffSucceedsSynchronouslyWithoutNetwork( ) {
1317+ let logoutSucceeded = expectation ( description: " logout success handler called " )
1318+ let networkSession = MockNetworkSession ( statusCode: 200 )
1319+ let context = createLogoutTestContext ( autoPushRegistration: false , networkSession: networkSession)
1320+
1321+ context. api. email = AuthTests . email
1322+
1323+ var logoutReturned = false
1324+ context. api. logoutUser ( withOnSuccess: { data in
1325+ XCTAssertNil ( data)
1326+ XCTAssertFalse ( logoutReturned)
1327+ logoutSucceeded. fulfill ( )
1328+ } , onFailure: { reason, _ in
1329+ XCTFail ( " logout should not fail: \( reason ?? " nil " ) " )
1330+ } )
1331+ logoutReturned = true
1332+
1333+ wait ( for: [ logoutSucceeded] , timeout: testExpectationTimeout)
1334+
1335+ assertLogoutCleanupCompleted ( context)
1336+ XCTAssertTrue ( networkSession. requests. isEmpty)
1337+ }
1338+
1339+ func testLogoutUserWithHandlersAutoPushOnNoTokenFailsButStillClearsLocalState( ) {
1340+ let logoutFailed = expectation ( description: " logout failure handler called " )
1341+ let networkSession = MockNetworkSession ( statusCode: 200 )
1342+ let context = createLogoutTestContext ( autoPushRegistration: true , networkSession: networkSession)
1343+
1344+ context. api. email = AuthTests . email
1345+ // Intentionally do NOT register a token: there is no push token to disable.
1346+
1347+ context. api. logoutUser ( withOnSuccess: { _ in
1348+ XCTFail ( " logout should not report success when there is no token to disable " )
1349+ } , onFailure: { reason, data in
1350+ XCTAssertEqual ( reason, " no token present " )
1351+ XCTAssertNil ( data)
1352+ logoutFailed. fulfill ( )
1353+ } )
1354+
1355+ wait ( for: [ logoutFailed] , timeout: testExpectationTimeout)
1356+
1357+ // Logout is local-only and must still complete even though the triggered
1358+ // disableDevice could not run (no token) and onFailure was reported.
1359+ assertLogoutCleanupCompleted ( context)
1360+ XCTAssertNil ( networkSession. getRequest ( withEndPoint: Const . Path. disableDevice) ,
1361+ " no disableDevice request should be sent when there is no token " )
1362+ }
1363+
1364+ func testLogoutUserWithHandlersNotInitializedFails( ) {
1365+ let logoutFailed = expectation ( description: " logout failure handler called " )
1366+ let networkSession = MockNetworkSession ( statusCode: 200 )
1367+ let context = createLogoutTestContext ( autoPushRegistration: true , networkSession: networkSession)
1368+
1369+ context. api. logoutUser ( withOnSuccess: { _ in
1370+ XCTFail ( " logout should not succeed " )
1371+ } , onFailure: { reason, data in
1372+ XCTAssertEqual ( reason, " Iterable SDK is not initialized " )
1373+ XCTAssertNil ( data)
1374+ logoutFailed. fulfill ( )
1375+ } )
1376+
1377+ wait ( for: [ logoutFailed] , timeout: testExpectationTimeout)
1378+
1379+ XCTAssertTrue ( networkSession. requests. isEmpty)
1380+ XCTAssertEqual ( context. inAppManager. resetCallCount, 0 )
1381+ XCTAssertEqual ( context. embeddedManager. resetCallCount, 0 )
1382+ }
1383+
1384+ func testDisableDeviceWithHandlersNotInitializedFails( ) {
1385+ let previousImplementation = IterableAPI . implementation
1386+ defer { IterableAPI . implementation = previousImplementation }
1387+ IterableAPI . implementation = nil
1388+ let currentUserFailed = expectation ( description: " current user disable failure handler called " )
1389+ let allUsersFailed = expectation ( description: " all users disable failure handler called " )
1390+
1391+ IterableAPI . disableDeviceForCurrentUser ( withOnSuccess: { _ in
1392+ XCTFail ( " disable current user should not succeed " )
1393+ } , onFailure: { reason, data in
1394+ XCTAssertEqual ( reason, " Iterable SDK is not initialized " )
1395+ XCTAssertNil ( data)
1396+ currentUserFailed. fulfill ( )
1397+ } )
1398+
1399+ IterableAPI . disableDeviceForAllUsers ( withOnSuccess: { _ in
1400+ XCTFail ( " disable all users should not succeed " )
1401+ } , onFailure: { reason, data in
1402+ XCTAssertEqual ( reason, " Iterable SDK is not initialized " )
1403+ XCTAssertNil ( data)
1404+ allUsersFailed. fulfill ( )
1405+ } )
1406+
1407+ wait ( for: [ currentUserFailed, allUsersFailed] , timeout: testExpectationTimeout)
1408+ }
1409+
1410+ func testLogoutUserParameterlessOverloadStillLogsOut( ) {
1411+ let networkSession = MockNetworkSession ( statusCode: 200 )
1412+ let context = createLogoutTestContext ( autoPushRegistration: false , networkSession: networkSession)
1413+
1414+ context. api. email = AuthTests . email
1415+
1416+ context. api. logoutUser ( )
1417+
1418+ assertLogoutCleanupCompleted ( context)
1419+ XCTAssertTrue ( networkSession. requests. isEmpty)
1420+ }
1421+
12541422 // MARK: - Private
1423+
1424+ private final class LogoutTrackingInAppManager : EmptyInAppManager {
1425+ private( set) var resetCallCount = 0
1426+
1427+ override func reset( ) -> Pending < Bool , Error > {
1428+ resetCallCount += 1
1429+ return Fulfill < Bool , Error > ( value: true )
1430+ }
1431+ }
1432+
1433+ private final class LogoutTrackingEmbeddedManager : EmptyEmbeddedManager {
1434+ private( set) var resetCallCount = 0
1435+
1436+ override func reset( ) {
1437+ resetCallCount += 1
1438+ }
1439+ }
1440+
1441+ private typealias LogoutTestContext = ( api: InternalIterableAPI ,
1442+ localStorage: MockLocalStorage ,
1443+ inAppManager: LogoutTrackingInAppManager ,
1444+ embeddedManager: LogoutTrackingEmbeddedManager )
1445+
1446+ private func createLogoutTestContext( autoPushRegistration: Bool ,
1447+ networkSession: MockNetworkSession ) -> LogoutTestContext {
1448+ let config = IterableConfig ( )
1449+ config. autoPushRegistration = autoPushRegistration
1450+ config. pushIntegrationName = " my-push-integration "
1451+
1452+ let localStorage = MockLocalStorage ( )
1453+ let api = InternalIterableAPI . initializeForTesting ( config: config,
1454+ networkSession: networkSession,
1455+ notificationStateProvider: MockNotificationStateProvider ( enabled: true ) ,
1456+ localStorage: localStorage)
1457+ let inAppManager = LogoutTrackingInAppManager ( )
1458+ let embeddedManager = LogoutTrackingEmbeddedManager ( )
1459+ api. inAppManager = inAppManager
1460+ api. embeddedManager = embeddedManager
1461+
1462+ return ( api: api,
1463+ localStorage: localStorage,
1464+ inAppManager: inAppManager,
1465+ embeddedManager: embeddedManager)
1466+ }
1467+
1468+ private func assertLogoutCleanupCompleted( _ context: LogoutTestContext ,
1469+ file: StaticString = #file,
1470+ line: UInt = #line) {
1471+ XCTAssertNil ( context. api. email, file: file, line: line)
1472+ XCTAssertNil ( context. api. userId, file: file, line: line)
1473+ XCTAssertNil ( context. localStorage. email, file: file, line: line)
1474+ XCTAssertNil ( context. localStorage. userId, file: file, line: line)
1475+ XCTAssertEqual ( context. inAppManager. resetCallCount, 1 , file: file, line: line)
1476+ XCTAssertEqual ( context. embeddedManager. resetCallCount, 1 , file: file, line: line)
1477+ }
12551478
12561479 class DefaultAuthDelegate : IterableAuthDelegate {
12571480 var authTokenGenerator : ( ( ) -> String ? )
0 commit comments