Skip to content

Commit 3fa3be5

Browse files
Merge pull request #55 from ably/ECO-5465-enable-disabled-integration-tests
[ECO-5465] Enable disabled integration tests
2 parents 1474140 + c4bab5f commit 3fa3be5

16 files changed

+447
-210
lines changed

Sources/AblyLiveObjects/Internal/CoreSDK.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,30 @@ internal final class DefaultCoreSDK: CoreSDK {
1616
private let channel: AblyPlugin.RealtimeChannel
1717
private let client: AblyPlugin.RealtimeClient
1818
private let pluginAPI: PluginAPIProtocol
19+
private let logger: AblyPlugin.Logger
1920

2021
internal init(
2122
channel: AblyPlugin.RealtimeChannel,
2223
client: AblyPlugin.RealtimeClient,
23-
pluginAPI: PluginAPIProtocol
24+
pluginAPI: PluginAPIProtocol,
25+
logger: AblyPlugin.Logger
2426
) {
2527
self.channel = channel
2628
self.client = client
2729
self.pluginAPI = pluginAPI
30+
self.logger = logger
2831
}
2932

3033
// MARK: - CoreSDK conformance
3134

3235
internal func publish(objectMessages: [OutboundObjectMessage]) async throws(InternalError) {
36+
logger.log("publish(objectMessages: \(LoggingUtilities.formatObjectMessagesForLogging(objectMessages)))", level: .debug)
37+
3338
// TODO: Implement the full spec of RTO15 (https://github.com/ably/ably-cocoa-liveobjects-plugin/issues/47)
3439
try await DefaultInternalPlugin.sendObject(
3540
objectMessages: objectMessages,
3641
channel: channel,
42+
client: client,
3743
pluginAPI: pluginAPI,
3844
)
3945
}

Sources/AblyLiveObjects/Internal/DefaultInternalPlugin.swift

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -128,19 +128,24 @@ internal final class DefaultInternalPlugin: NSObject, AblyPlugin.LiveObjectsInte
128128
internal static func sendObject(
129129
objectMessages: [OutboundObjectMessage],
130130
channel: AblyPlugin.RealtimeChannel,
131+
client: AblyPlugin.RealtimeClient,
131132
pluginAPI: PluginAPIProtocol,
132133
) async throws(InternalError) {
133134
let objectMessageBoxes: [ObjectMessageBox<OutboundObjectMessage>] = objectMessages.map { .init(objectMessage: $0) }
134135

135136
try await withCheckedContinuation { (continuation: CheckedContinuation<Result<Void, InternalError>, _>) in
136-
pluginAPI.sendObject(
137-
withObjectMessages: objectMessageBoxes,
138-
channel: channel,
139-
) { error in
140-
if let error {
141-
continuation.resume(returning: .failure(error.toInternalError()))
142-
} else {
143-
continuation.resume(returning: .success(()))
137+
let internalQueue = pluginAPI.internalQueue(for: client)
138+
139+
internalQueue.async {
140+
pluginAPI.sendObject(
141+
withObjectMessages: objectMessageBoxes,
142+
channel: channel,
143+
) { error in
144+
if let error {
145+
continuation.resume(returning: .failure(error.toInternalError()))
146+
} else {
147+
continuation.resume(returning: .success(()))
148+
}
144149
}
145150
}
146151
}.get()

Sources/AblyLiveObjects/Internal/InternalDefaultLiveCounter.swift

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,8 @@ internal final class InternalDefaultLiveCounter: Sendable {
8080
// MARK: - Internal methods that back LiveCounter conformance
8181

8282
internal func value(coreSDK: CoreSDK) throws(ARTErrorInfo) -> Double {
83-
// RTLC5b: If the channel is in the DETACHED or FAILED state, the library should indicate an error with code 90001
84-
try coreSDK.validateChannelState(notIn: [.detached, .failed], operationDescription: "LiveCounter.value")
85-
86-
return mutex.withLock {
87-
// RTLC5c
88-
mutableState.data
83+
try mutex.ablyLiveObjects_withLockWithTypedThrow { () throws(ARTErrorInfo) in
84+
try mutableState.value(coreSDK: coreSDK)
8985
}
9086
}
9187

@@ -419,5 +415,13 @@ internal final class InternalDefaultLiveCounter: Sendable {
419415
// RTLC4
420416
data = 0
421417
}
418+
419+
internal func value(coreSDK: CoreSDK) throws(ARTErrorInfo) -> Double {
420+
// RTLC5b: If the channel is in the DETACHED or FAILED state, the library should indicate an error with code 90001
421+
try coreSDK.validateChannelState(notIn: [.detached, .failed], operationDescription: "LiveCounter.value")
422+
423+
// RTLC5c
424+
return data
425+
}
422426
}
423427
}

Sources/AblyLiveObjects/Internal/InternalDefaultLiveMap.swift

Lines changed: 117 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -110,56 +110,20 @@ internal final class InternalDefaultLiveMap: Sendable {
110110

111111
/// Returns the value associated with a given key, following RTLM5d specification.
112112
internal func get(key: String, coreSDK: CoreSDK, delegate: LiveMapObjectPoolDelegate) throws(ARTErrorInfo) -> InternalLiveMapValue? {
113-
// RTLM5c: If the channel is in the DETACHED or FAILED state, the library should indicate an error with code 90001
114-
try coreSDK.validateChannelState(notIn: [.detached, .failed], operationDescription: "LiveMap.get")
115-
116-
// RTLM5e - Return nil if self is tombstone
117-
if isTombstone {
118-
return nil
119-
}
120-
121-
let entry = mutex.withLock {
122-
mutableState.data[key]
123-
}
124-
125-
// RTLM5d1: If no ObjectsMapEntry exists at the key, return undefined/null
126-
guard let entry else {
127-
return nil
113+
try mutex.ablyLiveObjects_withLockWithTypedThrow { () throws(ARTErrorInfo) in
114+
try mutableState.get(key: key, coreSDK: coreSDK, delegate: delegate)
128115
}
129-
130-
// RTLM5d2: If a ObjectsMapEntry exists at the key, convert it using the shared logic
131-
return convertEntryToLiveMapValue(entry, delegate: delegate)
132116
}
133117

134118
internal func size(coreSDK: CoreSDK, delegate: LiveMapObjectPoolDelegate) throws(ARTErrorInfo) -> Int {
135-
// RTLM10c: If the channel is in the DETACHED or FAILED state, the library should throw an ErrorInfo error with statusCode 400 and code 90001
136-
try coreSDK.validateChannelState(notIn: [.detached, .failed], operationDescription: "LiveMap.size")
137-
138-
return mutex.withLock {
139-
// RTLM10d: Returns the number of non-tombstoned entries (per RTLM14) in the internal data map
140-
mutableState.data.values.count { entry in
141-
!Self.isEntryTombstoned(entry, delegate: delegate)
142-
}
119+
try mutex.ablyLiveObjects_withLockWithTypedThrow { () throws(ARTErrorInfo) in
120+
try mutableState.size(coreSDK: coreSDK, delegate: delegate)
143121
}
144122
}
145123

146124
internal func entries(coreSDK: CoreSDK, delegate: LiveMapObjectPoolDelegate) throws(ARTErrorInfo) -> [(key: String, value: InternalLiveMapValue)] {
147-
// RTLM11c: If the channel is in the DETACHED or FAILED state, the library should throw an ErrorInfo error with statusCode 400 and code 90001
148-
try coreSDK.validateChannelState(notIn: [.detached, .failed], operationDescription: "LiveMap.entries")
149-
150-
return mutex.withLock {
151-
// RTLM11d: Returns key-value pairs from the internal data map
152-
// RTLM11d1: Pairs with tombstoned entries (per RTLM14) are not returned
153-
var result: [(key: String, value: InternalLiveMapValue)] = []
154-
155-
for (key, entry) in mutableState.data where !Self.isEntryTombstoned(entry, delegate: delegate) {
156-
// Convert entry to LiveMapValue using the same logic as get(key:)
157-
if let value = convertEntryToLiveMapValue(entry, delegate: delegate) {
158-
result.append((key: key, value: value))
159-
}
160-
}
161-
162-
return result
125+
try mutex.ablyLiveObjects_withLockWithTypedThrow { () throws(ARTErrorInfo) in
126+
try mutableState.entries(coreSDK: coreSDK, delegate: delegate)
163127
}
164128
}
165129

@@ -655,7 +619,7 @@ internal final class InternalDefaultLiveMap: Sendable {
655619
internal mutating func applyMapSetOperation(
656620
key: String,
657621
operationTimeserial: String?,
658-
operationData: ObjectData,
622+
operationData: ObjectData?,
659623
objectsPool: inout ObjectsPool,
660624
logger: AblyPlugin.Logger,
661625
userCallbackQueue: DispatchQueue,
@@ -684,7 +648,7 @@ internal final class InternalDefaultLiveMap: Sendable {
684648
}
685649

686650
// RTLM7c: If the operation has a non-empty ObjectData.objectId attribute
687-
if let objectId = operationData.objectId, !objectId.isEmpty {
651+
if let objectId = operationData?.objectId, !objectId.isEmpty {
688652
// RTLM7c1: Create a zero-value LiveObject in the internal ObjectsPool per RTO6
689653
_ = objectsPool.createZeroValueObject(forObjectID: objectId, logger: logger, userCallbackQueue: userCallbackQueue, clock: clock)
690654
}
@@ -721,7 +685,7 @@ internal final class InternalDefaultLiveMap: Sendable {
721685
// RTLM8a2c: Set ObjectsMapEntry.tombstone to true (equivalent to next point)
722686
// RTLM8a2d: Set ObjectsMapEntry.tombstonedAt per RTLM8a2d
723687
var updatedEntry = existingEntry
724-
updatedEntry.data = ObjectData()
688+
updatedEntry.data = nil
725689
updatedEntry.timeserial = operationTimeserial
726690
updatedEntry.tombstonedAt = tombstonedAt
727691
data[key] = updatedEntry
@@ -730,7 +694,7 @@ internal final class InternalDefaultLiveMap: Sendable {
730694
// RTLM8b1: Create a new entry in data for the specified key, with ObjectsMapEntry.data set to undefined/null and the operation's serial
731695
// RTLM8b2: Set ObjectsMapEntry.tombstone for the new entry to true
732696
// RTLM8b3: Set ObjectsMapEntry.tombstonedAt per RTLM8f
733-
data[key] = InternalObjectsMapEntry(tombstonedAt: tombstonedAt, timeserial: operationTimeserial, data: ObjectData())
697+
data[key] = InternalObjectsMapEntry(tombstonedAt: tombstonedAt, timeserial: operationTimeserial, data: nil)
734698
}
735699

736700
return .update(DefaultLiveMapUpdate(update: [key: .removed]))
@@ -843,90 +807,137 @@ internal final class InternalDefaultLiveMap: Sendable {
843807
return !shouldRelease
844808
}
845809
}
846-
}
847810

848-
// MARK: - Helper Methods
811+
/// Returns the value associated with a given key, following RTLM5d specification.
812+
internal func get(key: String, coreSDK: CoreSDK, delegate: LiveMapObjectPoolDelegate) throws(ARTErrorInfo) -> InternalLiveMapValue? {
813+
// RTLM5c: If the channel is in the DETACHED or FAILED state, the library should indicate an error with code 90001
814+
try coreSDK.validateChannelState(notIn: [.detached, .failed], operationDescription: "LiveMap.get")
849815

850-
/// Returns whether a map entry should be considered tombstoned, per the check described in RTLM14.
851-
private static func isEntryTombstoned(_ entry: InternalObjectsMapEntry, delegate: LiveMapObjectPoolDelegate) -> Bool {
852-
// RTLM14a
853-
if entry.tombstone {
854-
return true
855-
}
816+
// RTLM5e - Return nil if self is tombstone
817+
if liveObjectMutableState.isTombstone {
818+
return nil
819+
}
856820

857-
// RTLM14c
858-
if let objectId = entry.data.objectId {
859-
if let poolEntry = delegate.getObjectFromPool(id: objectId), poolEntry.isTombstone {
860-
return true
821+
// RTLM5d1: If no ObjectsMapEntry exists at the key, return undefined/null
822+
guard let entry = data[key] else {
823+
return nil
861824
}
825+
826+
// RTLM5d2: If a ObjectsMapEntry exists at the key, convert it using the shared logic
827+
return convertEntryToLiveMapValue(entry, delegate: delegate)
862828
}
863829

864-
// RTLM14b
865-
return false
866-
}
830+
internal func size(coreSDK: CoreSDK, delegate: LiveMapObjectPoolDelegate) throws(ARTErrorInfo) -> Int {
831+
// RTLM10c: If the channel is in the DETACHED or FAILED state, the library should throw an ErrorInfo error with statusCode 400 and code 90001
832+
try coreSDK.validateChannelState(notIn: [.detached, .failed], operationDescription: "LiveMap.size")
867833

868-
/// Converts an InternalObjectsMapEntry to LiveMapValue using the same logic as get(key:)
869-
/// This is used by entries to ensure consistent value conversion
870-
private func convertEntryToLiveMapValue(_ entry: InternalObjectsMapEntry, delegate: LiveMapObjectPoolDelegate) -> InternalLiveMapValue? {
871-
// RTLM5d2a: If ObjectsMapEntry.tombstone is true, return undefined/null
872-
if entry.tombstone == true {
873-
return nil
834+
// RTLM10d: Returns the number of non-tombstoned entries (per RTLM14) in the internal data map
835+
return data.values.count { entry in
836+
!Self.isEntryTombstoned(entry, delegate: delegate)
837+
}
874838
}
875839

876-
// Handle primitive values in the order specified by RTLM5d2b through RTLM5d2e
840+
internal func entries(coreSDK: CoreSDK, delegate: LiveMapObjectPoolDelegate) throws(ARTErrorInfo) -> [(key: String, value: InternalLiveMapValue)] {
841+
// RTLM11c: If the channel is in the DETACHED or FAILED state, the library should throw an ErrorInfo error with statusCode 400 and code 90001
842+
try coreSDK.validateChannelState(notIn: [.detached, .failed], operationDescription: "LiveMap.entries")
877843

878-
// RTLM5d2b: If ObjectsMapEntry.data.boolean exists, return it
879-
if let boolean = entry.data.boolean {
880-
return .primitive(.bool(boolean))
881-
}
844+
// RTLM11d: Returns key-value pairs from the internal data map
845+
// RTLM11d1: Pairs with tombstoned entries (per RTLM14) are not returned
846+
var result: [(key: String, value: InternalLiveMapValue)] = []
882847

883-
// RTLM5d2c: If ObjectsMapEntry.data.bytes exists, return it
884-
if let bytes = entry.data.bytes {
885-
return .primitive(.data(bytes))
886-
}
848+
for (key, entry) in data where !Self.isEntryTombstoned(entry, delegate: delegate) {
849+
// Convert entry to LiveMapValue using the same logic as get(key:)
850+
if let value = convertEntryToLiveMapValue(entry, delegate: delegate) {
851+
result.append((key: key, value: value))
852+
}
853+
}
887854

888-
// RTLM5d2d: If ObjectsMapEntry.data.number exists, return it
889-
if let number = entry.data.number {
890-
return .primitive(.number(number.doubleValue))
855+
return result
891856
}
892857

893-
// RTLM5d2e: If ObjectsMapEntry.data.string exists, return it
894-
if let string = entry.data.string {
895-
return .primitive(.string(string))
896-
}
858+
// MARK: - Helper Methods
897859

898-
// TODO: Needs specification (see https://github.com/ably/ably-cocoa-liveobjects-plugin/issues/46)
899-
if let json = entry.data.json {
900-
switch json {
901-
case let .array(array):
902-
return .primitive(.jsonArray(array))
903-
case let .object(object):
904-
return .primitive(.jsonObject(object))
860+
/// Returns whether a map entry should be considered tombstoned, per the check described in RTLM14.
861+
private static func isEntryTombstoned(_ entry: InternalObjectsMapEntry, delegate: LiveMapObjectPoolDelegate) -> Bool {
862+
// RTLM14a
863+
if entry.tombstone {
864+
return true
905865
}
866+
867+
// RTLM14c
868+
if let objectId = entry.data?.objectId {
869+
if let poolEntry = delegate.getObjectFromPool(id: objectId), poolEntry.isTombstone {
870+
return true
871+
}
872+
}
873+
874+
// RTLM14b
875+
return false
906876
}
907877

908-
// RTLM5d2f: If ObjectsMapEntry.data.objectId exists, get the object stored at that objectId from the internal ObjectsPool
909-
if let objectId = entry.data.objectId {
910-
// RTLM5d2f1: If an object with id objectId does not exist, return undefined/null
911-
guard let poolEntry = delegate.getObjectFromPool(id: objectId) else {
878+
/// Converts an InternalObjectsMapEntry to LiveMapValue using the same logic as get(key:)
879+
/// This is used by entries to ensure consistent value conversion
880+
private func convertEntryToLiveMapValue(_ entry: InternalObjectsMapEntry, delegate: LiveMapObjectPoolDelegate) -> InternalLiveMapValue? {
881+
// RTLM5d2a: If ObjectsMapEntry.tombstone is true, return undefined/null
882+
if entry.tombstone == true {
912883
return nil
913884
}
914885

915-
// RTLM5d2f3: If referenced object is tombstoned, return nil
916-
if poolEntry.isTombstone {
917-
return nil
886+
// Handle primitive values in the order specified by RTLM5d2b through RTLM5d2e
887+
888+
// RTLM5d2b: If ObjectsMapEntry.data.boolean exists, return it
889+
if let boolean = entry.data?.boolean {
890+
return .primitive(.bool(boolean))
918891
}
919892

920-
// RTLM5d2f2: Return referenced object
921-
switch poolEntry {
922-
case let .map(map):
923-
return .liveMap(map)
924-
case let .counter(counter):
925-
return .liveCounter(counter)
893+
// RTLM5d2c: If ObjectsMapEntry.data.bytes exists, return it
894+
if let bytes = entry.data?.bytes {
895+
return .primitive(.data(bytes))
896+
}
897+
898+
// RTLM5d2d: If ObjectsMapEntry.data.number exists, return it
899+
if let number = entry.data?.number {
900+
return .primitive(.number(number.doubleValue))
901+
}
902+
903+
// RTLM5d2e: If ObjectsMapEntry.data.string exists, return it
904+
if let string = entry.data?.string {
905+
return .primitive(.string(string))
906+
}
907+
908+
// TODO: Needs specification (see https://github.com/ably/ably-cocoa-liveobjects-plugin/issues/46)
909+
if let json = entry.data?.json {
910+
switch json {
911+
case let .array(array):
912+
return .primitive(.jsonArray(array))
913+
case let .object(object):
914+
return .primitive(.jsonObject(object))
915+
}
926916
}
927-
}
928917

929-
// RTLM5d2g: Otherwise, return undefined/null
930-
return nil
918+
// RTLM5d2f: If ObjectsMapEntry.data.objectId exists, get the object stored at that objectId from the internal ObjectsPool
919+
if let objectId = entry.data?.objectId {
920+
// RTLM5d2f1: If an object with id objectId does not exist, return undefined/null
921+
guard let poolEntry = delegate.getObjectFromPool(id: objectId) else {
922+
return nil
923+
}
924+
925+
// RTLM5d2f3: If referenced object is tombstoned, return nil
926+
if poolEntry.isTombstone {
927+
return nil
928+
}
929+
930+
// RTLM5d2f2: Return referenced object
931+
switch poolEntry {
932+
case let .map(map):
933+
return .liveMap(map)
934+
case let .counter(counter):
935+
return .liveCounter(counter)
936+
}
937+
}
938+
939+
// RTLM5d2g: Otherwise, return undefined/null
940+
return nil
941+
}
931942
}
932943
}

0 commit comments

Comments
 (0)