FIRIAMMessageClientCacheTests.m 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  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 "FIRIAMDisplayCheckOnAnalyticEventsFlow.h"
  19. #import "FIRIAMDisplayTriggerDefinition.h"
  20. #import "FIRIAMMessageClientCache.h"
  21. #import "FIRIAMMessageContentDataWithImageURL.h"
  22. #import "FIRIAMMessageDefinition.h"
  23. #import "FIRIAMTimeFetcher.h"
  24. @interface FIRIAMMessageClientCacheTests : XCTestCase
  25. @property id<FIRIAMBookKeeper> mockBookkeeper;
  26. @property(nonatomic) FIRIAMMessageClientCache *clientCache;
  27. @end
  28. @interface FIRIAMMessageClientCache ()
  29. // for the purpose of unit testing validations
  30. @property(nonatomic) NSMutableSet<NSString *> *firebaseAnalyticEventsToWatch;
  31. @end
  32. @implementation FIRIAMMessageClientCacheTests {
  33. // some predefined message definitions that are handy for certain test cases
  34. FIRIAMMessageDefinition *m1, *m2, *m3, *m4, *m5;
  35. }
  36. - (void)setUp {
  37. [super setUp];
  38. self.mockBookkeeper = OCMProtocolMock(@protocol(FIRIAMBookKeeper));
  39. FIRIAMFetchResponseParser *parser =
  40. [[FIRIAMFetchResponseParser alloc] initWithTimeFetcher:[[FIRIAMTimerWithNSDate alloc] init]];
  41. self.clientCache = [[FIRIAMMessageClientCache alloc] initWithBookkeeper:self.mockBookkeeper
  42. usingResponseParser:parser];
  43. // startTime, endTime here ensures messages with them are active
  44. NSTimeInterval activeStartTime = 0;
  45. NSTimeInterval activeEndTime = [[NSDate date] timeIntervalSince1970] + 10000;
  46. // m2 & m 4 will be of contextual trigger
  47. FIRIAMDisplayTriggerDefinition *contextualTriggerDefinition =
  48. [[FIRIAMDisplayTriggerDefinition alloc] initWithFirebaseAnalyticEvent:@"test_event"];
  49. FIRIAMDisplayTriggerDefinition *contextualTriggerDefinition2 =
  50. [[FIRIAMDisplayTriggerDefinition alloc] initWithFirebaseAnalyticEvent:@"second_event"];
  51. // m1 and m3 will be of app open trigger
  52. FIRIAMDisplayTriggerDefinition *appOpentriggerDefinition =
  53. [[FIRIAMDisplayTriggerDefinition alloc] initForAppForegroundTrigger];
  54. FIRIAMMessageContentDataWithImageURL *msgContentData =
  55. [[FIRIAMMessageContentDataWithImageURL alloc]
  56. initWithMessageTitle:@"title"
  57. messageBody:@"message body"
  58. actionButtonText:nil
  59. secondaryActionButtonText:nil
  60. actionURL:[NSURL URLWithString:@"http://google.com"]
  61. secondaryActionURL:nil
  62. imageURL:[NSURL URLWithString:@"https://unsplash.it/300/300"]
  63. landscapeImageURL:nil
  64. usingURLSession:nil];
  65. FIRIAMRenderingEffectSetting *renderSetting =
  66. [FIRIAMRenderingEffectSetting getDefaultRenderingEffectSetting];
  67. renderSetting.viewMode = FIRIAMRenderAsBannerView;
  68. FIRIAMMessageRenderData *renderData1 =
  69. [[FIRIAMMessageRenderData alloc] initWithMessageID:@"m1"
  70. messageName:@"name"
  71. contentData:msgContentData
  72. renderingEffect:renderSetting];
  73. m1 = [[FIRIAMMessageDefinition alloc] initWithRenderData:renderData1
  74. startTime:activeStartTime
  75. endTime:activeEndTime
  76. triggerDefinition:@[ appOpentriggerDefinition ]];
  77. FIRIAMMessageRenderData *renderData2 =
  78. [[FIRIAMMessageRenderData alloc] initWithMessageID:@"m2"
  79. messageName:@"name"
  80. contentData:msgContentData
  81. renderingEffect:renderSetting];
  82. m2 = [[FIRIAMMessageDefinition alloc] initWithRenderData:renderData2
  83. startTime:activeStartTime
  84. endTime:activeEndTime
  85. triggerDefinition:@[ contextualTriggerDefinition ]];
  86. FIRIAMMessageRenderData *renderData3 =
  87. [[FIRIAMMessageRenderData alloc] initWithMessageID:@"m3"
  88. messageName:@"name"
  89. contentData:msgContentData
  90. renderingEffect:renderSetting];
  91. m3 = [[FIRIAMMessageDefinition alloc] initWithRenderData:renderData3
  92. startTime:activeStartTime
  93. endTime:activeEndTime
  94. triggerDefinition:@[ appOpentriggerDefinition ]];
  95. FIRIAMMessageRenderData *renderData4 =
  96. [[FIRIAMMessageRenderData alloc] initWithMessageID:@"m4"
  97. messageName:@"name"
  98. contentData:msgContentData
  99. renderingEffect:renderSetting];
  100. FIRIAMMessageRenderData *renderData5 =
  101. [[FIRIAMMessageRenderData alloc] initWithMessageID:@"m5"
  102. messageName:@"name"
  103. contentData:msgContentData
  104. renderingEffect:renderSetting];
  105. m4 = [[FIRIAMMessageDefinition alloc]
  106. initWithRenderData:renderData4
  107. startTime:activeStartTime
  108. endTime:activeEndTime
  109. triggerDefinition:@[ contextualTriggerDefinition, contextualTriggerDefinition2 ]];
  110. // m5 is of mixture of both app-foreground and contextual triggers
  111. m5 = [[FIRIAMMessageDefinition alloc]
  112. initWithRenderData:renderData5
  113. startTime:activeStartTime
  114. endTime:activeEndTime
  115. triggerDefinition:@[ contextualTriggerDefinition, appOpentriggerDefinition ]];
  116. }
  117. - (void)tearDown {
  118. // Put teardown code here. This method is called after the invocation of each test method in the
  119. // class.
  120. [super tearDown];
  121. }
  122. - (void)testResetMessages {
  123. // test setting a mixture of display-on-app open messages and Firebase Analytics based messages
  124. // to see if the cache will keep them correctly
  125. OCMStub([self.mockBookkeeper getImpressions]).andReturn(@[]);
  126. [self.clientCache setMessageData:@[ m1, m2, m3, m4 ]];
  127. NSArray<FIRIAMMessageDefinition *> *messages = [self.clientCache allRegularMessages];
  128. XCTAssertEqual(4, [messages count]);
  129. // m4 have two contextual events defined as triggers
  130. XCTAssertEqual(2, [self.clientCache.firebaseAnalyticEventsToWatch count]);
  131. XCTAssert([self.clientCache.firebaseAnalyticEventsToWatch containsObject:@"test_event"]);
  132. XCTAssert([self.clientCache.firebaseAnalyticEventsToWatch containsObject:@"second_event"]);
  133. }
  134. - (void)testResetMessagesWithImpressionsData {
  135. // test setting a mixture of display-on-app open messages and Firebase Analytics based messages
  136. // to see if the cache will keep them correctly
  137. NSArray<NSString *> *impressionList = @[ @"m1", @"m2" ];
  138. OCMStub([self.mockBookkeeper getMessageIDsFromImpressions]).andReturn(impressionList);
  139. [self.clientCache setMessageData:@[ m1, m2, m3, m4 ]];
  140. // m1 and m2 should have been filtered out
  141. NSArray<FIRIAMMessageDefinition *> *messages = [self.clientCache allRegularMessages];
  142. XCTAssertEqual(2, messages.count);
  143. // m4 have two contextual events defined as triggers
  144. XCTAssertEqual(2, self.clientCache.firebaseAnalyticEventsToWatch.count);
  145. XCTAssert([self.clientCache.firebaseAnalyticEventsToWatch containsObject:@"test_event"]);
  146. XCTAssert([self.clientCache.firebaseAnalyticEventsToWatch containsObject:@"second_event"]);
  147. }
  148. - (void)testNextOnAppOpenDisplayMsg_ok {
  149. OCMStub([self.mockBookkeeper getImpressions]).andReturn(@[]);
  150. // m1 and m3 are messages rendered on app open
  151. [self.clientCache setMessageData:@[ m1, m2, m3, m4 ]];
  152. FIRIAMMessageDefinition *nextMsgOnAppOpen = [self.clientCache nextOnAppOpenDisplayMsg];
  153. XCTAssertEqual(@"m1", nextMsgOnAppOpen.renderData.messageID);
  154. // remove m1
  155. [self.clientCache removeMessageWithId:@"m1"];
  156. // read m2 and remove it
  157. nextMsgOnAppOpen = [self.clientCache nextOnAppOpenDisplayMsg];
  158. XCTAssertEqual(@"m3", nextMsgOnAppOpen.renderData.messageID);
  159. [self.clientCache removeMessageWithId:@"m3"];
  160. // no more message for display on app open
  161. nextMsgOnAppOpen = [self.clientCache nextOnAppOpenDisplayMsg];
  162. XCTAssertNil(nextMsgOnAppOpen);
  163. }
  164. - (void)testNextOnFirebaseAnalyticsEventDisplayMsg_ok {
  165. OCMStub([self.mockBookkeeper getImpressions]).andReturn(@[]);
  166. // m2 and m4 are messages rendered on 'app open'test_event' Firebase Analytics event
  167. [self.clientCache setMessageData:@[ m1, m2, m3, m4 ]];
  168. FIRIAMMessageDefinition *nextMsgOnFIREvent =
  169. [self.clientCache nextOnFirebaseAnalyticEventDisplayMsg:@"test_event"];
  170. XCTAssertEqual(@"m2", nextMsgOnFIREvent.renderData.messageID);
  171. // remove m2
  172. [self.clientCache removeMessageWithId:@"m2"];
  173. // verify that the watch set is empty after draining all the messages
  174. XCTAssertTrue([self.clientCache.firebaseAnalyticEventsToWatch containsObject:@"test_event"]);
  175. // read m4 and remove it
  176. nextMsgOnFIREvent = [self.clientCache nextOnFirebaseAnalyticEventDisplayMsg:@"test_event"];
  177. XCTAssertEqual(@"m4", nextMsgOnFIREvent.renderData.messageID);
  178. // remove m4
  179. [self.clientCache removeMessageWithId:@"m4"];
  180. // no more message for display on Firebase Analytics event
  181. nextMsgOnFIREvent = [self.clientCache nextOnFirebaseAnalyticEventDisplayMsg:@"test_event"];
  182. XCTAssertNil(nextMsgOnFIREvent);
  183. // verify that the watch set is empty after draining all the messages
  184. XCTAssertEqual(0, self.clientCache.firebaseAnalyticEventsToWatch.count);
  185. }
  186. - (void)testNextOnFirebaseAnalyticsEventDisplayMsgEventNameMustMatch_ok {
  187. OCMStub([self.mockBookkeeper getImpressions]).andReturn(@[]);
  188. // m2 and m4 are messages rendered on 'app open'test_event' Firebase Analytics event
  189. [self.clientCache setMessageData:@[ m1, m2, m3, m4 ]];
  190. FIRIAMMessageDefinition *nextMsgOnFIREvent =
  191. [self.clientCache nextOnFirebaseAnalyticEventDisplayMsg:@"different_event"];
  192. XCTAssertNil(nextMsgOnFIREvent);
  193. }
  194. - (void)testNextOnFirebaseAnalyticsEventDisplayMsgEventNameCanMatchAny_ok {
  195. OCMStub([self.mockBookkeeper getImpressions]).andReturn(@[]);
  196. // m4 are messages of multiple contextual triggers, one of which is for event
  197. // 'second_event'
  198. [self.clientCache setMessageData:@[ m1, m2, m3, m4 ]];
  199. FIRIAMMessageDefinition *nextMsgOnFIREvent =
  200. [self.clientCache nextOnFirebaseAnalyticEventDisplayMsg:@"second_event"];
  201. XCTAssertNotNil(nextMsgOnFIREvent);
  202. }
  203. - (void)testMessageCanHaveMixedTypeOfTriggers_ok {
  204. OCMStub([self.mockBookkeeper getImpressions]).andReturn(@[]);
  205. [self.clientCache setMessageData:@[ m5 ]];
  206. FIRIAMMessageDefinition *nextMsgOnFIREvent =
  207. [self.clientCache nextOnFirebaseAnalyticEventDisplayMsg:@"test_event"];
  208. XCTAssertNotNil(nextMsgOnFIREvent);
  209. // in the meanwhile, retrieving an app-foreground message should be successful
  210. FIRIAMMessageDefinition *nextMsgOnAppForeground = [self.clientCache nextOnAppOpenDisplayMsg];
  211. XCTAssertNotNil(nextMsgOnAppForeground);
  212. }
  213. - (void)testNextOnFirebaseAnalyticsEventDisplayMsg_handleStartEndTimeCorrectly {
  214. OCMStub([self.mockBookkeeper getImpressions]).andReturn(@[]);
  215. FIRIAMDisplayTriggerDefinition *appOpentriggerDefinition =
  216. [[FIRIAMDisplayTriggerDefinition alloc] initForAppForegroundTrigger];
  217. FIRIAMMessageContentDataWithImageURL *msgContentData =
  218. [[FIRIAMMessageContentDataWithImageURL alloc]
  219. initWithMessageTitle:@"title"
  220. messageBody:@"message body"
  221. actionButtonText:nil
  222. secondaryActionButtonText:nil
  223. actionURL:[NSURL URLWithString:@"http://google.com"]
  224. secondaryActionURL:nil
  225. imageURL:[NSURL URLWithString:@"https://unsplash.it/300/300"]
  226. landscapeImageURL:nil
  227. usingURLSession:nil];
  228. FIRIAMRenderingEffectSetting *renderSetting =
  229. [FIRIAMRenderingEffectSetting getDefaultRenderingEffectSetting];
  230. renderSetting.viewMode = FIRIAMRenderAsBannerView;
  231. FIRIAMMessageRenderData *renderData1 =
  232. [[FIRIAMMessageRenderData alloc] initWithMessageID:@"m1"
  233. messageName:@"name"
  234. contentData:msgContentData
  235. renderingEffect:renderSetting];
  236. // m1 has not started yet
  237. FIRIAMMessageDefinition *unstartedMessage = [[FIRIAMMessageDefinition alloc]
  238. initWithRenderData:renderData1
  239. startTime:[[NSDate date] timeIntervalSince1970] + 10000
  240. endTime:[[NSDate date] timeIntervalSince1970] + 20000
  241. triggerDefinition:@[ appOpentriggerDefinition ]];
  242. FIRIAMMessageRenderData *renderData2 =
  243. [[FIRIAMMessageRenderData alloc] initWithMessageID:@"m2"
  244. messageName:@"name"
  245. contentData:msgContentData
  246. renderingEffect:renderSetting];
  247. // m2 has ended
  248. FIRIAMMessageDefinition *endedMessage = [[FIRIAMMessageDefinition alloc]
  249. initWithRenderData:renderData2
  250. startTime:[[NSDate date] timeIntervalSince1970] - 20000
  251. endTime:[[NSDate date] timeIntervalSince1970] - 10000
  252. triggerDefinition:@[ appOpentriggerDefinition ]];
  253. // m3, m4 are campaigns with good start/end time
  254. [self.clientCache setMessageData:@[ unstartedMessage, endedMessage, m3, m4 ]];
  255. FIRIAMMessageDefinition *nextMsgOnAppOpen = [self.clientCache nextOnAppOpenDisplayMsg];
  256. FIRIAMMessageDefinition *nextMsgOnFIREvent =
  257. [self.clientCache nextOnFirebaseAnalyticEventDisplayMsg:@"test_event"];
  258. XCTAssertEqual(nextMsgOnAppOpen.renderData.messageID, @"m3");
  259. XCTAssertEqual(nextMsgOnFIREvent.renderData.messageID, @"m4");
  260. // no more on app open display message
  261. [self.clientCache removeMessageWithId:@"m3"];
  262. nextMsgOnAppOpen = [self.clientCache nextOnAppOpenDisplayMsg];
  263. XCTAssertNil(nextMsgOnAppOpen);
  264. }
  265. - (void)testCallingStartAnalyticsEventListenFlow_ok {
  266. OCMStub([self.mockBookkeeper getImpressions]).andReturn(@[]);
  267. FIRIAMDisplayCheckOnAnalyticEventsFlow *mockAnalyticsEventFlow =
  268. OCMClassMock(FIRIAMDisplayCheckOnAnalyticEventsFlow.class);
  269. self.clientCache.analycisEventDislayCheckFlow = mockAnalyticsEventFlow;
  270. // m2 and m4 are messages rendered on 'test_event' Firebase Analytics event
  271. // so we espect the analytics event listening flow to be started
  272. OCMExpect([mockAnalyticsEventFlow start]);
  273. [self.clientCache setMessageData:@[ m1, m2, m3, m4 ]];
  274. OCMVerifyAll((id)mockAnalyticsEventFlow);
  275. }
  276. - (void)testCallingStopAnalyticsEventListenFlow_ok {
  277. OCMStub([self.mockBookkeeper getImpressions]).andReturn(@[]);
  278. FIRIAMDisplayCheckOnAnalyticEventsFlow *mockAnalyticsEventFlow =
  279. OCMClassMock(FIRIAMDisplayCheckOnAnalyticEventsFlow.class);
  280. self.clientCache.analycisEventDislayCheckFlow = mockAnalyticsEventFlow;
  281. // m1 and m3 are messages rendered on app foreground triggers
  282. OCMExpect([mockAnalyticsEventFlow stop]);
  283. [self.clientCache setMessageData:@[ m1, m3 ]];
  284. OCMVerifyAll((id)mockAnalyticsEventFlow);
  285. }
  286. - (void)testCallingStartAndThenStopAnalyticsEventListenFlow_ok {
  287. OCMStub([self.mockBookkeeper getImpressions]).andReturn(@[]);
  288. FIRIAMDisplayCheckOnAnalyticEventsFlow *mockAnalyticsEventFlow =
  289. OCMClassMock(FIRIAMDisplayCheckOnAnalyticEventsFlow.class);
  290. self.clientCache.analycisEventDislayCheckFlow = mockAnalyticsEventFlow;
  291. // start is triggered on the setMessageData: call
  292. OCMExpect([mockAnalyticsEventFlow start]);
  293. // stop is triggered on removeMessageWithId: call since m2 is the only message
  294. // using contextual triggers
  295. OCMExpect([mockAnalyticsEventFlow stop]);
  296. [self.clientCache setMessageData:@[ m1, m2, m3 ]];
  297. [self.clientCache removeMessageWithId:m2.renderData.messageID];
  298. OCMVerifyAll((id)mockAnalyticsEventFlow);
  299. }
  300. - (void)testFetchTestMessageFirstOnNextOnAppOpenDisplayMsg_ok {
  301. OCMStub([self.mockBookkeeper getImpressions]).andReturn(@[]);
  302. FIRIAMMessageDefinition *testMessage =
  303. [[FIRIAMMessageDefinition alloc] initTestMessageWithRenderData:m2.renderData];
  304. // m1 and m3 are messages rendered on app open
  305. [self.clientCache setMessageData:@[ m1, m2, testMessage, m3, m4 ]];
  306. // we are fetching test message back
  307. FIRIAMMessageDefinition *nextMsgOnAppOpen = [self.clientCache nextOnAppOpenDisplayMsg];
  308. XCTAssertEqual(testMessage.renderData.messageID, nextMsgOnAppOpen.renderData.messageID);
  309. // we still have 4 regular messages after the first fetch
  310. XCTAssertEqual(4, self.clientCache.allRegularMessages.count);
  311. }
  312. @end