@@ -1383,10 +1383,108 @@ struct InternalDefaultLiveMapTests {
13831383
13841384 /// Tests for `MAP_CLEAR` operations, covering RTLM24 specification points
13851385 struct MapClearOperationTests {
1386+ // MARK: - RTLM24c Tests (clearTimeserial check)
1387+
1388+ // @spec RTLM24c
1389+ @Test ( arguments: [
1390+ // serial < clearTimeserial: discard
1391+ ( operationSerial: " ts4 " as String ? , clearTimeserial: " ts5 " , expectedApplied: false ) ,
1392+ // serial == clearTimeserial: discard
1393+ ( operationSerial: " ts5 " as String ? , clearTimeserial: " ts5 " , expectedApplied: false ) ,
1394+ // serial > clearTimeserial: allow
1395+ ( operationSerial: " ts6 " as String ? , clearTimeserial: " ts5 " , expectedApplied: true ) ,
1396+ // serial is nil: discard
1397+ ( operationSerial: nil as String ? , clearTimeserial: " ts5 " , expectedApplied: false ) ,
1398+ ] as [ ( operationSerial: String ? , clearTimeserial: String , expectedApplied: Bool ) ] )
1399+ func checksClearTimeserialBeforeApplying( operationSerial: String ? , clearTimeserial: String , expectedApplied: Bool ) throws {
1400+ let logger = TestLogger ( )
1401+ let internalQueue = TestFactories . createInternalQueue ( )
1402+ let delegate = MockLiveMapObjectsPoolDelegate ( internalQueue: internalQueue)
1403+ let coreSDK = MockCoreSDK ( channelState: . attaching, internalQueue: internalQueue)
1404+
1405+ // Given: a map with an existing entry and the specified clearTimeserial
1406+ let map = InternalDefaultLiveMap (
1407+ testsOnly_data: [ " key1 " : TestFactories . internalMapEntry ( timeserial: " ts1 " , data: ObjectData ( string: " existing " ) ) ] ,
1408+ objectID: " arbitrary " ,
1409+ logger: logger,
1410+ internalQueue: internalQueue,
1411+ userCallbackQueue: . main,
1412+ clock: MockSimpleClock ( ) ,
1413+ )
1414+
1415+ var pool = ObjectsPool ( logger: logger, internalQueue: internalQueue, userCallbackQueue: . main, clock: MockSimpleClock ( ) )
1416+ internalQueue. ably_syncNoDeadlock {
1417+ _ = map. nosync_replaceData (
1418+ using: TestFactories . objectState (
1419+ map: TestFactories . objectsMap (
1420+ entries: [ " key1 " : TestFactories . stringMapEntry ( key: " key1 " , value: " existing " ) . entry] ,
1421+ clearTimeserial: clearTimeserial,
1422+ ) ,
1423+ ) ,
1424+ objectMessageSerialTimestamp: nil ,
1425+ objectsPool: & pool,
1426+ )
1427+ }
1428+
1429+ // When: applying a MAP_CLEAR operation with the specified serial
1430+ let update = map. testsOnly_applyMapClearOperation ( serial: operationSerial)
1431+
1432+ // Then: the operation is applied or discarded as expected
1433+ #expect( update. isNoop == !expectedApplied)
1434+ if expectedApplied {
1435+ #expect( try map. get ( key: " key1 " , coreSDK: coreSDK, delegate: delegate) == nil )
1436+ } else {
1437+ #expect( try map. get ( key: " key1 " , coreSDK: coreSDK, delegate: delegate) ? . stringValue == " existing " )
1438+ }
1439+ }
1440+
1441+ // MARK: - RTLM24 Tests (MAP_CLEAR operation application)
1442+
13861443 // @spec RTLM24
1444+ // @spec RTLM24d
1445+ // @spec RTLM24e
1446+ // @spec RTLM24e1
1447+ // @spec RTLM24e1a
1448+ // @spec RTLM24e1b
1449+ // @spec RTLM24f
13871450 @Test
1388- func appliesMapClearOperation( ) {
1389- Issue . record ( " TODO: Add tests for MAP_CLEAR operation application " )
1451+ func appliesMapClearOperation( ) throws {
1452+ let logger = TestLogger ( )
1453+ let internalQueue = TestFactories . createInternalQueue ( )
1454+ let delegate = MockLiveMapObjectsPoolDelegate ( internalQueue: internalQueue)
1455+ let coreSDK = MockCoreSDK ( channelState: . attaching, internalQueue: internalQueue)
1456+
1457+ // Given: a map with multiple entries at different timeserials, including one with nil timeserial
1458+ let map = InternalDefaultLiveMap (
1459+ testsOnly_data: [
1460+ " olderThanClear " : TestFactories . internalMapEntry ( timeserial: " ts1 " , data: ObjectData ( string: " value1 " ) ) ,
1461+ " equalToClear " : TestFactories . internalMapEntry ( timeserial: " ts3 " , data: ObjectData ( string: " value2 " ) ) ,
1462+ " newerThanClear " : TestFactories . internalMapEntry ( timeserial: " ts5 " , data: ObjectData ( string: " value3 " ) ) ,
1463+ " nilTimeserial " : TestFactories . internalMapEntry ( timeserial: nil , data: ObjectData ( string: " value4 " ) ) ,
1464+ ] ,
1465+ objectID: " arbitrary " ,
1466+ logger: logger,
1467+ internalQueue: internalQueue,
1468+ userCallbackQueue: . main,
1469+ clock: MockSimpleClock ( ) ,
1470+ )
1471+
1472+ // When: applying a MAP_CLEAR operation with serial "ts3"
1473+ let update = map. testsOnly_applyMapClearOperation ( serial: " ts3 " )
1474+
1475+ // Then: entries with timeserial <= "ts3" or nil are removed from internal data, others remain
1476+ #expect( Set ( map. testsOnly_data. keys) == [ " newerThanClear " ] )
1477+
1478+ // RTLM24f: update contains exactly the removed keys
1479+ let mapUpdate = try #require( update. update)
1480+ #expect( mapUpdate. update == [
1481+ " olderThanClear " : . removed,
1482+ " equalToClear " : . removed,
1483+ " nilTimeserial " : . removed,
1484+ ] )
1485+
1486+ // RTLM24d: clearTimeserial should be set
1487+ #expect( map. testsOnly_clearTimeserial == " ts3 " )
13901488 }
13911489 }
13921490
@@ -1607,9 +1705,60 @@ struct InternalDefaultLiveMapTests {
16071705 // @spec RTLM15d8
16081706 // @spec RTLM15d8a
16091707 // @spec RTLM15d8b
1708+ @available ( iOS 17 . 0 . 0 , tvOS 17 . 0 . 0 , * )
16101709 @Test
1611- func appliesMapClearOperation( ) {
1612- Issue . record ( " TODO: Add test for MAP_CLEAR operation routing (copy structure from the above existing tests for other operations; should also test that siteTimeserials is updated per RTLM15c) " )
1710+ func appliesMapClearOperation( ) async throws {
1711+ let logger = TestLogger ( )
1712+ let internalQueue = TestFactories . createInternalQueue ( )
1713+ let delegate = MockLiveMapObjectsPoolDelegate ( internalQueue: internalQueue)
1714+ let coreSDK = MockCoreSDK ( channelState: . attaching, internalQueue: internalQueue)
1715+ let map = InternalDefaultLiveMap . createZeroValued ( objectID: " arbitrary " , logger: logger, internalQueue: internalQueue, userCallbackQueue: . main, clock: MockSimpleClock ( ) )
1716+
1717+ let subscriber = Subscriber < DefaultLiveMapUpdate , SubscribeResponse > ( callbackQueue: . main)
1718+ try map. subscribe ( listener: subscriber. createListener ( ) , coreSDK: coreSDK)
1719+
1720+ // Set initial data
1721+ var pool = ObjectsPool ( logger: logger, internalQueue: internalQueue, userCallbackQueue: . main, clock: MockSimpleClock ( ) )
1722+ let ( key1, entry1) = TestFactories . stringMapEntry ( key: " key1 " , value: " existing " , timeserial: nil )
1723+ internalQueue. ably_syncNoDeadlock {
1724+ _ = map. nosync_replaceData (
1725+ using: TestFactories . mapObjectState (
1726+ siteTimeserials: [ : ] ,
1727+ entries: [ key1: entry1] ,
1728+ ) ,
1729+ objectMessageSerialTimestamp: nil ,
1730+ objectsPool: & pool,
1731+ )
1732+ }
1733+ #expect( try map. get ( key: " key1 " , coreSDK: coreSDK, delegate: delegate) ? . stringValue == " existing " )
1734+
1735+ let operation = TestFactories . objectOperation (
1736+ action: . known( . mapClear) ,
1737+ mapClear: WireMapClear ( ) ,
1738+ )
1739+
1740+ // Apply MAP_CLEAR operation
1741+ let applied = internalQueue. ably_syncNoDeadlock {
1742+ map. nosync_apply (
1743+ operation,
1744+ source: . channel,
1745+ objectMessageSerial: " ts1 " ,
1746+ objectMessageSiteCode: " site1 " ,
1747+ objectMessageSerialTimestamp: nil ,
1748+ objectsPool: & pool,
1749+ )
1750+ }
1751+ #expect( applied)
1752+
1753+ // Verify the operation was applied (the full logic of RTLM24 is tested elsewhere; we just check for some of its side effects here)
1754+ #expect( try map. get ( key: " key1 " , coreSDK: coreSDK, delegate: delegate) == nil )
1755+ #expect( map. testsOnly_clearTimeserial == " ts1 " )
1756+ // Verify RTLM15c side-effect: site timeserial was updated
1757+ #expect( map. testsOnly_siteTimeserials == [ " site1 " : " ts1 " ] )
1758+
1759+ // Verify update was emitted per RTLM15d8a
1760+ let subscriberInvocations = await subscriber. getInvocations ( )
1761+ #expect( subscriberInvocations. map ( \. 0 ) == [ . init( update: [ " key1 " : . removed] ) ] )
16131762 }
16141763
16151764 // @specOneOf(5/5) RTLM15c - Tests that siteTimeserials is NOT updated when source is LOCAL
0 commit comments