@@ -1383,10 +1383,106 @@ 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+
1455+ // Given: a map with multiple entries at different timeserials, including one with nil timeserial
1456+ let map = InternalDefaultLiveMap (
1457+ testsOnly_data: [
1458+ " olderThanClear " : TestFactories . internalMapEntry ( timeserial: " ts1 " , data: ObjectData ( string: " value1 " ) ) ,
1459+ " equalToClear " : TestFactories . internalMapEntry ( timeserial: " ts3 " , data: ObjectData ( string: " value2 " ) ) ,
1460+ " newerThanClear " : TestFactories . internalMapEntry ( timeserial: " ts5 " , data: ObjectData ( string: " value3 " ) ) ,
1461+ " nilTimeserial " : TestFactories . internalMapEntry ( timeserial: nil , data: ObjectData ( string: " value4 " ) ) ,
1462+ ] ,
1463+ objectID: " arbitrary " ,
1464+ logger: logger,
1465+ internalQueue: internalQueue,
1466+ userCallbackQueue: . main,
1467+ clock: MockSimpleClock ( ) ,
1468+ )
1469+
1470+ // When: applying a MAP_CLEAR operation with serial "ts3"
1471+ let update = map. testsOnly_applyMapClearOperation ( serial: " ts3 " )
1472+
1473+ // Then: entries with timeserial <= "ts3" or nil are removed from internal data, others remain
1474+ #expect( Set ( map. testsOnly_data. keys) == [ " newerThanClear " ] )
1475+
1476+ // RTLM24f: update contains exactly the removed keys
1477+ let mapUpdate = try #require( update. update)
1478+ #expect( mapUpdate. update == [
1479+ " olderThanClear " : . removed,
1480+ " equalToClear " : . removed,
1481+ " nilTimeserial " : . removed,
1482+ ] )
1483+
1484+ // RTLM24d: clearTimeserial should be set
1485+ #expect( map. testsOnly_clearTimeserial == " ts3 " )
13901486 }
13911487 }
13921488
@@ -1607,9 +1703,60 @@ struct InternalDefaultLiveMapTests {
16071703 // @spec RTLM15d8
16081704 // @spec RTLM15d8a
16091705 // @spec RTLM15d8b
1706+ @available ( iOS 17 . 0 . 0 , tvOS 17 . 0 . 0 , * )
16101707 @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) " )
1708+ func appliesMapClearOperation( ) async throws {
1709+ let logger = TestLogger ( )
1710+ let internalQueue = TestFactories . createInternalQueue ( )
1711+ let delegate = MockLiveMapObjectsPoolDelegate ( internalQueue: internalQueue)
1712+ let coreSDK = MockCoreSDK ( channelState: . attaching, internalQueue: internalQueue)
1713+ let map = InternalDefaultLiveMap . createZeroValued ( objectID: " arbitrary " , logger: logger, internalQueue: internalQueue, userCallbackQueue: . main, clock: MockSimpleClock ( ) )
1714+
1715+ let subscriber = Subscriber < DefaultLiveMapUpdate , SubscribeResponse > ( callbackQueue: . main)
1716+ try map. subscribe ( listener: subscriber. createListener ( ) , coreSDK: coreSDK)
1717+
1718+ // Set initial data
1719+ var pool = ObjectsPool ( logger: logger, internalQueue: internalQueue, userCallbackQueue: . main, clock: MockSimpleClock ( ) )
1720+ let ( key1, entry1) = TestFactories . stringMapEntry ( key: " key1 " , value: " existing " , timeserial: nil )
1721+ internalQueue. ably_syncNoDeadlock {
1722+ _ = map. nosync_replaceData (
1723+ using: TestFactories . mapObjectState (
1724+ siteTimeserials: [ : ] ,
1725+ entries: [ key1: entry1] ,
1726+ ) ,
1727+ objectMessageSerialTimestamp: nil ,
1728+ objectsPool: & pool,
1729+ )
1730+ }
1731+ #expect( try map. get ( key: " key1 " , coreSDK: coreSDK, delegate: delegate) ? . stringValue == " existing " )
1732+
1733+ let operation = TestFactories . objectOperation (
1734+ action: . known( . mapClear) ,
1735+ mapClear: WireMapClear ( ) ,
1736+ )
1737+
1738+ // Apply MAP_CLEAR operation
1739+ let applied = internalQueue. ably_syncNoDeadlock {
1740+ map. nosync_apply (
1741+ operation,
1742+ source: . channel,
1743+ objectMessageSerial: " ts1 " ,
1744+ objectMessageSiteCode: " site1 " ,
1745+ objectMessageSerialTimestamp: nil ,
1746+ objectsPool: & pool,
1747+ )
1748+ }
1749+ #expect( applied)
1750+
1751+ // Verify the operation was applied (the full logic of RTLM24 is tested elsewhere; we just check for some of its side effects here)
1752+ #expect( try map. get ( key: " key1 " , coreSDK: coreSDK, delegate: delegate) == nil )
1753+ #expect( map. testsOnly_clearTimeserial == " ts1 " )
1754+ // Verify RTLM15c side-effect: site timeserial was updated
1755+ #expect( map. testsOnly_siteTimeserials == [ " site1 " : " ts1 " ] )
1756+
1757+ // Verify update was emitted per RTLM15d8a
1758+ let subscriberInvocations = await subscriber. getInvocations ( )
1759+ #expect( subscriberInvocations. map ( \. 0 ) == [ . init( update: [ " key1 " : . removed] ) ] )
16131760 }
16141761
16151762 // @specOneOf(5/5) RTLM15c - Tests that siteTimeserials is NOT updated when source is LOCAL
0 commit comments