FIRIAMMessageClientCacheTests.m 17 KB

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