diff --git a/Source/ARTPresenceMap.h b/Source/ARTPresenceMap.h index 843873bf1..d51571e47 100644 --- a/Source/ARTPresenceMap.h +++ b/Source/ARTPresenceMap.h @@ -20,7 +20,8 @@ ART_ASSUME_NONNULL_BEGIN /// The key is the clientId and the value is the latest relevant ARTPresenceMessage for that clientId. @property (readonly, atomic, getter=getMembers) __GENERIC(NSDictionary, NSString *, ARTPresenceMessage *) *members; -@property (readwrite, nonatomic, assign) int64_t syncSerial; +@property (readwrite, nonatomic, assign) int64_t syncMsgSerial; +@property (readwrite, nonatomic, nullable) NSString *syncChannelSerial; @property (readonly, nonatomic, assign) BOOL syncComplete; @property (readonly, nonatomic, getter=getSyncInProgress) BOOL syncInProgress; diff --git a/Source/ARTRealtime.m b/Source/ARTRealtime.m index a7791298b..f6e552133 100644 --- a/Source/ARTRealtime.m +++ b/Source/ARTRealtime.m @@ -266,7 +266,7 @@ - (void)transition:(ARTRealtimeConnectionState)state withErrorInfo:(ARTErrorInfo } // For every Channel for (ARTRealtimeChannel* channel in self.channels) { - if (channel.state == ARTRealtimeChannelInitialized || channel.state == ARTRealtimeChannelAttaching || channel.state == ARTRealtimeChannelAttached) { + if (channel.state == ARTRealtimeChannelInitialized || channel.state == ARTRealtimeChannelAttaching || channel.state == ARTRealtimeChannelAttached || channel.state == ARTRealtimeChannelFailed) { if(state == ARTRealtimeClosing) { //do nothing. Closed state is coming. } @@ -379,19 +379,18 @@ - (void)onConnected:(ARTProtocolMessage *)message { for (ARTRealtimeChannel *channel in self.channels) { [channel detachChannel:[ARTStatus state:ARTStateConnectionDisconnected info:message.error]]; } - - _resuming = false; - - for (ARTRealtimeChannel *channel in self.channels) { - if (channel.presenceMap.syncInProgress) { - [channel requestContinueSync]; - } - } } else if (message.error) { [self.logger warn:@"ARTRealtime: connection has resumed with non-fatal error %@", message.error.message]; // The error will be emitted on `transition` } + _resuming = false; + + for (ARTRealtimeChannel *channel in self.channels) { + if (channel.presenceMap.syncInProgress) { + [channel requestContinueSync]; + } + } } switch (self.connection.state) { diff --git a/Source/ARTRealtimeChannel.m b/Source/ARTRealtimeChannel.m index 2f5feb30c..9146e2241 100644 --- a/Source/ARTRealtimeChannel.m +++ b/Source/ARTRealtimeChannel.m @@ -24,6 +24,7 @@ #import "ARTNSArray+ARTFunctional.h" #import "ARTStatus.h" #import "ARTDefault.h" +#import "ARTClientOptions.h" @interface ARTRealtimeChannel () { ARTRealtimePresence *_realtimePresence; @@ -90,13 +91,27 @@ - (void)requestContinueSync { ARTProtocolMessage * msg = [[ARTProtocolMessage alloc] init]; msg.action = ARTProtocolMessageSync; - msg.msgSerial = self.presenceMap.syncSerial; + msg.msgSerial = self.presenceMap.syncMsgSerial; + msg.channelSerial = self.presenceMap.syncChannelSerial; msg.channel = self.name; [self.realtime send:msg callback:^(ARTStatus *status) {}]; } - (void)publishPresence:(ARTPresenceMessage *)msg callback:(art_nullable void (^)(ARTErrorInfo *__art_nullable))cb { + switch (_realtime.connection.state) { + case ARTRealtimeConnected: + break; + case ARTRealtimeConnecting: + case ARTRealtimeDisconnected: + if (_realtime.options.queueMessages) { + break; + } + default: + if (cb) cb([ARTErrorInfo createWithCode:ARTStateBadConnectionState message:@"attempted to publish presence message in a bad connection state"]); + return; + } + if (!msg.clientId) { msg.clientId = self.clientId; } @@ -148,7 +163,16 @@ - (void)publishProtocolMessage:(ARTProtocolMessage *)pm callback:(void (^)(ARTSt } case ARTRealtimeChannelAttached: { - [self sendMessage:pm callback:cb]; + if (_realtime.connection.state == ARTRealtimeConnected) { + [self sendMessage:pm callback:cb]; + } else { + ARTQueuedMessage *qm = [[ARTQueuedMessage alloc] initWithProtocolMessage:pm callback:cb]; + [self.queuedMessages addObject:qm]; + + [_realtime.connection once:ARTRealtimeConnected call:^(ARTConnectionStateChange *__art_nullable change) { + [self sendQueuedMessages]; + }]; + } break; } default: @@ -262,10 +286,14 @@ - (void)transition:(ARTRealtimeChannelState)state status:(ARTStatus *)status { if (self.state == state) { return; } - self.state = state; _errorReason = status.errorInfo; + if (state == ARTRealtimeChannelFailed) { + [_attachedEventEmitter emit:[NSNull null] with:status.errorInfo]; + [_detachedEventEmitter emit:[NSNull null] with:status.errorInfo]; + } + [self emit:(ARTChannelEvent)state with:status.errorInfo]; } @@ -327,6 +355,9 @@ - (ARTRealtimeChannelState)state { } - (void)setAttached:(ARTProtocolMessage *)message { + if (self.state == ARTRealtimeChannelFailed) { + return; + } self.attachSerial = message.channelSerial; if ([message isSyncEnabled]) { [self.presenceMap startSync]; @@ -340,9 +371,12 @@ - (void)setAttached:(ARTProtocolMessage *)message { } - (void)setDetached:(ARTProtocolMessage *)message { + if (self.state == ARTRealtimeChannelFailed) { + return; + } self.attachSerial = nil; - ARTStatus *reason = [ARTStatus state:ARTStateNotAttached info:message.error]; + ARTStatus *reason = [ARTStatus state:ARTStateNotAttached info:[ARTErrorInfo createWithCode:0 message:@"channel has detached"]]; [self detachChannel:reason]; [_detachedEventEmitter emit:[NSNull null] with:nil]; } @@ -422,13 +456,16 @@ - (void)onPresence:(ARTProtocolMessage *)message { } - (void)onSync:(ARTProtocolMessage *)message { - self.presenceMap.syncSerial = message.connectionSerial; + self.presenceMap.syncMsgSerial = message.msgSerial; + self.presenceMap.syncChannelSerial = message.channelSerial; if (message.action == ARTProtocolMessageSync) [self.logger info:@"ARTRealtime Sync message received"]; for (int i=0; i<[message.presence count]; i++) { - [self.presenceMap put:[message.presence objectAtIndex:i]]; + ARTPresenceMessage *presence = [message.presence objectAtIndex:i]; + [self.presenceMap put:presence]; + [self broadcastPresence:presence]; } if ([self isLastChannelSerial:message.channelSerial]) { @@ -460,6 +497,10 @@ - (void)attach:(void (^)(ARTErrorInfo * _Nullable))callback { [self.realtime.logger debug:__FILE__ line:__LINE__ message:@"already attached"]; if (callback) callback(nil); return; + case ARTRealtimeChannelFailed: + [self.realtime.logger debug:__FILE__ line:__LINE__ message:@"can't attach when in a failed state"]; + if (callback) callback([ARTErrorInfo createWithCode:90000 message:@"can't attach when in a failed state"]); + return; default: break; } @@ -505,6 +546,10 @@ - (void)detach:(void (^)(ARTErrorInfo * _Nullable))callback { [self.realtime.logger debug:__FILE__ line:__LINE__ message:@"already detached"]; if (callback) callback(nil); return; + case ARTRealtimeChannelFailed: + [self.realtime.logger debug:__FILE__ line:__LINE__ message:@"can't detach when in a failed state"]; + if (callback) callback([ARTErrorInfo createWithCode:90000 message:@"can't detach when in a failed state"]); + return; default: break; } diff --git a/Source/ARTRealtimePresence.h b/Source/ARTRealtimePresence.h index c44001095..f51d4f64f 100644 --- a/Source/ARTRealtimePresence.h +++ b/Source/ARTRealtimePresence.h @@ -48,8 +48,11 @@ ART_ASSUME_NONNULL_BEGIN - (void)leaveClient:(NSString *)clientId data:(id __art_nullable)data; - (void)leaveClient:(NSString *)clientId data:(id __art_nullable)data callback:(art_nullable void (^)(ARTErrorInfo *__art_nullable))cb; -- (__GENERIC(ARTEventListener, ARTPresenceMessage *) *)subscribe:(void (^)(ARTPresenceMessage *message))callback; -- (__GENERIC(ARTEventListener, ARTPresenceMessage *) *)subscribe:(ARTPresenceAction)action callback:(void (^)(ARTPresenceMessage *message))cb; +- (__GENERIC(ARTEventListener, ARTPresenceMessage *) *__art_nullable)subscribe:(void (^)(ARTPresenceMessage *message))callback; +- (__GENERIC(ARTEventListener, ARTPresenceMessage *) *__art_nullable)subscribeWithAttachCallback:(art_nullable void (^)(ARTErrorInfo *__art_nullable))onAttach callback:(void (^)(ARTPresenceMessage *message))cb; +- (__GENERIC(ARTEventListener, ARTPresenceMessage *) *__art_nullable)subscribe:(ARTPresenceAction)action callback:(void (^)(ARTPresenceMessage *message))cb; +- (__GENERIC(ARTEventListener, ARTPresenceMessage *) *__art_nullable)subscribe:(ARTPresenceAction)action onAttach:(art_nullable void (^)(ARTErrorInfo *__art_nullable))onAttach callback:(void (^)(ARTPresenceMessage *message))cb; + - (void)unsubscribe; - (void)unsubscribe:(__GENERIC(ARTEventListener, ARTPresenceMessage *) *)listener; - (void)unsubscribe:(ARTPresenceAction)action listener:(__GENERIC(ARTEventListener, ARTPresenceMessage *) *)listener; diff --git a/Source/ARTRealtimePresence.m b/Source/ARTRealtimePresence.m index 7ab17a9b4..ba49ea154 100644 --- a/Source/ARTRealtimePresence.m +++ b/Source/ARTRealtimePresence.m @@ -55,15 +55,13 @@ - (void)get:(void (^)(NSArray *, ARTErrorInfo *))callback - (void)get:(ARTRealtimePresenceQuery *)query callback:(void (^)(NSArray *, ARTErrorInfo *))callback { [self.channel throwOnDisconnectedOrFailed]; [self.channel attach:^(ARTErrorInfo *error) { - if (query.waitForSync) { + if (error) { + callback(nil, error); + } else if (query.waitForSync) { [self.channel.presenceMap onceSyncEnds:^(NSArray *members) { callback(members, nil); }]; - } - else if (error) { - callback(nil, error); - } - else { + } else { callback(self.channel.presenceMap.members.allValues, nil); } }]; @@ -164,12 +162,28 @@ - (BOOL)getSyncComplete { } - (ARTEventListener *)subscribe:(void (^)(ARTPresenceMessage * _Nonnull))callback { - [self.channel attach]; - return [self.channel.presenceEventEmitter on:callback]; + return [self subscribeWithAttachCallback:nil callback:callback]; +} + +- (ARTEventListener *)subscribeWithAttachCallback:(void (^)(ARTErrorInfo * _Nullable))onAttach callback:(void (^)(ARTPresenceMessage * _Nonnull))cb { + if (self.channel.state == ARTRealtimeChannelFailed) { + if (onAttach) onAttach([ARTErrorInfo createWithCode:0 message:@"attempted to subscribe while channel is in Failed state."]); + return nil; + } + [self.channel attach:onAttach]; + return [self.channel.presenceEventEmitter on:cb]; } - (ARTEventListener *)subscribe:(ARTPresenceAction)action callback:(void (^)(ARTPresenceMessage * _Nonnull))cb { - [self.channel attach]; + return [self subscribe:action onAttach:nil callback:cb]; +} + +- (ARTEventListener *)subscribe:(ARTPresenceAction)action onAttach:(void (^)(ARTErrorInfo * _Nullable))onAttach callback:(void (^)(ARTPresenceMessage * _Nonnull))cb { + if (self.channel.state == ARTRealtimeChannelFailed) { + if (onAttach) onAttach([ARTErrorInfo createWithCode:0 message:@"attempted to subscribe while channel is in Failed state."]); + return nil; + } + [self.channel attach:onAttach]; return [self.channel.presenceEventEmitter on:[NSNumber numberWithUnsignedInteger:action] call:cb]; } diff --git a/Source/ARTStatus.h b/Source/ARTStatus.h index 14542ff6d..1856d7310 100644 --- a/Source/ARTStatus.h +++ b/Source/ARTStatus.h @@ -24,6 +24,7 @@ typedef NS_ENUM(NSUInteger, ARTState) { ARTStateInvalidArgs, ARTStateCryptoBadPadding, ARTStateNoClientId, + ARTStateBadConnectionState, ARTStateError = 99999 }; diff --git a/Spec/RealtimeClientPresence.swift b/Spec/RealtimeClientPresence.swift index 9f96592c0..71c7fdca6 100644 --- a/Spec/RealtimeClientPresence.swift +++ b/Spec/RealtimeClientPresence.swift @@ -8,6 +8,7 @@ import Quick import Nimble +import Foundation class RealtimeClientPresence: QuickSpec { override func spec() { @@ -79,7 +80,7 @@ class RealtimeClientPresence: QuickSpec { } // RTP3 - pending("should complete the SYNC operation when the connection is disconnected unexpectedly") { + it("should complete the SYNC operation when the connection is disconnected unexpectedly") { let options = AblyTests.commonAppSetup() options.disconnectedRetryTimeout = 1.0 var clientSecondary: ARTRealtime! @@ -99,7 +100,7 @@ class RealtimeClientPresence: QuickSpec { waitUntil(timeout: testTimeout) { done in channel.attach() { _ in let transport = client.transport as! TestProxyTransport - transport.beforeProcessingReceivedMessage = { protocolMessage in + transport.afterProcessingReceivedMessage = { protocolMessage in if protocolMessage.action == .Sync { lastSyncSerial = protocolMessage.channelSerial client.onDisconnected() @@ -110,33 +111,41 @@ class RealtimeClientPresence: QuickSpec { } expect(client.connection.state).toEventually(equal(ARTRealtimeConnectionState.Connecting), timeout: options.disconnectedRetryTimeout + 1.0) + expect(client.connection.state).toEventually(equal(ARTRealtimeConnectionState.Connected), timeout: testTimeout) //Client library requests a SYNC resume by sending a SYNC ProtocolMessage with the last received sync serial number let transport = client.transport as! TestProxyTransport expect(transport.protocolMessagesSent.filter{ $0.action == .Sync }).toEventually(haveCount(1), timeout: testTimeout) expect(transport.protocolMessagesSent.filter{ $0.action == .Sync }.first!.channelSerial).to(equal(lastSyncSerial)) - expect(transport.protocolMessagesReceived.filter{ $0.action == .Sync }).to(haveCount(2)) + expect(transport.protocolMessagesReceived.filter{ $0.action == .Sync }).toEventually(haveCount(2), timeout: testTimeout) } // RTP4 - pending("should receive all 250 members") { + it("should receive all 250 members") { let options = AblyTests.commonAppSetup() var clientSource: ARTRealtime! defer { clientSource.close() } + waitUntil(timeout: testTimeout) { done in + clientSource = AblyTests.addMembersSequentiallyToChannel("test", members: 250, options: options) { + done() + }.first + } + let clientTarget = ARTRealtime(options: options) defer { clientTarget.close() } let channel = clientTarget.channels.get("test") - channel.presence.subscribe { member in - expect(member.action).to(equal(ARTPresenceAction.Present)) - } - waitUntil(timeout: testTimeout) { done in - clientSource = AblyTests.addMembersSequentiallyToChannel("test", members: 250, options: options) { - done() - }.first + var pending = 250 + channel.presence.subscribe { member in + expect(member.action).to(equal(ARTPresenceAction.Present)) + pending -= 1 + if pending == 0 { + done() + } + } } waitUntil(timeout: testTimeout) { done in @@ -152,7 +161,7 @@ class RealtimeClientPresence: QuickSpec { context("subscribe") { // RTP6a - pending("with no arguments should subscribe a listener to all presence messages") { + it("with no arguments should subscribe a listener to all presence messages") { let options = AblyTests.commonAppSetup() let client1 = ARTRealtime(options: options) @@ -168,9 +177,15 @@ class RealtimeClientPresence: QuickSpec { receivedMembers.append(member) } - channel2.presence.enterClient("john", data: "online") - channel2.presence.updateClient("john", data: "away") - channel2.presence.leaveClient("john", data: nil) + waitUntil(timeout: testTimeout) { done in + channel2.presence.enterClient("john", data: "online") { err in + channel2.presence.updateClient("john", data: "away") { err in + channel2.presence.leaveClient("john", data: nil) { err in + done() + } + } + } + } expect(receivedMembers).toEventually(haveCount(3), timeout: testTimeout) @@ -183,7 +198,7 @@ class RealtimeClientPresence: QuickSpec { expect(receivedMembers[1].clientId).to(equal("john")) expect(receivedMembers[2].action).to(equal(ARTPresenceAction.Leave)) - expect(receivedMembers[2].data).to(beNil()) + expect(receivedMembers[2].data as? NSObject).to(equal("away")) expect(receivedMembers[2].clientId).to(equal("john")) } @@ -198,7 +213,7 @@ class RealtimeClientPresence: QuickSpec { defer { client.close() } let channel = client.channels.get("test") - let listener = channel.presence.subscribe { _ in } + let listener = channel.presence.subscribe { _ in }! expect(channel.presenceEventEmitter.anyListeners).to(haveCount(1)) channel.presence.unsubscribe(listener) expect(channel.presenceEventEmitter.anyListeners).to(haveCount(0)) @@ -227,7 +242,7 @@ class RealtimeClientPresence: QuickSpec { // RTP5a - pending("all queued presence messages should fail immediately if the channel enters the DETACHED state") { + it("all queued presence messages should fail immediately if the channel enters the DETACHED state") { let client = ARTRealtime(options: AblyTests.commonAppSetup()) defer { client.close() } let channel = client.channels.get("test") @@ -248,24 +263,38 @@ class RealtimeClientPresence: QuickSpec { context("Channel state change side effects") { // RTP5b - pending("all queued presence messages will be sent immediately and a presence SYNC will be initiated implicitly if a channel enters the ATTACHED state") { - let client = ARTRealtime(options: AblyTests.commonAppSetup()) - defer { client.close() } - let channel = client.channels.get("test") - channel.attach() + it("all queued presence messages will be sent immediately and a presence SYNC will be initiated implicitly if a channel enters the ATTACHED state") { + let options = AblyTests.commonAppSetup() + let client1 = AblyTests.newRealtime(options) + defer { client1.close() } + let channel1 = client1.channels.get("room") waitUntil(timeout: testTimeout) { done in - channel.presence.enterClient("user", data: nil) { error in - expect(error).to(beNil()) - expect(channel.queuedMessages).to(haveCount(0)) + channel1.presence.enterClient("Client 1", data: nil) { errorInfo in + expect(errorInfo).to(beNil()) done() } - expect(channel.queuedMessages).to(haveCount(1)) } - expect(channel.state).to(equal(ARTRealtimeChannelState.Attached)) - expect(channel.presence.syncComplete).toEventually(beTrue(), timeout: testTimeout) - expect(channel.presenceMap.members).to(haveCount(1)) + let client2 = AblyTests.newRealtime(options) + defer { client2.close() } + let channel2 = client2.channels.get(channel1.name) + + channel2.presence.enterClient("Client 2", data: nil) { error in + expect(error).to(beNil()) + expect(channel2.queuedMessages).to(haveCount(0)) + expect(channel2.state).to(equal(ARTRealtimeChannelState.Attached)) + } + expect(channel2.queuedMessages).to(haveCount(1)) + + expect(channel2.presence.syncComplete).to(beFalse()) + + expect(channel1.presenceMap.members).to(haveCount(1)) + expect(channel2.presenceMap.members).to(haveCount(0)) + + expect(channel2.state).toEventually(equal(ARTRealtimeChannelState.Attached), timeout: testTimeout) + + expect(channel2.presence.syncComplete).toEventually(beTrue(), timeout: testTimeout) } } @@ -310,7 +339,7 @@ class RealtimeClientPresence: QuickSpec { defer { client.close() } let channel = client.channels.get("test") - let listener = channel.presence.subscribe(.Present) { _ in } + let listener = channel.presence.subscribe(.Present) { _ in }! expect(channel.presenceEventEmitter.listeners).to(haveCount(1)) channel.presence.unsubscribe(.Present, listener: listener) expect(channel.presenceEventEmitter.listeners).to(haveCount(0)) @@ -340,37 +369,44 @@ class RealtimeClientPresence: QuickSpec { } // RTP6c - pending("should result in an error if the channel is in the FAILED state") { + it("should result in an error if the channel is in the FAILED state") { let client = ARTRealtime(options: AblyTests.commonAppSetup()) defer { client.close() } - let channel = client.channels.get("test") + let channel = client.channels.get("test") channel.onError(AblyTests.newErrorProtocolMessage()) + expect(channel.state).to(equal(ARTRealtimeChannelState.Failed)) waitUntil(timeout: testTimeout) { done in - channel.presence.subscribe { member in - // TODO: missing error - done() - } + channel.presence.subscribeWithAttachCallback({ errorInfo in + expect(errorInfo).toNot(beNil()) + + channel.presence.subscribe(.Enter, onAttach: { errorInfo in + expect(errorInfo).toNot(beNil()) + done() + }) { _ in } + }) { _ in } } - expect(channel.presenceEventEmitter.anyListeners).to(haveCount(0)) } // RTP6c - pending("should result in an error if the channel moves to the FAILED state") { + it("should result in an error if the channel moves to the FAILED state") { let client = ARTRealtime(options: AblyTests.commonAppSetup()) defer { client.close() } let channel = client.channels.get("test") waitUntil(timeout: testTimeout) { done in let error = AblyTests.newErrorProtocolMessage() - channel.presence.subscribe { _ in - // TODO: missing error - done() - } + channel.presence.subscribeWithAttachCallback({ errorInfo in + expect(errorInfo).toNot(beNil()) + + channel.presence.subscribe(.Enter, onAttach: { errorInfo in + expect(errorInfo).toNot(beNil()) + done() + }) { _ in } + }) {_ in } channel.onError(error) } - expect(channel.presenceEventEmitter.anyListeners).to(haveCount(0)) } } @@ -806,7 +842,7 @@ class RealtimeClientPresence: QuickSpec { } // RTP16b - pending("all presence messages will be queued and delivered as soon as the connection state returns to CONNECTED") { + it("all presence messages will be queued and delivered as soon as the connection state returns to CONNECTED") { let options = AblyTests.commonAppSetup() options.disconnectedRetryTimeout = 1.0 let client = ARTRealtime(options: options) @@ -834,7 +870,7 @@ class RealtimeClientPresence: QuickSpec { } // RTP16b - pending("all presence messages will be lost if queueMessages has been explicitly set to false") { + it("all presence messages will be lost if queueMessages has been explicitly set to false") { let options = AblyTests.commonAppSetup() options.disconnectedRetryTimeout = 1.0 options.queueMessages = false @@ -843,6 +879,8 @@ class RealtimeClientPresence: QuickSpec { let channel = client.channels.get("test") expect(client.options.queueMessages).to(beFalse()) + expect(client.connection.state).toEventually(equal(ARTRealtimeConnectionState.Connected), timeout: testTimeout) + waitUntil(timeout: testTimeout) { done in channel.attach() { _ in client.onDisconnected() @@ -860,7 +898,7 @@ class RealtimeClientPresence: QuickSpec { } // RTP16c - pending("should result in an error if the connection state is INITIALIZED") { + it("should result in an error if the connection state is INITIALIZED") { let options = AblyTests.commonAppSetup() options.autoConnect = false let client = ARTRealtime(options: options) @@ -987,7 +1025,7 @@ class RealtimeClientPresence: QuickSpec { } // RTP11b - pending("should result in an error if the channel is in the FAILED state") { + it("should result in an error if the channel is in the FAILED state") { let client = ARTRealtime(options: AblyTests.commonAppSetup()) defer { client.close() } let channel = client.channels.get("test") @@ -996,7 +1034,7 @@ class RealtimeClientPresence: QuickSpec { waitUntil(timeout: testTimeout) { done in channel.presence.get() { members, error in - expect(error!.message).to(contain("invalid channel state")) + expect(error!.message).to(contain("can't attach when in a failed state")) expect(members).to(beNil()) done() } @@ -1004,19 +1042,19 @@ class RealtimeClientPresence: QuickSpec { } // RTP11b - pending("should result in an error if the channel moves to the FAILED state") { + it("should result in an error if the channel moves to the FAILED state") { let client = ARTRealtime(options: AblyTests.commonAppSetup()) defer { client.close() } let channel = client.channels.get("test") waitUntil(timeout: testTimeout) { done in - let error = AblyTests.newErrorProtocolMessage() + let protoError = AblyTests.newErrorProtocolMessage() channel.presence.get() { members, error in - expect(error).to(equal(error)) + expect(error).to(equal(protoError.error)) expect(members).to(beNil()) done() } - channel.onError(error) + channel.onError(protoError) } } diff --git a/Spec/TestUtilities.swift b/Spec/TestUtilities.swift index 7badd955a..14158ee1e 100644 --- a/Spec/TestUtilities.swift +++ b/Spec/TestUtilities.swift @@ -485,6 +485,7 @@ class TestProxyTransport: ARTWebSocketTransport { var beforeProcessingSentMessage: Optional<(ARTProtocolMessage)->()> = nil var beforeProcessingReceivedMessage: Optional<(ARTProtocolMessage)->()> = nil + var afterProcessingReceivedMessage: Optional<(ARTProtocolMessage)->()> = nil var actionsIgnored = [ARTProtocolMessageAction]() @@ -516,6 +517,9 @@ class TestProxyTransport: ARTWebSocketTransport { performEvent(msg) } super.receive(msg) + if let performEvent = afterProcessingReceivedMessage { + performEvent(msg) + } } override func receiveWithData(data: NSData) {