-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce a ResumeRequestResponse type
It would be great if we could start writing smaller, unit-testable pieces of code. This is an example of what one might look like. It’s based on spec version 90fb845 ("no-connection-serial"). The intention is that it form part of our implementation of #1494.
- Loading branch information
1 parent
3025f98
commit 9f9e5b6
Showing
6 changed files
with
261 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
#import "ARTResumeRequestResponse.h" | ||
#import "ARTStatus.h" | ||
#import "ARTProtocolMessage.h" | ||
#import "ARTErrorChecker.h" | ||
|
||
static NSString *TypeDescription(const ARTResumeRequestResponseType type) { | ||
switch (type) { | ||
case ARTResumeRequestResponseTypeValid: | ||
return @"Valid"; | ||
case ARTResumeRequestResponseTypeInvalid: | ||
return @"Invalid"; | ||
case ARTResumeRequestResponseTypeFatalError: | ||
return @"FatalError"; | ||
case ARTResumeRequestResponseTypeTokenError: | ||
return @"TokenError"; | ||
case ARTResumeRequestResponseTypeUnknown: | ||
return @"Unknown"; | ||
} | ||
} | ||
|
||
@implementation ARTResumeRequestResponse | ||
|
||
- (instancetype)initWithCurrentConnectionID:(NSString *const)currentConnectionID | ||
protocolMessage:(ARTProtocolMessage *const)protocolMessage | ||
errorChecker:(const id<ARTErrorChecker>)errorChecker { | ||
if (!(self = [super init])) { | ||
return nil; | ||
} | ||
|
||
// RTN15c6: "A CONNECTED ProtocolMessage with the same connectionId as the current client (and no error property)." | ||
if (protocolMessage.action == ARTProtocolMessageConnected && [protocolMessage.connectionId isEqualToString:currentConnectionID] && protocolMessage.error == nil) { | ||
_type = ARTResumeRequestResponseTypeValid; | ||
return self; | ||
} | ||
|
||
// RTN15c7: "CONNECTED ProtocolMessage with a new connectionId and an ErrorInfo in the error field." | ||
if (protocolMessage.action == ARTProtocolMessageConnected && ![protocolMessage.connectionId isEqualToString:currentConnectionID] && protocolMessage.error != nil) { | ||
_type = ARTResumeRequestResponseTypeInvalid; | ||
_error = protocolMessage.error; | ||
return self; | ||
} | ||
|
||
// RTN15c5: "ERROR ProtocolMessage indicating a failure to authenticate as a result of a token error (see RTN15h)." | ||
if (protocolMessage.action == ARTProtocolMessageError && protocolMessage.error != nil && [errorChecker isTokenError:protocolMessage.error]) { | ||
_type = ARTResumeRequestResponseTypeTokenError; | ||
_error = protocolMessage.error; | ||
return self; | ||
} | ||
|
||
// RTN15c4: "Any other ERROR ProtocolMessage indicating a fatal error in the connection." | ||
// (I’m reading this as "Any other ERROR ProtocolMessage. This indicates a fatal error in the connection." — that is, I am not expected to apply any further criteria to determine if it is a _fatal_ error.) | ||
// TODO why the != nil check | ||
if (protocolMessage.action == ARTProtocolMessageError && protocolMessage.error != nil) { | ||
_type = ARTResumeRequestResponseTypeFatalError; | ||
_error = protocolMessage.error; | ||
return self; | ||
} | ||
|
||
_type = ARTResumeRequestResponseTypeUnknown; | ||
return self; | ||
} | ||
|
||
- (NSString *)description { | ||
return [NSString stringWithFormat: @"<%@ %p: type: %@, error: %@>", [self class], self, TypeDescription(self.type), [self.error localizedDescription]]; | ||
} | ||
|
||
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
@import Foundation; | ||
|
||
@class ARTProtocolMessage; | ||
@class ARTErrorInfo; | ||
@protocol ARTErrorChecker; | ||
|
||
/** | ||
The type of the Realtime system’s response to a resume request, as described by RTN15c. | ||
*/ | ||
typedef NS_ENUM(NSUInteger, ARTResumeRequestResponseType) { | ||
/** | ||
RTN15c6: "This indicates that the resume attempt was valid. The client library should move all channels that were in the @ATTACHING@, @ATTACHED@, or @SUSPENDED@ states to the @ATTACHING@ state, and initiate an @RTL4c@ attach sequence for each. The connection should also process any messages queued per @RTL6c2@ (there is no need to wait for the attaches to finish before processing queued messages)." | ||
*/ | ||
ARTResumeRequestResponseTypeValid, | ||
|
||
/** | ||
RTN15c7: "In this case, the resume was invalid, and the error indicates the cause. The @error@ should be set as the @reason@ in the @CONNECTED@ event, and as the @Connection#errorReason@. The internal @msgSerial@ counter should be reset so that the first message published to Ably will contain a @msgSerial@ of @0@. The rest of the process is the same as for @RTN16c6@: The client library should move all channels that were in the @ATTACHING@, @ATTACHED@, or @SUSPENDED@ states to the @ATTACHING@ state, and initiate an @RTL4c@ attach sequence for each. The connection should also process any messages queued per @RTL6c2@." | ||
*/ | ||
ARTResumeRequestResponseTypeInvalid, | ||
|
||
/** | ||
RTN15c5: "The transport will be closed by the server. The spec described in "RTN15h":#RTN15h must be followed for a connection being resumed with a token error" | ||
*/ | ||
ARTResumeRequestResponseTypeTokenError, | ||
|
||
/** | ||
RTN15c4: "Any other @ERROR@ @ProtocolMessage@ indicating a fatal error in the connection. The server will close the transport immediately after. The client should transition to the @FAILED@ state triggering all attached channels to transition to the @FAILED@ state as well. Additionally the @Connection#errorReason@ will be set should be set with the error received from Ably" | ||
*/ | ||
ARTResumeRequestResponseTypeFatalError, | ||
|
||
/** | ||
The response from the Realtime system was not one of the expected responses. | ||
*/ | ||
ARTResumeRequestResponseTypeUnknown, | ||
} NS_SWIFT_NAME(ResumeRequestResponse.ResponseType); | ||
|
||
NS_ASSUME_NONNULL_BEGIN | ||
|
||
/** | ||
The Realtime system’s response to a resume request, as described by RTN15c. | ||
*/ | ||
NS_SWIFT_NAME(ResumeRequestResponse) | ||
@interface ARTResumeRequestResponse: NSObject | ||
|
||
- (instancetype)init NS_UNAVAILABLE; | ||
|
||
/** | ||
Creates an ``ARTResumeRequestResponse`` describing the resume request response that the Realtime system has communicated through the use of a protocol message. | ||
@param currentConnectionID The ID of the connection that we are trying to resume. | ||
@param protocolMessage The first protocol message received on a transport which is trying to resume a connection with ID `currentConnectionID`. | ||
@param errorChecker An error checker which will be used to check whether an error is a token error. | ||
*/ | ||
- (instancetype)initWithCurrentConnectionID:(NSString *)currentConnectionID | ||
protocolMessage:(ARTProtocolMessage *)protocolMessage | ||
errorChecker:(id<ARTErrorChecker>)errorChecker NS_DESIGNATED_INITIALIZER; | ||
|
||
/** | ||
The type of the response. This indicates how a client is meant to act upon receiving this response. | ||
*/ | ||
@property (nonatomic, readonly) ARTResumeRequestResponseType type; | ||
|
||
/** | ||
The error that the Realtime system included in its response. | ||
Non-nil if and only if `type` is ``ARTResumeRequestResponseTypeInvalid``, ``ARTResumeRequestResponseTypeTokenError``, or ``ARTResumeRequestResponseTypeFatalError``. | ||
*/ | ||
@property (nullable, nonatomic, readonly, strong) ARTErrorInfo *error; | ||
|
||
@end | ||
|
||
NS_ASSUME_NONNULL_END |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import XCTest | ||
import Ably.Private | ||
|
||
// TODO given / when / then | ||
|
||
class ResumeRequestResponseTests: XCTestCase { | ||
func test_valid() { | ||
let protocolMessage = ARTProtocolMessage() | ||
protocolMessage.action = .connected | ||
protocolMessage.connectionId = "123" | ||
|
||
let errorChecker = MockErrorChecker() | ||
|
||
let response = ResumeRequestResponse( | ||
currentConnectionID: "123", | ||
protocolMessage: protocolMessage, | ||
errorChecker: errorChecker | ||
) | ||
|
||
XCTAssertEqual(response.type, .valid) | ||
XCTAssertNil(response.error) | ||
} | ||
|
||
func test_invalid() { | ||
let protocolMessage = ARTProtocolMessage() | ||
protocolMessage.action = .connected | ||
protocolMessage.connectionId = "456" | ||
protocolMessage.error = .init() | ||
|
||
let errorChecker = MockErrorChecker() | ||
|
||
let response = ResumeRequestResponse( | ||
currentConnectionID: "123", | ||
protocolMessage: protocolMessage, | ||
errorChecker: errorChecker | ||
) | ||
|
||
XCTAssertEqual(response.type, .invalid) | ||
XCTAssertEqual(response.error, protocolMessage.error) | ||
} | ||
|
||
func test_tokenError() { | ||
let protocolMessage = ARTProtocolMessage() | ||
protocolMessage.action = .error | ||
protocolMessage.error = .init() // arbitrarily chosen | ||
|
||
let errorChecker = MockErrorChecker() | ||
errorChecker.isTokenError = true | ||
|
||
let response = ResumeRequestResponse( | ||
currentConnectionID: "123", | ||
protocolMessage: protocolMessage, | ||
errorChecker: errorChecker | ||
) | ||
|
||
XCTAssertEqual(response.type, .tokenError) | ||
XCTAssertEqual(response.error, protocolMessage.error) | ||
} | ||
|
||
func test_fatalError() { | ||
let protocolMessage = ARTProtocolMessage() | ||
protocolMessage.action = .error | ||
protocolMessage.error = .init() // arbitrarily chosen | ||
|
||
let errorChecker = MockErrorChecker() | ||
errorChecker.isTokenError = false | ||
|
||
let response = ResumeRequestResponse( | ||
currentConnectionID: "123", | ||
protocolMessage: protocolMessage, | ||
errorChecker: errorChecker | ||
) | ||
|
||
XCTAssertEqual(response.type, .fatalError) | ||
XCTAssertEqual(response.error, protocolMessage.error) | ||
} | ||
|
||
func test_unknown() { | ||
// connected with same connection ID and error | ||
let protocolMessage = ARTProtocolMessage() | ||
protocolMessage.action = .connected | ||
protocolMessage.connectionId = "123" | ||
protocolMessage.error = .init() // arbitrarily chosen | ||
|
||
let errorChecker = MockErrorChecker() | ||
|
||
let response = ResumeRequestResponse( | ||
currentConnectionID: "123", | ||
protocolMessage: protocolMessage, | ||
errorChecker: errorChecker | ||
) | ||
|
||
XCTAssertEqual(response.type, .unknown) | ||
XCTAssertNil(response.error) | ||
} | ||
} |