FIRIAMFetchFlowTests.m 17 KB

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