From 1c9b69b19d59a6e8ff127fb35eb341de145cd5bc Mon Sep 17 00:00:00 2001 From: Marat Al Date: Sun, 22 May 2022 22:38:52 +0200 Subject: [PATCH 01/15] Added creating boolean from dictionary --- Source/Private/ARTNSDictionary+ARTDictionaryUtil.h | 1 + Source/Private/ARTNSDictionary+ARTDictionaryUtil.m | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/Source/Private/ARTNSDictionary+ARTDictionaryUtil.h b/Source/Private/ARTNSDictionary+ARTDictionaryUtil.h index 44379e1bc..078353c1d 100644 --- a/Source/Private/ARTNSDictionary+ARTDictionaryUtil.h +++ b/Source/Private/ARTNSDictionary+ARTDictionaryUtil.h @@ -8,6 +8,7 @@ - (NSArray *)artArray:(id)key; - (NSDictionary *)artDictionary:(id)key; - (NSInteger)artInteger:(id)key; +- (BOOL)artBoolean:(id)key; - (id)artTyped:(Class)cls key:(id)key; diff --git a/Source/Private/ARTNSDictionary+ARTDictionaryUtil.m b/Source/Private/ARTNSDictionary+ARTDictionaryUtil.m index 1893e2534..188f8c966 100644 --- a/Source/Private/ARTNSDictionary+ARTDictionaryUtil.m +++ b/Source/Private/ARTNSDictionary+ARTDictionaryUtil.m @@ -51,4 +51,8 @@ - (NSInteger)artInteger:(id)key { return 0; } +- (BOOL)artBoolean:(id)key { + return [self artInteger:key] != 0; +} + @end From 81f2d6d136cba6ceab7da062eb4192fa54efc328 Mon Sep 17 00:00:00 2001 From: Marat Al Date: Sun, 22 May 2022 22:42:52 +0200 Subject: [PATCH 02/15] Added support to get channel lifecycle status --- Source/ARTEncoder.h | 4 +++ Source/ARTJsonLikeEncoder.m | 26 ++++++++++++++ Source/ARTRestChannel.h | 2 ++ Source/ARTRestChannel.m | 55 ++++++++++++++++++++++++++++ Source/ARTTypes.h | 57 +++++++++++++++++++++++++++++ Source/ARTTypes.m | 72 +++++++++++++++++++++++++++++++++++++ 6 files changed, 216 insertions(+) diff --git a/Source/ARTEncoder.h b/Source/ARTEncoder.h index 82c27d939..b0a679535 100644 --- a/Source/ARTEncoder.h +++ b/Source/ARTEncoder.h @@ -64,6 +64,10 @@ NS_ASSUME_NONNULL_BEGIN // DeviceDetails - (nullable NSData *)encodeDeviceDetails:(ARTDeviceDetails *)deviceDetails error:(NSError *_Nullable *_Nullable)error; - (nullable ARTDeviceDetails *)decodeDeviceDetails:(NSData *)data error:(NSError *_Nullable *_Nullable)error; + +// ChannelDetails +- (nullable ARTChannelDetails *)decodeChannelDetails:(NSData *)data error:(NSError *_Nullable *_Nullable)error; + - (nullable NSArray *)decodeDevicesDetails:(NSData *)data error:(NSError * __autoreleasing *)error; - (nullable ARTDeviceIdentityTokenDetails *)decodeDeviceIdentityTokenDetails:(NSData *)data error:(NSError * __autoreleasing *)error; diff --git a/Source/ARTJsonLikeEncoder.m b/Source/ARTJsonLikeEncoder.m index eb4adc1f5..827223a8b 100644 --- a/Source/ARTJsonLikeEncoder.m +++ b/Source/ARTJsonLikeEncoder.m @@ -136,6 +136,10 @@ - (ARTDeviceDetails *)decodeDeviceDetails:(NSData *)data error:(NSError **)error return [self deviceDetailsFromDictionary:[self decodeDictionary:data error:nil] error:error]; } +- (ARTChannelDetails *)decodeChannelDetails:(NSData *)data error:(NSError **)error { + return [self channelDetailsFromDictionary:[self decodeDictionary:data error:error]]; +} + - (NSArray *)decodeDevicesDetails:(NSData *)data error:(NSError * __autoreleasing *)error { return [self devicesDetailsFromArray:[self decodeArray:data error:nil] error:error]; } @@ -753,6 +757,28 @@ - (ARTConnectionDetails *)connectionDetailsFromDictionary:(NSDictionary *)input maxIdleInterval:millisecondsToTimeInterval([input artInteger:@"maxIdleInterval"])]; } +- (ARTChannelDetails *)channelDetailsFromDictionary:(NSDictionary *)input { + if (!input) { + return nil; + } + + NSDictionary* statusDict = [input valueForKey:@"status"]; + NSDictionary* metricsDict = [statusDict valueForKeyPath:@"occupancy.metrics"]; + + ARTChannelMetrics* metrics = nil; + if (metricsDict != nil) { + metrics = [[ARTChannelMetrics alloc] initWithConnections:[metricsDict artInteger:@"connections"] + publishers:[metricsDict artInteger:@"publishers"] + subscribers:[metricsDict artInteger:@"subscribers"] + presenceConnections:[metricsDict artInteger:@"presenceConnections"] + presenceMembers:[metricsDict artInteger:@"presenceMembers"] + presenceSubscribers:[metricsDict artInteger:@"presenceSubscribers"]]; + } + return [[ARTChannelDetails alloc] initWithChannelId:[input artString:@"channelId"] + status:[statusDict artBoolean:@"isActive"] + metrics:metrics]; +} + - (NSArray *)statsFromArray:(NSArray *)input { if (![input isKindOfClass:[NSArray class]]) { return nil; diff --git a/Source/ARTRestChannel.h b/Source/ARTRestChannel.h index 0fe0e61e1..b7c73c84b 100644 --- a/Source/ARTRestChannel.h +++ b/Source/ARTRestChannel.h @@ -15,6 +15,8 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)history:(nullable ARTDataQuery *)query callback:(ARTPaginatedMessagesCallback)callback error:(NSError *_Nullable *_Nullable)errorPtr; +- (void)status:(ARTChannelDetailsCallback)callback; + - (void)setOptions:(ARTChannelOptions *_Nullable)options; @end diff --git a/Source/ARTRestChannel.m b/Source/ARTRestChannel.m index 6d07a03e9..65f871882 100644 --- a/Source/ARTRestChannel.m +++ b/Source/ARTRestChannel.m @@ -46,6 +46,10 @@ - (BOOL)history:(nullable ARTDataQuery *)query callback:(ARTPaginatedMessagesCal return [_internal history:query callback:callback error:errorPtr]; } +- (void)status:(ARTChannelDetailsCallback)callback { + [_internal status:callback]; +} + - (void)publish:(nullable NSString *)name data:(nullable id)data { [_internal publish:name data:data]; } @@ -214,6 +218,57 @@ - (BOOL)history:(ARTDataQuery *)query callback:(ARTPaginatedMessagesCallback)cal return ret; } +- (void)status:(ARTChannelDetailsCallback)callback { + if (callback) { + ARTChannelDetailsCallback userCallback = callback; + callback = ^(ARTChannelDetails *details, ARTErrorInfo *_Nullable error) { + dispatch_async(self->_userQueue, ^{ + userCallback(details, error); + }); + }; + } + dispatch_async(_queue, ^{ + NSURL *url = [NSURL URLWithString:self->_basePath]; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; + + [self.logger debug:__FILE__ line:__LINE__ message:@"RS:%p C:%p (%@) channel details request %@", self->_rest, self, self.name, request]; + + [self->_rest executeRequest:request withAuthOption:ARTAuthenticationOn completion:^(NSHTTPURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable error) { + + if (response.statusCode == 200 /*OK*/) { + NSError *decodeError = nil; + ARTChannelDetails *channelDetails = [[self->_rest defaultEncoder] decodeChannelDetails:data error:&decodeError]; + if (decodeError) { + [self.logger debug:__FILE__ line:__LINE__ message:@"%@: decode channel details failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; + if (callback) { + callback(nil, [ARTErrorInfo createFromNSError:decodeError]); + } + } + else { + [self.logger debug:__FILE__ line:__LINE__ message:@"%@: successfully got channel details %@", NSStringFromClass(self.class), channelDetails.channelId]; + if (callback) { + callback(channelDetails, nil); + } + } + } + else { + [self.logger debug:__FILE__ line:__LINE__ message:@"%@: get channel details failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; + ARTErrorInfo *errorInfo = nil; + if (error) { + if (self->_rest.options.addRequestIds) { + errorInfo = [ARTErrorInfo wrap:[ARTErrorInfo createFromNSError:error] prepend:[NSString stringWithFormat:@"Request '%@' failed with ", request.URL]]; + } else { + errorInfo = [ARTErrorInfo createFromNSError:error]; + } + } + if (callback) { + callback(nil, errorInfo); + } + } + }]; + }); +} + - (void)internalPostMessages:(id)data callback:(ARTCallback)callback { if (callback) { ARTCallback userCallback = callback; diff --git a/Source/ARTTypes.h b/Source/ARTTypes.h index 4166b25c1..2f2f6c02f 100644 --- a/Source/ARTTypes.h +++ b/Source/ARTTypes.h @@ -201,6 +201,62 @@ NSString *generateNonce(void); @end +#pragma mark - ARTChannelMetrics + +@interface ARTChannelMetrics : NSObject + +@property (nonatomic, readonly) NSInteger connections; +@property (nonatomic, readonly) NSInteger publishers; +@property (nonatomic, readonly) NSInteger subscribers; +@property (nonatomic, readonly) NSInteger presenceConnections; +@property (nonatomic, readonly) NSInteger presenceMembers; +@property (nonatomic, readonly) NSInteger presenceSubscribers; + +- (instancetype)initWithConnections:(NSInteger)connections + publishers:(NSInteger)publishers + subscribers:(NSInteger)subscribers + presenceConnections:(NSInteger)presenceConnections + presenceMembers:(NSInteger)presenceMembers + presenceSubscribers:(NSInteger)presenceSubscribers; + +@end + +#pragma mark - ARTChannelOccupancy + +@interface ARTChannelOccupancy : NSObject + +@property (nonatomic, strong, readonly) ARTChannelMetrics *metrics; + +- (instancetype)initWithMetrics:(ARTChannelMetrics *)metrics; + +@end + +#pragma mark - ARTChannelStatus + +@interface ARTChannelStatus : NSObject + +@property (nonatomic, readonly) BOOL active; + +@property (nonatomic, strong, readonly, nullable) ARTChannelOccupancy *occupancy; + +- (instancetype)initWithOccupancy:(nullable ARTChannelOccupancy *)occupancy active:(BOOL)active; + +@end + +#pragma mark - ARTChannelDetails + +@interface ARTChannelDetails : NSObject + +@property (nonatomic, strong, readonly) NSString *channelId; + +@property (nonatomic, strong, readonly) ARTChannelStatus *status; + +- (instancetype)initWithChannelId:(NSString *)channelId status:(ARTChannelStatus *)status; + +- (instancetype)initWithChannelId:(NSString *)channelId status:(BOOL)status metrics:(nullable ARTChannelMetrics *)metrics; + +@end + #pragma mark - ARTJsonCompatible @protocol ARTJsonCompatible @@ -264,6 +320,7 @@ typedef void (^ARTChannelStateCallback)(ARTChannelStateChange *stateChange); typedef void (^ARTConnectionStateCallback)(ARTConnectionStateChange *stateChange); typedef void (^ARTPresenceMessageCallback)(ARTPresenceMessage *message); typedef void (^ARTPresenceMessagesCallback)(NSArray *_Nullable result, ARTErrorInfo *_Nullable error); +typedef void (^ARTChannelDetailsCallback)(ARTChannelDetails *_Nullable details, ARTErrorInfo *_Nullable error); typedef void (^ARTStatusCallback)(ARTStatus *status); typedef void (^ARTURLRequestCallback)(NSHTTPURLResponse *_Nullable result, NSData *_Nullable data, NSError *_Nullable error); diff --git a/Source/ARTTypes.m b/Source/ARTTypes.m index d45fc0acb..3c13a28f8 100644 --- a/Source/ARTTypes.m +++ b/Source/ARTTypes.m @@ -134,6 +134,78 @@ - (NSString *)description { @end +#pragma mark - ARTChannelMetrics + +@implementation ARTChannelMetrics + +- (instancetype)initWithConnections:(NSInteger)connections + publishers:(NSInteger)publishers + subscribers:(NSInteger)subscribers + presenceConnections:(NSInteger)presenceConnections + presenceMembers:(NSInteger)presenceMembers + presenceSubscribers:(NSInteger)presenceSubscribers { + + if (self = [super init]) { + _connections = connections; + _publishers = publishers; + _subscribers = subscribers; + _presenceConnections = presenceConnections; + _presenceMembers = presenceMembers; + _presenceSubscribers = presenceSubscribers; + } + return self; +} + +@end + +#pragma mark - ARTChannelOccupancy + +@implementation ARTChannelOccupancy + +- (instancetype)initWithMetrics:(ARTChannelMetrics *)metrics { + if (self = [super init]) { + _metrics = metrics; + } + return self; +} + +@end + +#pragma mark - ARTChannelStatus + +@implementation ARTChannelStatus + +- (instancetype)initWithOccupancy:(ARTChannelOccupancy *)occupancy active:(BOOL)active { + if (self = [super init]) { + _occupancy = occupancy; + _active = active; + } + return self; +} + +@end + +#pragma mark - ARTChannelDetails + +@implementation ARTChannelDetails + +- (instancetype)initWithChannelId:(NSString *)channelId status:(ARTChannelStatus *)status { + if (self = [super init]) { + _channelId = channelId; + _status = status; + } + return self; +} + +- (instancetype)initWithChannelId:(NSString *)channelId status:(BOOL)status metrics:(ARTChannelMetrics *)metrics { + ARTChannelOccupancy *occupancy = metrics != nil ? [[ARTChannelOccupancy alloc] initWithMetrics:metrics] : nil; + ARTChannelStatus *channelStatus = [[ARTChannelStatus alloc] initWithOccupancy:occupancy active:status]; + ARTChannelDetails *details = [[ARTChannelDetails alloc] initWithChannelId:channelId status:channelStatus]; + return details; +} + +@end + #pragma mark - ARTEventIdentification @implementation NSString (ARTEventIdentification) From 188047d87dc1ff048418d0621b75e2076079ed10 Mon Sep 17 00:00:00 2001 From: Marat Al Date: Sun, 22 May 2022 22:43:45 +0200 Subject: [PATCH 03/15] Added test for getting channel lifecycle status --- Spec/Tests/RestClientChannelTests.swift | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Spec/Tests/RestClientChannelTests.swift b/Spec/Tests/RestClientChannelTests.swift index 9caee7d4d..09e1a95ad 100644 --- a/Spec/Tests/RestClientChannelTests.swift +++ b/Spec/Tests/RestClientChannelTests.swift @@ -1488,4 +1488,26 @@ class RestClientChannelTests: XCTestCase { } } } + + // ?? + func test__047__lifecycle_status_response_should_contain_status_object() { + let channelName = uniqueChannelName() + let options = AblyTests.commonAppSetup() + let testToken = getTestToken(capability: "{\"*\":[\"channel-metadata\"]}") + options.token = testToken + let client = ARTRest(options: options) + let channel = client.channels.get(channelName) + + waitUntil(timeout: testTimeout) { done in + channel.status { details, error in + expect(error).to(beNil()) + guard let details = details else { + fail("Channel details are empty"); done() + return + } + expect(details.status).toNot(beNil()) + done() + } + } + } } From a68c3a0af033951c2069c3a38fddd9310a5d0db9 Mon Sep 17 00:00:00 2001 From: Marat Al Date: Sun, 12 Jun 2022 20:21:13 +0200 Subject: [PATCH 04/15] `ARTChannelDetails` parser refactored: parsing function for each nested object; `ARTChannelDetails.status` property is nullable now --- Source/ARTJsonLikeEncoder.m | 64 +++++++++++++++++++++---------------- Source/ARTTypes.h | 6 ++-- Source/ARTTypes.m | 7 ---- 3 files changed, 39 insertions(+), 38 deletions(-) diff --git a/Source/ARTJsonLikeEncoder.m b/Source/ARTJsonLikeEncoder.m index 827223a8b..adf8140f8 100644 --- a/Source/ARTJsonLikeEncoder.m +++ b/Source/ARTJsonLikeEncoder.m @@ -746,37 +746,47 @@ - (ARTConnectionDetails *)connectionDetailsFromDictionary:(NSDictionary *)input if (!input) { return nil; } + return [[ARTConnectionDetails alloc] initWithClientId:[input artString:@"clientId"] + connectionKey:[input artString:@"connectionKey"] + maxMessageSize:[input artInteger:@"maxMessageSize"] + maxFrameSize:[input artInteger:@"maxFrameSize"] + maxInboundRate:[input artInteger:@"maxInboundRate"] + connectionStateTtl:millisecondsToTimeInterval([input artInteger:@"connectionStateTtl"]) + serverId:[input artString:@"serverId"] + maxIdleInterval:millisecondsToTimeInterval([input artInteger:@"maxIdleInterval"])]; +} + +- (ARTChannelMetrics *)channelMetricsFromDictionary:(NSDictionary *)input { + return [[ARTChannelMetrics alloc] initWithConnections:[input artInteger:@"connections"] + publishers:[input artInteger:@"publishers"] + subscribers:[input artInteger:@"subscribers"] + presenceConnections:[input artInteger:@"presenceConnections"] + presenceMembers:[input artInteger:@"presenceMembers"] + presenceSubscribers:[input artInteger:@"presenceSubscribers"]];; +} + +- (nullable ARTChannelOccupancy *)channelOccupancyFromDictionary:(NSDictionary *)input { + NSDictionary *metricsDict = [input valueForKey:@"metrics"]; + if (metricsDict == nil) { + return nil; + } + ARTChannelMetrics *metrics = [self channelMetricsFromDictionary:metricsDict]; + ARTChannelOccupancy *occupancy = [[ARTChannelOccupancy alloc] initWithMetrics:metrics]; + return occupancy; +} - return [[ARTConnectionDetails alloc] initWithClientId:[input artString:@"clientId"] - connectionKey:[input artString:@"connectionKey"] - maxMessageSize:[input artInteger:@"maxMessageSize"] - maxFrameSize:[input artInteger:@"maxFrameSize"] - maxInboundRate:[input artInteger:@"maxInboundRate"] - connectionStateTtl:millisecondsToTimeInterval([input artInteger:@"connectionStateTtl"]) - serverId:[input artString:@"serverId"] - maxIdleInterval:millisecondsToTimeInterval([input artInteger:@"maxIdleInterval"])]; +- (nullable ARTChannelStatus *)channelStatusFromDictionary:(NSDictionary *)input { + NSDictionary *occupancyDict = [input valueForKey:@"occupancy"]; + ARTChannelOccupancy *occupancy = occupancyDict != nil ? [self channelOccupancyFromDictionary:occupancyDict] : nil; + ARTChannelStatus *status = [[ARTChannelStatus alloc] initWithOccupancy:occupancy active:[input artBoolean:@"isActive"]]; + return status; } - (ARTChannelDetails *)channelDetailsFromDictionary:(NSDictionary *)input { - if (!input) { - return nil; - } - - NSDictionary* statusDict = [input valueForKey:@"status"]; - NSDictionary* metricsDict = [statusDict valueForKeyPath:@"occupancy.metrics"]; - - ARTChannelMetrics* metrics = nil; - if (metricsDict != nil) { - metrics = [[ARTChannelMetrics alloc] initWithConnections:[metricsDict artInteger:@"connections"] - publishers:[metricsDict artInteger:@"publishers"] - subscribers:[metricsDict artInteger:@"subscribers"] - presenceConnections:[metricsDict artInteger:@"presenceConnections"] - presenceMembers:[metricsDict artInteger:@"presenceMembers"] - presenceSubscribers:[metricsDict artInteger:@"presenceSubscribers"]]; - } - return [[ARTChannelDetails alloc] initWithChannelId:[input artString:@"channelId"] - status:[statusDict artBoolean:@"isActive"] - metrics:metrics]; + NSDictionary *statusDict = [input valueForKey:@"status"]; + ARTChannelStatus *status = statusDict != nil ? [self channelStatusFromDictionary:statusDict] : nil; + ARTChannelDetails *details = [[ARTChannelDetails alloc] initWithChannelId:[input artString:@"channelId"] status:status]; + return details; } - (NSArray *)statsFromArray:(NSArray *)input { diff --git a/Source/ARTTypes.h b/Source/ARTTypes.h index 2f2f6c02f..d96c5be8b 100644 --- a/Source/ARTTypes.h +++ b/Source/ARTTypes.h @@ -249,11 +249,9 @@ NSString *generateNonce(void); @property (nonatomic, strong, readonly) NSString *channelId; -@property (nonatomic, strong, readonly) ARTChannelStatus *status; +@property (nonatomic, strong, readonly, nullable) ARTChannelStatus *status; -- (instancetype)initWithChannelId:(NSString *)channelId status:(ARTChannelStatus *)status; - -- (instancetype)initWithChannelId:(NSString *)channelId status:(BOOL)status metrics:(nullable ARTChannelMetrics *)metrics; +- (instancetype)initWithChannelId:(NSString *)channelId status:(nullable ARTChannelStatus *)status; @end diff --git a/Source/ARTTypes.m b/Source/ARTTypes.m index 3c13a28f8..f723ef6cd 100644 --- a/Source/ARTTypes.m +++ b/Source/ARTTypes.m @@ -197,13 +197,6 @@ - (instancetype)initWithChannelId:(NSString *)channelId status:(ARTChannelStatus return self; } -- (instancetype)initWithChannelId:(NSString *)channelId status:(BOOL)status metrics:(ARTChannelMetrics *)metrics { - ARTChannelOccupancy *occupancy = metrics != nil ? [[ARTChannelOccupancy alloc] initWithMetrics:metrics] : nil; - ARTChannelStatus *channelStatus = [[ARTChannelStatus alloc] initWithOccupancy:occupancy active:status]; - ARTChannelDetails *details = [[ARTChannelDetails alloc] initWithChannelId:channelId status:channelStatus]; - return details; -} - @end #pragma mark - ARTEventIdentification From f83d845dd5f51e680acec4a04a323eabbeb356a0 Mon Sep 17 00:00:00 2001 From: Marat Al Date: Sun, 12 Jun 2022 21:28:45 +0200 Subject: [PATCH 05/15] Using proper encoder based on response mime type --- Source/ARTRestChannel.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/ARTRestChannel.m b/Source/ARTRestChannel.m index 65f871882..969487a51 100644 --- a/Source/ARTRestChannel.m +++ b/Source/ARTRestChannel.m @@ -237,7 +237,8 @@ - (void)status:(ARTChannelDetailsCallback)callback { if (response.statusCode == 200 /*OK*/) { NSError *decodeError = nil; - ARTChannelDetails *channelDetails = [[self->_rest defaultEncoder] decodeChannelDetails:data error:&decodeError]; + id encoder = self->_rest.encoders[response.MIMEType]; + ARTChannelDetails *channelDetails = [encoder decodeChannelDetails:data error:&decodeError]; if (decodeError) { [self.logger debug:__FILE__ line:__LINE__ message:@"%@: decode channel details failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; if (callback) { From 8d9917c10bb226641e00e3176351519749a36940 Mon Sep 17 00:00:00 2001 From: Marat Al Date: Sun, 12 Jun 2022 22:02:39 +0200 Subject: [PATCH 06/15] Test name and spec point numbers fixed --- Spec/Tests/RestClientChannelTests.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Spec/Tests/RestClientChannelTests.swift b/Spec/Tests/RestClientChannelTests.swift index 09e1a95ad..9f66c011b 100644 --- a/Spec/Tests/RestClientChannelTests.swift +++ b/Spec/Tests/RestClientChannelTests.swift @@ -1489,8 +1489,8 @@ class RestClientChannelTests: XCTestCase { } } - // ?? - func test__047__lifecycle_status_response_should_contain_status_object() { + // RSL8a + func test__047__channel_status_returns_a_channel_details_object() { let channelName = uniqueChannelName() let options = AblyTests.commonAppSetup() let testToken = getTestToken(capability: "{\"*\":[\"channel-metadata\"]}") @@ -1505,7 +1505,8 @@ class RestClientChannelTests: XCTestCase { fail("Channel details are empty"); done() return } - expect(details.status).toNot(beNil()) + expect(details.channelId).toNot(beNil()) // CHD2a + expect(details.status).toNot(beNil()) // CHD2b done() } } From 8c4610825ced2c1c6d51c19879ea9f46d1ee977e Mon Sep 17 00:00:00 2001 From: Marat Al Date: Fri, 24 Jun 2022 15:13:14 +0200 Subject: [PATCH 07/15] Removed nullability from non-nullable properties --- Source/ARTJsonLikeEncoder.m | 13 +++++-------- Source/ARTTypes.h | 8 ++++---- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/Source/ARTJsonLikeEncoder.m b/Source/ARTJsonLikeEncoder.m index adf8140f8..221e68c68 100644 --- a/Source/ARTJsonLikeEncoder.m +++ b/Source/ARTJsonLikeEncoder.m @@ -765,26 +765,23 @@ - (ARTChannelMetrics *)channelMetricsFromDictionary:(NSDictionary *)input { presenceSubscribers:[input artInteger:@"presenceSubscribers"]];; } -- (nullable ARTChannelOccupancy *)channelOccupancyFromDictionary:(NSDictionary *)input { - NSDictionary *metricsDict = [input valueForKey:@"metrics"]; - if (metricsDict == nil) { - return nil; - } +- (ARTChannelOccupancy *)channelOccupancyFromDictionary:(NSDictionary *)input { + NSDictionary *metricsDict = [input valueForKey:@"metrics"] ?: @{}; ARTChannelMetrics *metrics = [self channelMetricsFromDictionary:metricsDict]; ARTChannelOccupancy *occupancy = [[ARTChannelOccupancy alloc] initWithMetrics:metrics]; return occupancy; } -- (nullable ARTChannelStatus *)channelStatusFromDictionary:(NSDictionary *)input { +- (ARTChannelStatus *)channelStatusFromDictionary:(NSDictionary *)input { NSDictionary *occupancyDict = [input valueForKey:@"occupancy"]; - ARTChannelOccupancy *occupancy = occupancyDict != nil ? [self channelOccupancyFromDictionary:occupancyDict] : nil; + ARTChannelOccupancy *occupancy = [self channelOccupancyFromDictionary:occupancyDict]; ARTChannelStatus *status = [[ARTChannelStatus alloc] initWithOccupancy:occupancy active:[input artBoolean:@"isActive"]]; return status; } - (ARTChannelDetails *)channelDetailsFromDictionary:(NSDictionary *)input { NSDictionary *statusDict = [input valueForKey:@"status"]; - ARTChannelStatus *status = statusDict != nil ? [self channelStatusFromDictionary:statusDict] : nil; + ARTChannelStatus *status = [self channelStatusFromDictionary:statusDict]; ARTChannelDetails *details = [[ARTChannelDetails alloc] initWithChannelId:[input artString:@"channelId"] status:status]; return details; } diff --git a/Source/ARTTypes.h b/Source/ARTTypes.h index d96c5be8b..8c10d7bdc 100644 --- a/Source/ARTTypes.h +++ b/Source/ARTTypes.h @@ -237,9 +237,9 @@ NSString *generateNonce(void); @property (nonatomic, readonly) BOOL active; -@property (nonatomic, strong, readonly, nullable) ARTChannelOccupancy *occupancy; +@property (nonatomic, strong, readonly) ARTChannelOccupancy *occupancy; -- (instancetype)initWithOccupancy:(nullable ARTChannelOccupancy *)occupancy active:(BOOL)active; +- (instancetype)initWithOccupancy:(ARTChannelOccupancy *)occupancy active:(BOOL)active; @end @@ -249,9 +249,9 @@ NSString *generateNonce(void); @property (nonatomic, strong, readonly) NSString *channelId; -@property (nonatomic, strong, readonly, nullable) ARTChannelStatus *status; +@property (nonatomic, strong, readonly) ARTChannelStatus *status; -- (instancetype)initWithChannelId:(NSString *)channelId status:(nullable ARTChannelStatus *)status; +- (instancetype)initWithChannelId:(NSString *)channelId status:(ARTChannelStatus *)status; @end From 5fd6e4ef9fb26e5b5dd08dc9b1b66827f0bc0d93 Mon Sep 17 00:00:00 2001 From: Marat Al Date: Fri, 24 Jun 2022 16:10:22 +0200 Subject: [PATCH 08/15] Testing metrics counters with 1 channel subscriber --- Spec/Tests/RestClientChannelTests.swift | 38 +++++++++++++++++++------ 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/Spec/Tests/RestClientChannelTests.swift b/Spec/Tests/RestClientChannelTests.swift index 9f66c011b..ede7508c7 100644 --- a/Spec/Tests/RestClientChannelTests.swift +++ b/Spec/Tests/RestClientChannelTests.swift @@ -1490,23 +1490,43 @@ class RestClientChannelTests: XCTestCase { } // RSL8a - func test__047__channel_status_returns_a_channel_details_object() { - let channelName = uniqueChannelName() + func test__047__channel_with_subscribers_has_status_method_returning_a_channel_details_object_with_fullfilled_channel_metrics() { let options = AblyTests.commonAppSetup() - let testToken = getTestToken(capability: "{\"*\":[\"channel-metadata\"]}") - options.token = testToken - let client = ARTRest(options: options) - let channel = client.channels.get(channelName) + options.logHandler = ARTLog(capturingOutput: true) + let rest = ARTRest(options: options) + let channelOptions = ARTChannelOptions(cipher: ["key": ARTCrypto.generateRandomKey()] as ARTCipherParamsCompatible) + let channelName = uniqueChannelName(prefix: "ch1") + let restChannel = rest.channels.get(channelName, options: channelOptions) + rest.internal.httpExecutor = testHTTPExecutor + let realtime = ARTRealtime(options: options) + let realtimeChannel = realtime.channels.get(channelName) + + let expectedMessage = ["something": 1] waitUntil(timeout: testTimeout) { done in - channel.status { details, error in + realtimeChannel.subscribe { message in + print(message) + done() + } + restChannel.publish(nil, data: expectedMessage) { err in + print(err == nil ? "No error" : "Publish error: \(err!)") + } + } + + waitUntil(timeout: testTimeout) { done in + restChannel.status { details, error in expect(error).to(beNil()) guard let details = details else { fail("Channel details are empty"); done() return } - expect(details.channelId).toNot(beNil()) // CHD2a - expect(details.status).toNot(beNil()) // CHD2b + expect(details.status.occupancy.metrics.connections) == 1 // CHM2a + expect(details.status.occupancy.metrics.publishers) == 1 // CHM2e + expect(details.status.occupancy.metrics.subscribers) == 1 // CHM2f + + expect(details.status.occupancy.metrics.presenceMembers) == 0 // CHM2c + expect(details.status.occupancy.metrics.presenceConnections) == 1 // CHM2b + expect(details.status.occupancy.metrics.presenceSubscribers) == 1 // CHM2d done() } } From ab35c8bf3a2931fdbee4f049d695b10ba33b70fb Mon Sep 17 00:00:00 2001 From: Marat Al Date: Fri, 24 Jun 2022 20:36:34 +0200 Subject: [PATCH 09/15] Test fixed --- Spec/Tests/RestClientChannelTests.swift | 28 ++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Spec/Tests/RestClientChannelTests.swift b/Spec/Tests/RestClientChannelTests.swift index ede7508c7..2c28e9006 100644 --- a/Spec/Tests/RestClientChannelTests.swift +++ b/Spec/Tests/RestClientChannelTests.swift @@ -1489,30 +1489,31 @@ class RestClientChannelTests: XCTestCase { } } - // RSL8a - func test__047__channel_with_subscribers_has_status_method_returning_a_channel_details_object_with_fullfilled_channel_metrics() { + // RSL8a, CHD2b, CHS2b, CHO2a + func test__047__channel_with_subscribers_status_method_returns_a_channel_details_object_with_fullfilled_channel_metrics() { let options = AblyTests.commonAppSetup() - options.logHandler = ARTLog(capturingOutput: true) let rest = ARTRest(options: options) - let channelOptions = ARTChannelOptions(cipher: ["key": ARTCrypto.generateRandomKey()] as ARTCipherParamsCompatible) - let channelName = uniqueChannelName(prefix: "ch1") - let restChannel = rest.channels.get(channelName, options: channelOptions) - rest.internal.httpExecutor = testHTTPExecutor let realtime = ARTRealtime(options: options) - let realtimeChannel = realtime.channels.get(channelName) - let expectedMessage = ["something": 1] + let channelName = uniqueChannelName() + let realtimeChannel = realtime.channels.get(channelName) + waitUntil(timeout: testTimeout) { done in + realtimeChannel.once(.attached) { _ in + done() + } + } + + let restChannel = rest.channels.get(channelName) waitUntil(timeout: testTimeout) { done in realtimeChannel.subscribe { message in - print(message) done() } - restChannel.publish(nil, data: expectedMessage) { err in - print(err == nil ? "No error" : "Publish error: \(err!)") + restChannel.publish(nil, data: nil) { error in + expect(error).to(beNil()) } } - + waitUntil(timeout: testTimeout) { done in restChannel.status { details, error in expect(error).to(beNil()) @@ -1523,7 +1524,6 @@ class RestClientChannelTests: XCTestCase { expect(details.status.occupancy.metrics.connections) == 1 // CHM2a expect(details.status.occupancy.metrics.publishers) == 1 // CHM2e expect(details.status.occupancy.metrics.subscribers) == 1 // CHM2f - expect(details.status.occupancy.metrics.presenceMembers) == 0 // CHM2c expect(details.status.occupancy.metrics.presenceConnections) == 1 // CHM2b expect(details.status.occupancy.metrics.presenceSubscribers) == 1 // CHM2d From 31503e8339246b3e0db96209a43cb28a1930dffe Mon Sep 17 00:00:00 2001 From: Marat Al Date: Fri, 8 Jul 2022 19:39:08 +0200 Subject: [PATCH 10/15] Fixed test name --- Spec/Tests/RestClientChannelTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Spec/Tests/RestClientChannelTests.swift b/Spec/Tests/RestClientChannelTests.swift index 2c28e9006..450cdfb72 100644 --- a/Spec/Tests/RestClientChannelTests.swift +++ b/Spec/Tests/RestClientChannelTests.swift @@ -1490,7 +1490,7 @@ class RestClientChannelTests: XCTestCase { } // RSL8a, CHD2b, CHS2b, CHO2a - func test__047__channel_with_subscribers_status_method_returns_a_channel_details_object_with_fullfilled_channel_metrics() { + func test__047__status__with_subscribers__returns_a_channel_details_object_populated_with_channel_metrics() { let options = AblyTests.commonAppSetup() let rest = ARTRest(options: options) let realtime = ARTRealtime(options: options) From 20ded1dc68855cae2e09e13ce21e5a93f8246826 Mon Sep 17 00:00:00 2001 From: Marat Al Date: Fri, 8 Jul 2022 19:40:26 +0200 Subject: [PATCH 11/15] Made `presenceMembers` equal to 1 --- Spec/Tests/RestClientChannelTests.swift | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Spec/Tests/RestClientChannelTests.swift b/Spec/Tests/RestClientChannelTests.swift index 450cdfb72..7ed94291a 100644 --- a/Spec/Tests/RestClientChannelTests.swift +++ b/Spec/Tests/RestClientChannelTests.swift @@ -1492,6 +1492,7 @@ class RestClientChannelTests: XCTestCase { // RSL8a, CHD2b, CHS2b, CHO2a func test__047__status__with_subscribers__returns_a_channel_details_object_populated_with_channel_metrics() { let options = AblyTests.commonAppSetup() + options.clientId = "Client 1" let rest = ARTRest(options: options) let realtime = ARTRealtime(options: options) @@ -1499,8 +1500,13 @@ class RestClientChannelTests: XCTestCase { let realtimeChannel = realtime.channels.get(channelName) waitUntil(timeout: testTimeout) { done in + let partialDone = AblyTests.splitDone(2, done: done) + realtimeChannel.presence.enter(nil) { error in + expect(error).to(beNil()) + partialDone() + } realtimeChannel.once(.attached) { _ in - done() + partialDone() } } @@ -1524,7 +1530,7 @@ class RestClientChannelTests: XCTestCase { expect(details.status.occupancy.metrics.connections) == 1 // CHM2a expect(details.status.occupancy.metrics.publishers) == 1 // CHM2e expect(details.status.occupancy.metrics.subscribers) == 1 // CHM2f - expect(details.status.occupancy.metrics.presenceMembers) == 0 // CHM2c + expect(details.status.occupancy.metrics.presenceMembers) == 1 // CHM2c expect(details.status.occupancy.metrics.presenceConnections) == 1 // CHM2b expect(details.status.occupancy.metrics.presenceSubscribers) == 1 // CHM2d done() From a1b14095b3822be654a08d04818e5eeaa8f30c40 Mon Sep 17 00:00:00 2001 From: Marat Al Date: Fri, 8 Jul 2022 21:40:15 +0200 Subject: [PATCH 12/15] Removed unnecessary calls affecting metrics `realtimeChannel.presence.enter` is enough to increase everything in `occupancy.metrics` to 1. --- Spec/Tests/RestClientChannelTests.swift | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/Spec/Tests/RestClientChannelTests.swift b/Spec/Tests/RestClientChannelTests.swift index 7ed94291a..b70df8652 100644 --- a/Spec/Tests/RestClientChannelTests.swift +++ b/Spec/Tests/RestClientChannelTests.swift @@ -1500,26 +1500,13 @@ class RestClientChannelTests: XCTestCase { let realtimeChannel = realtime.channels.get(channelName) waitUntil(timeout: testTimeout) { done in - let partialDone = AblyTests.splitDone(2, done: done) realtimeChannel.presence.enter(nil) { error in expect(error).to(beNil()) - partialDone() - } - realtimeChannel.once(.attached) { _ in - partialDone() + done() } } let restChannel = rest.channels.get(channelName) - waitUntil(timeout: testTimeout) { done in - realtimeChannel.subscribe { message in - done() - } - restChannel.publish(nil, data: nil) { error in - expect(error).to(beNil()) - } - } - waitUntil(timeout: testTimeout) { done in restChannel.status { details, error in expect(error).to(beNil()) From 4777c543399f0baa2e377d4c37afb0b5f7c342a5 Mon Sep 17 00:00:00 2001 From: Marat Al Date: Fri, 8 Jul 2022 21:40:55 +0200 Subject: [PATCH 13/15] Fixed non-nil `metrics` in `channelOccupancyFromDictionary` --- Source/ARTJsonLikeEncoder.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/ARTJsonLikeEncoder.m b/Source/ARTJsonLikeEncoder.m index 221e68c68..4280d1260 100644 --- a/Source/ARTJsonLikeEncoder.m +++ b/Source/ARTJsonLikeEncoder.m @@ -766,7 +766,7 @@ - (ARTChannelMetrics *)channelMetricsFromDictionary:(NSDictionary *)input { } - (ARTChannelOccupancy *)channelOccupancyFromDictionary:(NSDictionary *)input { - NSDictionary *metricsDict = [input valueForKey:@"metrics"] ?: @{}; + NSDictionary *metricsDict = [input valueForKey:@"metrics"]; ARTChannelMetrics *metrics = [self channelMetricsFromDictionary:metricsDict]; ARTChannelOccupancy *occupancy = [[ARTChannelOccupancy alloc] initWithMetrics:metrics]; return occupancy; From da242aca6be9312406c30cb605b3a9c817f18361 Mon Sep 17 00:00:00 2001 From: Marat Al Date: Fri, 8 Jul 2022 21:44:48 +0200 Subject: [PATCH 14/15] Code formatting --- Spec/Tests/RestClientChannelTests.swift | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Spec/Tests/RestClientChannelTests.swift b/Spec/Tests/RestClientChannelTests.swift index b70df8652..c8122d77a 100644 --- a/Spec/Tests/RestClientChannelTests.swift +++ b/Spec/Tests/RestClientChannelTests.swift @@ -1495,18 +1495,16 @@ class RestClientChannelTests: XCTestCase { options.clientId = "Client 1" let rest = ARTRest(options: options) let realtime = ARTRealtime(options: options) - let channelName = uniqueChannelName() - let realtimeChannel = realtime.channels.get(channelName) + let restChannel = rest.channels.get(channelName) + waitUntil(timeout: testTimeout) { done in realtimeChannel.presence.enter(nil) { error in expect(error).to(beNil()) done() } } - - let restChannel = rest.channels.get(channelName) waitUntil(timeout: testTimeout) { done in restChannel.status { details, error in expect(error).to(beNil()) From 8af9a1a4ae27085cb49a2954be4d6be7f01e1a89 Mon Sep 17 00:00:00 2001 From: Marat Al Date: Sat, 9 Jul 2022 00:21:27 +0200 Subject: [PATCH 15/15] Return an error if a decoder wasn't found for a MIMEType. --- Source/ARTRestChannel.m | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/Source/ARTRestChannel.m b/Source/ARTRestChannel.m index 969487a51..35d613467 100644 --- a/Source/ARTRestChannel.m +++ b/Source/ARTRestChannel.m @@ -237,18 +237,27 @@ - (void)status:(ARTChannelDetailsCallback)callback { if (response.statusCode == 200 /*OK*/) { NSError *decodeError = nil; - id encoder = self->_rest.encoders[response.MIMEType]; - ARTChannelDetails *channelDetails = [encoder decodeChannelDetails:data error:&decodeError]; - if (decodeError) { - [self.logger debug:__FILE__ line:__LINE__ message:@"%@: decode channel details failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; + id decoder = self->_rest.encoders[response.MIMEType]; + if (decoder == nil) { + NSString* errorMessage = [NSString stringWithFormat:@"Decoder for MIMEType '%@' wasn't found.", response.MIMEType]; + [self.logger debug:__FILE__ line:__LINE__ message:@"%@: %@", NSStringFromClass(self.class), errorMessage]; if (callback) { - callback(nil, [ARTErrorInfo createFromNSError:decodeError]); + callback(nil, [ARTErrorInfo createWithCode:ARTErrorUnableToDecodeMessage message:errorMessage]); } } else { - [self.logger debug:__FILE__ line:__LINE__ message:@"%@: successfully got channel details %@", NSStringFromClass(self.class), channelDetails.channelId]; - if (callback) { - callback(channelDetails, nil); + ARTChannelDetails *channelDetails = [decoder decodeChannelDetails:data error:&decodeError]; + if (decodeError) { + [self.logger debug:__FILE__ line:__LINE__ message:@"%@: decode channel details failed (%@)", NSStringFromClass(self.class), error.localizedDescription]; + if (callback) { + callback(nil, [ARTErrorInfo createFromNSError:decodeError]); + } + } + else { + [self.logger debug:__FILE__ line:__LINE__ message:@"%@: successfully got channel details %@", NSStringFromClass(self.class), channelDetails.channelId]; + if (callback) { + callback(channelDetails, nil); + } } } }