FIRMessagingPendingTopicsListTest.m 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import <OCMock/OCMock.h>
  17. #import <XCTest/XCTest.h>
  18. #import "FIRMessagingDefines.h"
  19. #import "FIRMessagingPendingTopicsList.h"
  20. #import "FIRMessagingTopicsCommon.h"
  21. typedef void (^MockDelegateSubscriptionHandler)(NSString *topic,
  22. FIRMessagingTopicAction action,
  23. FIRMessagingTopicOperationCompletion completion);
  24. /**
  25. * This object lets us provide a stub delegate where we can customize the behavior by providing
  26. * blocks. We need to use this instead of stubbing a OCMockProtocol because our delegate methods
  27. * take primitive values (e.g. action), which is not easy to use from OCMock
  28. * @see http://stackoverflow.com/a/6332023
  29. */
  30. @interface MockPendingTopicsListDelegate: NSObject <FIRMessagingPendingTopicsListDelegate>
  31. @property(nonatomic, assign) BOOL isReady;
  32. @property(nonatomic, copy) MockDelegateSubscriptionHandler subscriptionHandler;
  33. @property(nonatomic, copy) void(^updateHandler)();
  34. @end
  35. @implementation MockPendingTopicsListDelegate
  36. - (BOOL)pendingTopicsListCanRequestTopicUpdates:(FIRMessagingPendingTopicsList *)list {
  37. return self.isReady;
  38. }
  39. - (void)pendingTopicsList:(FIRMessagingPendingTopicsList *)list
  40. requestedUpdateForTopic:(NSString *)topic
  41. action:(FIRMessagingTopicAction)action
  42. completion:(FIRMessagingTopicOperationCompletion)completion {
  43. if (self.subscriptionHandler) {
  44. self.subscriptionHandler(topic, action, completion);
  45. }
  46. }
  47. - (void)pendingTopicsListDidUpdate:(FIRMessagingPendingTopicsList *)list {
  48. if (self.updateHandler) {
  49. self.updateHandler();
  50. }
  51. }
  52. @end
  53. @interface FIRMessagingPendingTopicsListTest : XCTestCase
  54. /// Using this delegate lets us prevent any topic operations from start, making it easy to measure
  55. /// our batches
  56. @property(nonatomic, strong) MockPendingTopicsListDelegate *notReadyDelegate;
  57. /// Using this delegate will always begin topic operations (which will never return by default).
  58. /// Useful for overriding with block-methods to handle update requests
  59. @property(nonatomic, strong) MockPendingTopicsListDelegate *alwaysReadyDelegate;
  60. @end
  61. @implementation FIRMessagingPendingTopicsListTest
  62. - (void)setUp {
  63. [super setUp];
  64. self.notReadyDelegate = [[MockPendingTopicsListDelegate alloc] init];
  65. self.notReadyDelegate.isReady = NO;
  66. self.alwaysReadyDelegate = [[MockPendingTopicsListDelegate alloc] init];
  67. self.alwaysReadyDelegate.isReady = YES;
  68. }
  69. - (void)tearDown {
  70. self.notReadyDelegate = nil;
  71. self.alwaysReadyDelegate = nil;
  72. [super tearDown];
  73. }
  74. - (void)testAddSingleTopic {
  75. FIRMessagingPendingTopicsList *pendingTopics = [[FIRMessagingPendingTopicsList alloc] init];
  76. pendingTopics.delegate = self.notReadyDelegate;
  77. [pendingTopics addOperationForTopic:@"/topics/0"
  78. withAction:FIRMessagingTopicActionSubscribe
  79. completion:nil];
  80. XCTAssertEqual(pendingTopics.numberOfBatches, 1);
  81. }
  82. - (void)testAddSameTopicAndActionMultipleTimes {
  83. FIRMessagingPendingTopicsList *pendingTopics = [[FIRMessagingPendingTopicsList alloc] init];
  84. pendingTopics.delegate = self.notReadyDelegate;
  85. [pendingTopics addOperationForTopic:@"/topics/0"
  86. withAction:FIRMessagingTopicActionSubscribe
  87. completion:nil];
  88. [pendingTopics addOperationForTopic:@"/topics/0"
  89. withAction:FIRMessagingTopicActionSubscribe
  90. completion:nil];
  91. [pendingTopics addOperationForTopic:@"/topics/0"
  92. withAction:FIRMessagingTopicActionSubscribe
  93. completion:nil];
  94. XCTAssertEqual(pendingTopics.numberOfBatches, 1);
  95. }
  96. - (void)testAddMultiplePendingTopicsWithSameAction {
  97. FIRMessagingPendingTopicsList *pendingTopics = [[FIRMessagingPendingTopicsList alloc] init];
  98. pendingTopics.delegate = self.notReadyDelegate;
  99. for (NSInteger i = 0; i < 10; i++) {
  100. NSString *topic = [NSString stringWithFormat:@"/topics/%ld", i];
  101. [pendingTopics addOperationForTopic:topic
  102. withAction:FIRMessagingTopicActionSubscribe
  103. completion:nil];
  104. }
  105. XCTAssertEqual(pendingTopics.numberOfBatches, 1);
  106. }
  107. - (void)testAddTopicsWithDifferentActions {
  108. FIRMessagingPendingTopicsList *pendingTopics = [[FIRMessagingPendingTopicsList alloc] init];
  109. pendingTopics.delegate = self.notReadyDelegate;
  110. [pendingTopics addOperationForTopic:@"/topics/0"
  111. withAction:FIRMessagingTopicActionSubscribe
  112. completion:nil];
  113. [pendingTopics addOperationForTopic:@"/topics/1"
  114. withAction:FIRMessagingTopicActionUnsubscribe
  115. completion:nil];
  116. [pendingTopics addOperationForTopic:@"/topics/2"
  117. withAction:FIRMessagingTopicActionSubscribe
  118. completion:nil];
  119. XCTAssertEqual(pendingTopics.numberOfBatches, 3);
  120. }
  121. - (void)testBatchSizeReductionAfterSuccessfulTopicUpdate {
  122. FIRMessagingPendingTopicsList *pendingTopics = [[FIRMessagingPendingTopicsList alloc] init];
  123. pendingTopics.delegate = self.alwaysReadyDelegate;
  124. XCTestExpectation *batchSizeReductionExpectation =
  125. [self expectationWithDescription:@"Batch size was reduced after topic suscription"];
  126. FIRMessaging_WEAKIFY(self)
  127. self.alwaysReadyDelegate.subscriptionHandler =
  128. ^(NSString *topic,
  129. FIRMessagingTopicAction action,
  130. FIRMessagingTopicOperationCompletion completion) {
  131. // Simulate that the handler is generally called asynchronously
  132. dispatch_async(dispatch_get_main_queue(), ^{
  133. FIRMessaging_STRONGIFY(self)
  134. if (action == FIRMessagingTopicActionUnsubscribe) {
  135. XCTAssertEqual(pendingTopics.numberOfBatches, 1);
  136. [batchSizeReductionExpectation fulfill];
  137. }
  138. completion(nil);
  139. });
  140. };
  141. [pendingTopics addOperationForTopic:@"/topics/0"
  142. withAction:FIRMessagingTopicActionSubscribe
  143. completion:nil];
  144. [pendingTopics addOperationForTopic:@"/topics/1"
  145. withAction:FIRMessagingTopicActionSubscribe
  146. completion:nil];
  147. [pendingTopics addOperationForTopic:@"/topics/2"
  148. withAction:FIRMessagingTopicActionSubscribe
  149. completion:nil];
  150. [pendingTopics addOperationForTopic:@"/topics/1"
  151. withAction:FIRMessagingTopicActionUnsubscribe
  152. completion:nil];
  153. [self waitForExpectationsWithTimeout:5.0 handler:nil];
  154. }
  155. - (void)testCompletionOfTopicUpdatesInSameThread {
  156. FIRMessagingPendingTopicsList *pendingTopics = [[FIRMessagingPendingTopicsList alloc] init];
  157. pendingTopics.delegate = self.alwaysReadyDelegate;
  158. XCTestExpectation *allOperationsSucceededed =
  159. [self expectationWithDescription:@"All queued operations succeeded"];
  160. self.alwaysReadyDelegate.subscriptionHandler =
  161. ^(NSString *topic,
  162. FIRMessagingTopicAction action,
  163. FIRMessagingTopicOperationCompletion completion) {
  164. // Typically, our callbacks happen asynchronously, but to ensure resilience,
  165. // call back the operation on the same thread it was called in.
  166. completion(nil);
  167. };
  168. self.alwaysReadyDelegate.updateHandler = ^{
  169. if (pendingTopics.numberOfBatches == 0) {
  170. [allOperationsSucceededed fulfill];
  171. }
  172. };
  173. [pendingTopics addOperationForTopic:@"/topics/0"
  174. withAction:FIRMessagingTopicActionSubscribe
  175. completion:nil];
  176. [pendingTopics addOperationForTopic:@"/topics/1"
  177. withAction:FIRMessagingTopicActionSubscribe
  178. completion:nil];
  179. [pendingTopics addOperationForTopic:@"/topics/2"
  180. withAction:FIRMessagingTopicActionSubscribe
  181. completion:nil];
  182. [self waitForExpectationsWithTimeout:5.0 handler:nil];
  183. }
  184. - (void)testAddingTopicToCurrentBatchWhileCurrentBatchTopicsInFlight {
  185. FIRMessagingPendingTopicsList *pendingTopics = [[FIRMessagingPendingTopicsList alloc] init];
  186. pendingTopics.delegate = self.alwaysReadyDelegate;
  187. NSString *stragglerTopic = @"/topics/straggler";
  188. XCTestExpectation *stragglerTopicWasAddedToInFlightOperations =
  189. [self expectationWithDescription:@"The topic was added to in-flight operations"];
  190. self.alwaysReadyDelegate.subscriptionHandler =
  191. ^(NSString *topic,
  192. FIRMessagingTopicAction action,
  193. FIRMessagingTopicOperationCompletion completion) {
  194. if ([topic isEqualToString:stragglerTopic]) {
  195. [stragglerTopicWasAddedToInFlightOperations fulfill];
  196. }
  197. // Add a 0.5 second delay to the completion, to give time to add a straggler before the batch
  198. // is completed
  199. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)),
  200. dispatch_get_main_queue(), ^{
  201. completion(nil);
  202. });
  203. };
  204. // This is a normal topic, which should start fairly soon, but take a while to complete
  205. [pendingTopics addOperationForTopic:@"/topics/0"
  206. withAction:FIRMessagingTopicActionSubscribe
  207. completion:nil];
  208. // While waiting for the first topic to complete, we add another topic after a slight delay
  209. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)),
  210. dispatch_get_main_queue(),
  211. ^{
  212. [pendingTopics addOperationForTopic:stragglerTopic
  213. withAction:FIRMessagingTopicActionSubscribe
  214. completion:nil];
  215. });
  216. [self waitForExpectationsWithTimeout:5.0 handler:nil];
  217. }
  218. @end