Skip to content

Commit

Permalink
Introduce a ResumeRequestResponse type
Browse files Browse the repository at this point in the history
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
lawrence-forooghian committed Mar 28, 2023
1 parent 3025f98 commit 9f9e5b6
Show file tree
Hide file tree
Showing 6 changed files with 261 additions and 0 deletions.
24 changes: 24 additions & 0 deletions Ably.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@
2132C22929D23484000C4355 /* MockErrorChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2132C22529D2347F000C4355 /* MockErrorChecker.swift */; };
2132C22A29D23484000C4355 /* MockErrorChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2132C22529D2347F000C4355 /* MockErrorChecker.swift */; };
2132C22B29D23485000C4355 /* MockErrorChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2132C22529D2347F000C4355 /* MockErrorChecker.swift */; };
2132C20E29D20EEC000C4355 /* ARTResumeRequestResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 2132C20D29D20EEC000C4355 /* ARTResumeRequestResponse.h */; settings = {ATTRIBUTES = (Private, ); }; };
2132C20F29D20EEC000C4355 /* ARTResumeRequestResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 2132C20D29D20EEC000C4355 /* ARTResumeRequestResponse.h */; settings = {ATTRIBUTES = (Private, ); }; };
2132C21029D20EEC000C4355 /* ARTResumeRequestResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 2132C20D29D20EEC000C4355 /* ARTResumeRequestResponse.h */; settings = {ATTRIBUTES = (Private, ); }; };
2132C21229D20F05000C4355 /* ARTResumeRequestResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 2132C21129D20F05000C4355 /* ARTResumeRequestResponse.m */; };
2132C21329D20F05000C4355 /* ARTResumeRequestResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 2132C21129D20F05000C4355 /* ARTResumeRequestResponse.m */; };
2132C21429D20F05000C4355 /* ARTResumeRequestResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 2132C21129D20F05000C4355 /* ARTResumeRequestResponse.m */; };
2132C21629D20F69000C4355 /* ResumeRequestResponseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2132C21529D20F69000C4355 /* ResumeRequestResponseTests.swift */; };
2132C21729D20F69000C4355 /* ResumeRequestResponseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2132C21529D20F69000C4355 /* ResumeRequestResponseTests.swift */; };
2132C21829D20F69000C4355 /* ResumeRequestResponseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2132C21529D20F69000C4355 /* ResumeRequestResponseTests.swift */; };
21447D3B254A2ECB00B3905A /* ARTSRWebSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 217D181B25421FED00DFF07E /* ARTSRWebSocket.h */; settings = {ATTRIBUTES = (Private, ); }; };
21447D40254A2ECE00B3905A /* ARTSRWebSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 217D181B25421FED00DFF07E /* ARTSRWebSocket.h */; settings = {ATTRIBUTES = (Private, ); }; };
21447D45254A2ED100B3905A /* ARTSRWebSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 217D181B25421FED00DFF07E /* ARTSRWebSocket.h */; settings = {ATTRIBUTES = (Private, ); }; };
Expand Down Expand Up @@ -987,6 +996,9 @@
2132C21D29D23196000C4355 /* ARTErrorChecker.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ARTErrorChecker.m; sourceTree = "<group>"; };
2132C22129D233EB000C4355 /* DefaultErrorCheckerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultErrorCheckerTests.swift; sourceTree = "<group>"; };
2132C22529D2347F000C4355 /* MockErrorChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockErrorChecker.swift; sourceTree = "<group>"; };
2132C20D29D20EEC000C4355 /* ARTResumeRequestResponse.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ARTResumeRequestResponse.h; path = PrivateHeaders/Ably/ARTResumeRequestResponse.h; sourceTree = "<group>"; };
2132C21129D20F05000C4355 /* ARTResumeRequestResponse.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ARTResumeRequestResponse.m; sourceTree = "<group>"; };
2132C21529D20F69000C4355 /* ResumeRequestResponseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResumeRequestResponseTests.swift; sourceTree = "<group>"; };
215F75F62922B1DB009E0E76 /* ARTClientInformation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ARTClientInformation.h; path = include/Ably/ARTClientInformation.h; sourceTree = "<group>"; };
215F75F72922B1DB009E0E76 /* ARTClientInformation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ARTClientInformation.m; sourceTree = "<group>"; };
215F75FE2922B30F009E0E76 /* ClientInformationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientInformationTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1518,6 +1530,7 @@
D72768201C9C19040022F8B2 /* RestClientPresenceTests.swift */,
853ED7C31B7A1A3C006F1C6F /* RestClientStatsTests.swift */,
D74CBC09212EC02C00D090E4 /* RestPaginatedTests.swift */,
2132C21529D20F69000C4355 /* ResumeRequestResponseTests.swift */,
851674EE1B7BA5CD00D35169 /* StatsTests.swift */,
D520C4DD2680A1E3000012B2 /* StringifiableTests.swift */,
EB1AE0CD1C5C3A4900D62250 /* UtilitiesTests.swift */,
Expand Down Expand Up @@ -1698,6 +1711,8 @@
EB89D4081C61C5ED007FA5B7 /* ARTRealtimeChannels.h */,
D7CEF12C1C8D821D004FB242 /* ARTRealtimeChannels+Private.h */,
EB89D40A1C61C6EA007FA5B7 /* ARTRealtimeChannels.m */,
2132C20D29D20EEC000C4355 /* ARTResumeRequestResponse.h */,
2132C21129D20F05000C4355 /* ARTResumeRequestResponse.m */,
);
name = Realtime;
sourceTree = "<group>";
Expand Down Expand Up @@ -1948,6 +1963,7 @@
961343D81A42E0B7006DC822 /* ARTClientOptions.h in Headers */,
D7534C321D79E5C20054C182 /* Ably.h in Headers */,
D777EEE0206285CF002EBA03 /* ARTDeviceIdentityTokenDetails.h in Headers */,
2132C20E29D20EEC000C4355 /* ARTResumeRequestResponse.h in Headers */,
215F76032922C76C009E0E76 /* ARTClientInformation+Private.h in Headers */,
96BF615E1A35C1C8004CF2B3 /* ARTTypes.h in Headers */,
EB503C881C7E4A090053AF00 /* ARTClientOptions+Private.h in Headers */,
Expand Down Expand Up @@ -2214,6 +2230,7 @@
D5BB211026AA993C00AA5F3E /* ARTNSURL+ARTUtils.h in Headers */,
D710D60E21949DDB008F54AD /* ARTPaginatedResult.h in Headers */,
D710D4BF21949B6C008F54AD /* ARTNSMutableRequest+ARTRest.h in Headers */,
2132C20F29D20EEC000C4355 /* ARTResumeRequestResponse.h in Headers */,
D710D5BE21949D4F008F54AD /* ARTBaseMessage+Private.h in Headers */,
D5BB20FE26A7F50000AA5F3E /* ARTSRPinningSecurityPolicy.h in Headers */,
215F75F92922B1DB009E0E76 /* ARTClientInformation.h in Headers */,
Expand Down Expand Up @@ -2359,6 +2376,7 @@
D710D61821949DDC008F54AD /* ARTPaginatedResult.h in Headers */,
D5BB213B26AAA60500AA5F3E /* ARTNSError+ARTUtils.h in Headers */,
D710D4C121949B6D008F54AD /* ARTNSMutableRequest+ARTRest.h in Headers */,
2132C21029D20EEC000C4355 /* ARTResumeRequestResponse.h in Headers */,
D710D5CE21949D50008F54AD /* ARTBaseMessage+Private.h in Headers */,
D5BB20FF26A7F50800AA5F3E /* ARTSRPinningSecurityPolicy.h in Headers */,
215F75FA2922B1DB009E0E76 /* ARTClientInformation.h in Headers */,
Expand Down Expand Up @@ -2694,6 +2712,7 @@
D72304701BB72CED00F1ABDA /* RealtimeClientTests.swift in Sources */,
841134782722205400CFA837 /* ARTArchiveTests.m in Sources */,
D74A17B81FA0D9A3006D27B5 /* PushAdminTests.swift in Sources */,
2132C21629D20F69000C4355 /* ResumeRequestResponseTests.swift in Sources */,
EB7913A81C6E54C3000ABF9B /* CryptoTests.swift in Sources */,
2132C22229D233EB000C4355 /* DefaultErrorCheckerTests.swift in Sources */,
D777EEE820650ADF002EBA03 /* PushChannelTests.swift in Sources */,
Expand Down Expand Up @@ -2778,6 +2797,7 @@
D7D29B421BE3DEB300374295 /* ARTConnection.m in Sources */,
D74CBC08212EB5B900D090E4 /* ARTNSMutableURLRequest+ARTPaginated.m in Sources */,
96BF61711A35FB7C004CF2B3 /* ARTAuth.m in Sources */,
2132C21229D20F05000C4355 /* ARTResumeRequestResponse.m in Sources */,
96E408441A38939E00087F77 /* ARTProtocolMessage.m in Sources */,
D71966E51E5DF360000974DD /* ARTPushActivationStateMachine.m in Sources */,
EB9121401CA0AD8200BA0A40 /* ARTMsgPackEncoder.m in Sources */,
Expand Down Expand Up @@ -2844,6 +2864,7 @@
EB1B53FA22F85CE4006A59AC /* ObjectLifetimesTests.swift in Sources */,
D7093C2A219E466E00723F17 /* UtilitiesTests.swift in Sources */,
D798554923EB96C000946BE2 /* DeltaCodecTests.swift in Sources */,
2132C21729D20F69000C4355 /* ResumeRequestResponseTests.swift in Sources */,
D7093C21219E466E00723F17 /* RestClientChannelsTests.swift in Sources */,
2132C22A29D23484000C4355 /* MockErrorChecker.swift in Sources */,
D7093C26219E466E00723F17 /* RealtimeClientChannelTests.swift in Sources */,
Expand Down Expand Up @@ -2880,6 +2901,7 @@
EB1B53FB22F85CE4006A59AC /* ObjectLifetimesTests.swift in Sources */,
D7093C7C219EE26400723F17 /* RealtimeClientConnectionTests.swift in Sources */,
D798554A23EB96C000946BE2 /* DeltaCodecTests.swift in Sources */,
2132C21829D20F69000C4355 /* ResumeRequestResponseTests.swift in Sources */,
D7093C74219EE26400723F17 /* AuthTests.swift in Sources */,
2132C22B29D23485000C4355 /* MockErrorChecker.swift in Sources */,
D7093C72219EE25B00723F17 /* NSObject+TestSuite.swift in Sources */,
Expand Down Expand Up @@ -2962,6 +2984,7 @@
D710D49921949ACA008F54AD /* ARTAuth.m in Sources */,
D710D5D321949D78008F54AD /* ARTTokenParams.m in Sources */,
D710D53221949C54008F54AD /* ARTPushChannel.m in Sources */,
2132C21329D20F05000C4355 /* ARTResumeRequestResponse.m in Sources */,
D710D5D421949D78008F54AD /* ARTTokenDetails.m in Sources */,
D710D49B21949ACA008F54AD /* ARTRestChannels.m in Sources */,
D710D62E21949E03008F54AD /* ARTURLSessionServerTrust.m in Sources */,
Expand Down Expand Up @@ -3074,6 +3097,7 @@
D710D5F921949D79008F54AD /* ARTTokenParams.m in Sources */,
D710D54421949C55008F54AD /* ARTPushChannel.m in Sources */,
D710D5FA21949D79008F54AD /* ARTTokenDetails.m in Sources */,
2132C21429D20F05000C4355 /* ARTResumeRequestResponse.m in Sources */,
D710D4A521949ACB008F54AD /* ARTRestChannels.m in Sources */,
D710D63E21949E04008F54AD /* ARTURLSessionServerTrust.m in Sources */,
D710D65121949E77008F54AD /* ARTJsonEncoder.m in Sources */,
Expand Down
67 changes: 67 additions & 0 deletions Source/ARTResumeRequestResponse.m
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
1 change: 1 addition & 0 deletions Source/Ably.modulemap
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,6 @@ framework module Ably {
header "ARTNSMutableURLRequest+ARTUtils.h"
header "ARTTime.h"
header "ARTErrorChecker.h"
header "ARTResumeRequestResponse.h"
}
}
72 changes: 72 additions & 0 deletions Source/PrivateHeaders/Ably/ARTResumeRequestResponse.h
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
1 change: 1 addition & 0 deletions Source/include/Ably.modulemap
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,6 @@ framework module Ably {
header "Ably/ARTNSMutableURLRequest+ARTUtils.h"
header "Ably/ARTTime.h"
header "Ably/ARTErrorChecker.h"
header "Ably/ARTResumeRequestResponse.h"
}
}
96 changes: 96 additions & 0 deletions Test/Tests/ResumeRequestResponseTests.swift
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)
}
}

0 comments on commit 9f9e5b6

Please sign in to comment.