FIRIAMFetchFlowTests.m 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  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 <XCTest/XCTest.h>
  17. #import <OCMock/OCMock.h>
  18. #import "FIRIAMAnalyticsEventLogger.h"
  19. #import "FIRIAMDisplayTriggerDefinition.h"
  20. #import "FIRIAMFetchFlow.h"
  21. #import "FIRIAMMessageContentDataWithImageURL.h"
  22. #import "FIRIAMSDKModeManager.h"
  23. @interface FIRIAMFetchFlowTests : XCTestCase
  24. @property(nonatomic) FIRIAMFetchSetting *fetchSetting;
  25. @property FIRIAMMessageClientCache *clientMessageCache;
  26. @property id<FIRIAMMessageFetcher> mockMessageFetcher;
  27. @property id<FIRIAMBookKeeper> mockBookkeeper;
  28. @property id<FIRIAMTimeFetcher> mockTimeFetcher;
  29. @property FIRIAMFetchFlow *flow;
  30. @property FIRIAMActivityLogger *activityLogger;
  31. @property FIRIAMSDKModeManager *mockSDKModeManager;
  32. @property id<FIRIAMAnalyticsEventLogger> mockAnaltycisEventLogger;
  33. // three pre-defined messages
  34. @property FIRIAMMessageDefinition *m1, *m2, *m3;
  35. @end
  36. CGFloat FETCH_MIN_INTERVALS = 1;
  37. @implementation FIRIAMFetchFlowTests
  38. - (void)setupMessageTexture {
  39. // startTime, endTime here ensures messages with them are active
  40. NSTimeInterval activeStartTime = 0;
  41. NSTimeInterval activeEndTime = [[NSDate date] timeIntervalSince1970] + 10000;
  42. FIRIAMDisplayTriggerDefinition *triggerDefinition =
  43. [[FIRIAMDisplayTriggerDefinition alloc] initWithFirebaseAnalyticEvent:@"test_event"];
  44. FIRIAMMessageContentDataWithImageURL *m1ContentData =
  45. [[FIRIAMMessageContentDataWithImageURL alloc]
  46. initWithMessageTitle:@"m1 title"
  47. messageBody:@"message body"
  48. actionButtonText:nil
  49. actionURL:[NSURL URLWithString:@"http://google.com"]
  50. imageURL:[NSURL URLWithString:@"https://unsplash.it/300/300"]
  51. usingURLSession:nil];
  52. FIRIAMRenderingEffectSetting *renderSetting1 =
  53. [FIRIAMRenderingEffectSetting getDefaultRenderingEffectSetting];
  54. renderSetting1.viewMode = FIRIAMRenderAsBannerView;
  55. FIRIAMMessageRenderData *renderData1 =
  56. [[FIRIAMMessageRenderData alloc] initWithMessageID:@"m1"
  57. messageName:@"name"
  58. contentData:m1ContentData
  59. renderingEffect:renderSetting1];
  60. self.m1 = [[FIRIAMMessageDefinition alloc] initWithRenderData:renderData1
  61. startTime:activeStartTime
  62. endTime:activeEndTime
  63. triggerDefinition:@[ triggerDefinition ]];
  64. FIRIAMMessageContentDataWithImageURL *m2ContentData =
  65. [[FIRIAMMessageContentDataWithImageURL alloc]
  66. initWithMessageTitle:@"m2 title"
  67. messageBody:@"message body"
  68. actionButtonText:nil
  69. actionURL:[NSURL URLWithString:@"http://google.com"]
  70. imageURL:[NSURL URLWithString:@"https://unsplash.it/300/400"]
  71. usingURLSession:nil];
  72. FIRIAMRenderingEffectSetting *renderSetting2 =
  73. [FIRIAMRenderingEffectSetting getDefaultRenderingEffectSetting];
  74. renderSetting2.viewMode = FIRIAMRenderAsModalView;
  75. FIRIAMMessageRenderData *renderData2 =
  76. [[FIRIAMMessageRenderData alloc] initWithMessageID:@"m2"
  77. messageName:@"name"
  78. contentData:m2ContentData
  79. renderingEffect:renderSetting2];
  80. self.m2 = [[FIRIAMMessageDefinition alloc] initWithRenderData:renderData2
  81. startTime:activeStartTime
  82. endTime:activeEndTime
  83. triggerDefinition:@[ triggerDefinition ]];
  84. FIRIAMMessageContentDataWithImageURL *m3ContentData =
  85. [[FIRIAMMessageContentDataWithImageURL alloc]
  86. initWithMessageTitle:@"m3 title"
  87. messageBody:@"message body"
  88. actionButtonText:nil
  89. actionURL:[NSURL URLWithString:@"http://google.com"]
  90. imageURL:[NSURL URLWithString:@"https://unsplash.it/400/300"]
  91. usingURLSession:nil];
  92. FIRIAMRenderingEffectSetting *renderSetting3 =
  93. [FIRIAMRenderingEffectSetting getDefaultRenderingEffectSetting];
  94. renderSetting3.viewMode = FIRIAMRenderAsImageOnlyView;
  95. FIRIAMMessageRenderData *renderData3 =
  96. [[FIRIAMMessageRenderData alloc] initWithMessageID:@"m3"
  97. messageName:@"name"
  98. contentData:m3ContentData
  99. renderingEffect:renderSetting3];
  100. self.m3 = [[FIRIAMMessageDefinition alloc] initWithRenderData:renderData3
  101. startTime:activeStartTime
  102. endTime:activeEndTime
  103. triggerDefinition:@[ triggerDefinition ]];
  104. }
  105. - (void)setUp {
  106. [super setUp];
  107. [self setupMessageTexture];
  108. self.fetchSetting = [[FIRIAMFetchSetting alloc] init];
  109. self.fetchSetting.fetchMinIntervalInMinutes = FETCH_MIN_INTERVALS;
  110. self.mockMessageFetcher = OCMProtocolMock(@protocol(FIRIAMMessageFetcher));
  111. FIRIAMFetchResponseParser *parser =
  112. [[FIRIAMFetchResponseParser alloc] initWithTimeFetcher:[[FIRIAMTimerWithNSDate alloc] init]];
  113. self.clientMessageCache = [[FIRIAMMessageClientCache alloc] initWithBookkeeper:self.mockBookkeeper
  114. usingResponseParser:parser];
  115. self.mockTimeFetcher = OCMProtocolMock(@protocol(FIRIAMTimeFetcher));
  116. self.mockBookkeeper = OCMProtocolMock(@protocol(FIRIAMBookKeeper));
  117. self.activityLogger = OCMClassMock([FIRIAMActivityLogger class]);
  118. self.mockAnaltycisEventLogger = OCMProtocolMock(@protocol(FIRIAMAnalyticsEventLogger));
  119. self.mockSDKModeManager = OCMClassMock([FIRIAMSDKModeManager class]);
  120. self.mockTimeFetcher = OCMProtocolMock(@protocol(FIRIAMTimeFetcher));
  121. self.flow = [[FIRIAMFetchFlow alloc] initWithSetting:self.fetchSetting
  122. messageCache:self.clientMessageCache
  123. messageFetcher:self.mockMessageFetcher
  124. timeFetcher:self.mockTimeFetcher
  125. bookKeeper:self.mockBookkeeper
  126. activityLogger:self.activityLogger
  127. analyticsEventLogger:self.mockAnaltycisEventLogger
  128. FIRIAMSDKModeManager:self.mockSDKModeManager];
  129. }
  130. - (void)tearDown {
  131. // Put teardown code here. This method is called after the invocation of each test method in the
  132. // class.
  133. [super tearDown];
  134. }
  135. // In happy path, the fetch is allowed and we are able to fetch two messages back
  136. - (void)testHappyPath {
  137. OCMStub([self.mockBookkeeper lastFetchTime]).andReturn(0);
  138. // Set it up so that we already have impressions for m1 and m3
  139. FIRIAMImpressionRecord *impression1 =
  140. [[FIRIAMImpressionRecord alloc] initWithMessageID:self.m1.renderData.messageID
  141. impressionTimeInSeconds:1233];
  142. FIRIAMImpressionRecord *impression2 = [[FIRIAMImpressionRecord alloc] initWithMessageID:@"m3"
  143. impressionTimeInSeconds:5678];
  144. NSArray<FIRIAMImpressionRecord *> *impressions = @[ impression1, impression2 ];
  145. OCMStub([self.mockBookkeeper getImpressions]).andReturn(impressions);
  146. NSArray<FIRIAMMessageDefinition *> *fetchedMessages = @[ self.m1, self.m2 ];
  147. // 200 seconds is larger than fetch wait time which is 100 in this setup
  148. OCMStub([self.mockBookkeeper nextFetchWaitTime]).andReturn(100);
  149. OCMStub([self.mockTimeFetcher currentTimestampInSeconds]).andReturn(200);
  150. OCMStub([self.mockSDKModeManager currentMode]).andReturn(FIRIAMSDKModeRegular);
  151. NSNumber *fetchWaitTimeFromResponse = [NSNumber numberWithInt:2000];
  152. OCMStub([self.mockMessageFetcher
  153. fetchMessagesWithImpressionList:[OCMArg any]
  154. withCompletion:([OCMArg invokeBlockWithArgs:fetchedMessages,
  155. fetchWaitTimeFromResponse,
  156. [NSNull null], [NSNull null],
  157. nil])]);
  158. [self.flow checkAndFetch];
  159. // We expect m1 and m2 to be dumped into clientMessageCache.
  160. NSArray<FIRIAMMessageDefinition *> *foundMessages = [self.clientMessageCache allRegularMessages];
  161. XCTAssertEqual(2, foundMessages.count);
  162. XCTAssertEqualObjects(foundMessages[0].renderData.messageID, self.m1.renderData.messageID);
  163. XCTAssertEqualObjects(foundMessages[1].renderData.messageID, self.m2.renderData.messageID);
  164. // Verify that we record the new fetch with bookkeeper
  165. OCMVerify([self.mockBookkeeper recordNewFetchWithFetchCount:2
  166. withTimestampInSeconds:200
  167. nextFetchWaitTime:fetchWaitTimeFromResponse]);
  168. // So we are sending the request with impression for m1 and m3 and getting back messages for m1
  169. // and m2. In here m1 is a recurring message and after the fetch, we should call
  170. // book keeper's clearImpressionsWithMessageList: method with m1 which is an intersection
  171. // between the request impression list and the response message id list. We are skipping
  172. // m2 since it's not included in the impression records sent along with the request.
  173. OCMVerify(
  174. [self.mockBookkeeper clearImpressionsWithMessageList:@[ self.m1.renderData.messageID ]]);
  175. }
  176. // No fetch is to be performed if the required fetch interval is not met
  177. - (void)testNoFetchDueToIntervalConstraint {
  178. OCMStub([self.mockSDKModeManager currentMode]).andReturn(FIRIAMSDKModeRegular);
  179. // We need to wait at least 300 seconds before making another fetch
  180. OCMStub([self.mockBookkeeper nextFetchWaitTime]).andReturn(300);
  181. // And it's only been 200 seconds since last fetch, so no fetch should happen
  182. OCMStub([self.mockTimeFetcher currentTimestampInSeconds]).andReturn(200);
  183. OCMStub([self.mockBookkeeper lastFetchTime]).andReturn(0);
  184. // We don't expect fetchMessages: for self.mockMessageFetcher to be triggred
  185. OCMReject([self.mockMessageFetcher fetchMessagesWithImpressionList:[OCMArg any]
  186. withCompletion:[OCMArg any]]);
  187. [self.flow checkAndFetch];
  188. NSArray<FIRIAMMessageDefinition *> *foundMessages = [self.clientMessageCache allRegularMessages];
  189. XCTAssertEqual(0, foundMessages.count);
  190. }
  191. // Fetch always in newly installed mode
  192. - (void)testAlwaysFetchForNewlyInstalledMode {
  193. OCMStub([self.mockBookkeeper lastFetchTime]).andReturn(0);
  194. OCMStub([self.mockSDKModeManager currentMode]).andReturn(FIRIAMSDKModeNewlyInstalled);
  195. OCMStub([self.mockMessageFetcher
  196. fetchMessagesWithImpressionList:[OCMArg any]
  197. withCompletion:([OCMArg invokeBlockWithArgs:@[ self.m1, self.m2 ],
  198. [NSNull null], [NSNull null],
  199. [NSNull null], nil])]);
  200. // 100 seconds is less than fetch wait time which is 1000 in this setup,
  201. // but since we are in newly installed mode, fetch would still happen
  202. OCMStub([self.mockBookkeeper nextFetchWaitTime]).andReturn(1000);
  203. OCMStub([self.mockTimeFetcher currentTimestampInSeconds]).andReturn(100);
  204. [self.flow checkAndFetch];
  205. // we expect m1 and m2 to be dumped into clientMessageCache
  206. NSArray<FIRIAMMessageDefinition *> *foundMessages = [self.clientMessageCache allRegularMessages];
  207. XCTAssertEqual(2, foundMessages.count);
  208. XCTAssertEqualObjects(foundMessages[0].renderData.messageID, self.m1.renderData.messageID);
  209. XCTAssertEqualObjects(foundMessages[1].renderData.messageID, self.m2.renderData.messageID);
  210. // we expect to register a fetch with sdk manager
  211. OCMVerify([self.mockSDKModeManager registerOneMoreFetch]);
  212. }
  213. // Fetch always in testing app instance mode
  214. - (void)testAlwaysFetchForTestingAppInstanceMode {
  215. OCMStub([self.mockBookkeeper lastFetchTime]).andReturn(0);
  216. OCMStub([self.mockSDKModeManager currentMode]).andReturn(FIRIAMSDKModeTesting);
  217. OCMStub([self.mockMessageFetcher
  218. fetchMessagesWithImpressionList:[OCMArg any]
  219. withCompletion:([OCMArg invokeBlockWithArgs:@[ self.m1, self.m2 ],
  220. [NSNull null], [NSNull null],
  221. [NSNull null], nil])]);
  222. // 100 seconds is less than fetch wait time which is 1000 in this setup,
  223. // but since we are in testing app instance mode, fetch would still happen
  224. OCMStub([self.mockBookkeeper nextFetchWaitTime]).andReturn(1000);
  225. OCMStub([self.mockTimeFetcher currentTimestampInSeconds]).andReturn(100);
  226. [self.flow checkAndFetch];
  227. // we expect m1 and m2 to be dumped into clientMessageCache
  228. NSArray<FIRIAMMessageDefinition *> *foundMessages = [self.clientMessageCache allRegularMessages];
  229. XCTAssertEqual(2, foundMessages.count);
  230. XCTAssertEqualObjects(foundMessages[0].renderData.messageID, self.m1.renderData.messageID);
  231. XCTAssertEqualObjects(foundMessages[1].renderData.messageID, self.m2.renderData.messageID);
  232. // we expect to register a fetch with sdk manager
  233. OCMVerify([self.mockSDKModeManager registerOneMoreFetch]);
  234. }
  235. - (void)testTurnIntoTestigModeOnSeeingTestMessage {
  236. OCMStub([self.mockBookkeeper lastFetchTime]).andReturn(0);
  237. OCMStub([self.mockSDKModeManager currentMode]).andReturn(FIRIAMSDKModeNewlyInstalled);
  238. FIRIAMMessageDefinition *testMessage =
  239. [[FIRIAMMessageDefinition alloc] initTestMessageWithRenderData:self.m2.renderData];
  240. OCMStub([self.mockMessageFetcher
  241. fetchMessagesWithImpressionList:[OCMArg any]
  242. withCompletion:([OCMArg invokeBlockWithArgs:@[ self.m1, testMessage ],
  243. [NSNull null], [NSNull null],
  244. [NSNull null], nil])]);
  245. self.fetchSetting.fetchMinIntervalInMinutes = 10; // at least 600 seconds between fetches
  246. // 100 seconds is larger than FETCH_MIN_INTERVALS minutes
  247. OCMStub([self.mockTimeFetcher currentTimestampInSeconds]).andReturn(100);
  248. [self.flow checkAndFetch];
  249. // Expecting turning sdk mode into a testing instance
  250. OCMVerify([self.mockSDKModeManager becomeTestingInstance]);
  251. }
  252. - (void)testNotTurningIntoTestingModeIfAlreadyInTestingMode {
  253. OCMStub([self.mockBookkeeper lastFetchTime]).andReturn(0);
  254. OCMStub([self.mockSDKModeManager currentMode]).andReturn(FIRIAMSDKModeTesting);
  255. FIRIAMMessageDefinition *testMessage =
  256. [[FIRIAMMessageDefinition alloc] initTestMessageWithRenderData:self.m2.renderData];
  257. OCMStub([self.mockMessageFetcher
  258. fetchMessagesWithImpressionList:[OCMArg any]
  259. withCompletion:([OCMArg invokeBlockWithArgs:@[ self.m1, testMessage ],
  260. [NSNull null], [NSNull null],
  261. [NSNull null], nil])]);
  262. self.fetchSetting.fetchMinIntervalInMinutes = 10; // at least 600 seconds between fetches
  263. OCMStub([self.mockTimeFetcher currentTimestampInSeconds]).andReturn(1000);
  264. OCMReject([self.mockSDKModeManager becomeTestingInstance]);
  265. [self.flow checkAndFetch];
  266. }
  267. @end