FIRIAMClearcutUploaderTests.m 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. /*
  2. * Copyright 2018 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 "FirebaseInAppMessaging/Sources/Analytics/FIRIAMClearcutHttpRequestSender.h"
  19. #import "FirebaseInAppMessaging/Sources/Analytics/FIRIAMClearcutLogStorage.h"
  20. #import "FirebaseInAppMessaging/Sources/Private/Analytics/FIRIAMClearcutUploader.h"
  21. #import "FirebaseInAppMessaging/Sources/Private/Util/FIRIAMTimeFetcher.h"
  22. @interface FIRIAMClearcutUploaderTests : XCTestCase
  23. @property(nonatomic) id<FIRIAMTimeFetcher> mockTimeFetcher;
  24. @property(nonatomic) FIRIAMClearcutHttpRequestSender *mockRequestSender;
  25. @property(nonatomic) FIRIAMClearcutLogStorage *mockLogStorage;
  26. @property(nonatomic) FIRIAMClearcutStrategy *defaultStrategy;
  27. @property(nonatomic) NSUserDefaults *mockUserDefaults;
  28. @property(nonatomic) NSString *cachePath;
  29. @end
  30. // Expose certain internal things to help with unit testing.
  31. @interface FIRIAMClearcutUploader (UnitTest)
  32. @property(nonatomic, assign) int64_t nextValidSendTimeInMills;
  33. @end
  34. @implementation FIRIAMClearcutUploaderTests
  35. // Helper function to avoid conflicts between tests with the singleton cache path.
  36. - (NSString *)generatedCachePath {
  37. // Filter out any invalid filesystem characters.
  38. NSCharacterSet *invalidCharacters = [[NSCharacterSet alphanumericCharacterSet] invertedSet];
  39. // This will result in a string with the class name, a space, and the test name. We only care
  40. // about the test name so split it into components and return the last item.
  41. NSString *friendlyTestName = [self.name stringByTrimmingCharactersInSet:invalidCharacters];
  42. NSArray<NSString *> *components = [friendlyTestName componentsSeparatedByString:@" "];
  43. NSString *testName = [components lastObject];
  44. NSString *cacheDirPath =
  45. NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
  46. return [NSString stringWithFormat:@"%@/%@", cacheDirPath, testName];
  47. }
  48. - (void)setUp {
  49. [super setUp];
  50. self.mockTimeFetcher = OCMProtocolMock(@protocol(FIRIAMTimeFetcher));
  51. self.mockRequestSender = OCMClassMock(FIRIAMClearcutHttpRequestSender.class);
  52. self.mockLogStorage = OCMClassMock(FIRIAMClearcutLogStorage.class);
  53. self.defaultStrategy = [[FIRIAMClearcutStrategy alloc] initWithMinWaitTimeInMills:1000
  54. maxWaitTimeInMills:2000
  55. failureBackoffTimeInMills:1000
  56. batchSendSize:10];
  57. self.mockUserDefaults = OCMClassMock(NSUserDefaults.class);
  58. self.cachePath = [self generatedCachePath];
  59. OCMStub([self.mockUserDefaults integerForKey:[OCMArg any]]).andReturn(0);
  60. }
  61. - (void)tearDown {
  62. [[NSFileManager defaultManager] removeItemAtPath:self.cachePath error:NULL];
  63. [super tearDown];
  64. }
  65. - (void)testUploadTriggeredWhenWaitTimeConditionSatisfied {
  66. NSTimeInterval currentMoment = 10000;
  67. OCMStub([self.mockTimeFetcher currentTimestampInSeconds]).andReturn(currentMoment);
  68. // using a real storage in this case
  69. FIRIAMClearcutLogStorage *logStorage =
  70. [[FIRIAMClearcutLogStorage alloc] initWithExpireAfterInSeconds:1000
  71. withTimeFetcher:self.mockTimeFetcher
  72. cachePath:self.cachePath];
  73. FIRIAMClearcutUploader *uploader =
  74. [[FIRIAMClearcutUploader alloc] initWithRequestSender:self.mockRequestSender
  75. timeFetcher:self.mockTimeFetcher
  76. logStorage:logStorage
  77. usingStrategy:self.defaultStrategy
  78. usingUserDefaults:self.mockUserDefaults];
  79. // Upload right away: nextValidSendTimeInMills < current time
  80. uploader.nextValidSendTimeInMills = (int64_t)(currentMoment - 1) * 1000;
  81. XCTestExpectation *expectation = [self expectationWithDescription:@"Triggers send on sender"];
  82. OCMStub([self.mockRequestSender sendClearcutHttpRequestForLogs:[OCMArg any]
  83. withCompletion:[OCMArg any]])
  84. .andDo(^(NSInvocation *invocation) {
  85. [expectation fulfill];
  86. });
  87. FIRIAMClearcutLogRecord *newRecord =
  88. [[FIRIAMClearcutLogRecord alloc] initWithExtensionJsonString:@"string"
  89. eventTimestampInSeconds:currentMoment];
  90. [uploader addNewLogRecord:newRecord];
  91. // We expect expectation to be fulfilled right away since the upload can be carried out without
  92. // delay.
  93. [self waitForExpectationsWithTimeout:1.0 handler:nil];
  94. }
  95. - (void)testUploadNotTriggeredWhenWaitTimeConditionNotSatisfied {
  96. // using a real storage in this case
  97. NSTimeInterval currentMoment = 10000;
  98. OCMStub([self.mockTimeFetcher currentTimestampInSeconds]).andReturn(currentMoment);
  99. FIRIAMClearcutLogStorage *logStorage =
  100. [[FIRIAMClearcutLogStorage alloc] initWithExpireAfterInSeconds:1000
  101. withTimeFetcher:self.mockTimeFetcher
  102. cachePath:self.cachePath];
  103. FIRIAMClearcutUploader *uploader =
  104. [[FIRIAMClearcutUploader alloc] initWithRequestSender:self.mockRequestSender
  105. timeFetcher:self.mockTimeFetcher
  106. logStorage:logStorage
  107. usingStrategy:self.defaultStrategy
  108. usingUserDefaults:self.mockUserDefaults];
  109. // Forces uploading to be at least 5 seconds later.
  110. uploader.nextValidSendTimeInMills = (int64_t)(currentMoment + 5) * 1000;
  111. XCTestExpectation *expectation = [self expectationWithDescription:@"Triggers send on sender"];
  112. FIRIAMClearcutLogRecord *newRecord =
  113. [[FIRIAMClearcutLogRecord alloc] initWithExtensionJsonString:@"string"
  114. eventTimestampInSeconds:currentMoment];
  115. __block BOOL sendingAttempted = NO;
  116. // We don't expect sendClearcutHttpRequestForLogs:withCompletion: to be triggered
  117. // after wait for 2.0 seconds below. We have a BOOL flag to be used for that kind verification
  118. // checking.
  119. OCMStub([self.mockRequestSender sendClearcutHttpRequestForLogs:[OCMArg any]
  120. withCompletion:[OCMArg any]])
  121. .andDo(^(NSInvocation *invocation) {
  122. sendingAttempted = YES;
  123. });
  124. [uploader addNewLogRecord:newRecord];
  125. // We wait for 2 seconds and we expect nothing should happen to self.mockRequestSender right after
  126. // 2 seconds: the upload will eventually be attempted in after 10 seconds based on the setup
  127. // in this unit test.
  128. double delayInSeconds = 2.0;
  129. dispatch_time_t popTime =
  130. dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
  131. dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
  132. [expectation fulfill];
  133. });
  134. // We expect expectation to be fulfilled right away since the upload can be carried out without
  135. // delay.
  136. [self waitForExpectationsWithTimeout:10.0 handler:nil];
  137. XCTAssertFalse(sendingAttempted);
  138. }
  139. - (void)testUploadBatchSizeIsBasedOnStrategySetting {
  140. int batchSendSize = 5;
  141. // using a strategy with batch send size as 5
  142. FIRIAMClearcutStrategy *strategy =
  143. [[FIRIAMClearcutStrategy alloc] initWithMinWaitTimeInMills:1000
  144. maxWaitTimeInMills:2000
  145. failureBackoffTimeInMills:1000
  146. batchSendSize:batchSendSize];
  147. // Next upload is now.
  148. NSTimeInterval currentMoment = 10000;
  149. OCMStub([self.mockTimeFetcher currentTimestampInSeconds]).andReturn(currentMoment);
  150. FIRIAMClearcutUploader *uploader =
  151. [[FIRIAMClearcutUploader alloc] initWithRequestSender:self.mockRequestSender
  152. timeFetcher:self.mockTimeFetcher
  153. logStorage:self.mockLogStorage
  154. usingStrategy:strategy
  155. usingUserDefaults:self.mockUserDefaults];
  156. uploader.nextValidSendTimeInMills = (int64_t)currentMoment * 1000;
  157. XCTestExpectation *expectation = [self expectationWithDescription:@"Triggers send on sender"];
  158. OCMExpect([self.mockLogStorage popStillValidRecordsForUpTo:batchSendSize]);
  159. FIRIAMClearcutLogRecord *newRecord =
  160. [[FIRIAMClearcutLogRecord alloc] initWithExtensionJsonString:@"string"
  161. eventTimestampInSeconds:currentMoment];
  162. [uploader addNewLogRecord:newRecord];
  163. // we wait for 2 seconds to ensure that the next send is attempted and then verify its
  164. // interacton with the underlying storage
  165. double delayInSeconds = 2.0;
  166. dispatch_time_t popTime =
  167. dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
  168. dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
  169. [expectation fulfill];
  170. });
  171. // we expect expectation to be fulfilled right away since the upload can be carried out without
  172. // delay
  173. [self waitForExpectationsWithTimeout:10.0 handler:nil];
  174. OCMVerifyAll((id)self.mockLogStorage);
  175. }
  176. - (void)testRespectingWaitTimeFromRequestSender {
  177. // The next upload is now.
  178. NSTimeInterval currentMoment = 10000;
  179. OCMStub([self.mockTimeFetcher currentTimestampInSeconds]).andReturn(currentMoment);
  180. // using a real storage in this case
  181. FIRIAMClearcutLogStorage *logStorage =
  182. [[FIRIAMClearcutLogStorage alloc] initWithExpireAfterInSeconds:1000
  183. withTimeFetcher:self.mockTimeFetcher
  184. cachePath:self.cachePath];
  185. FIRIAMClearcutUploader *uploader =
  186. [[FIRIAMClearcutUploader alloc] initWithRequestSender:self.mockRequestSender
  187. timeFetcher:self.mockTimeFetcher
  188. logStorage:logStorage
  189. usingStrategy:self.defaultStrategy
  190. usingUserDefaults:self.mockUserDefaults];
  191. uploader.nextValidSendTimeInMills = (int64_t)currentMoment * 1000;
  192. XCTestExpectation *expectation = [self expectationWithDescription:@"Triggers send on sender"];
  193. // notice that waitTime is between minWaitTimeInMills and maxWaitTimeInMills in the default
  194. // strategy
  195. NSNumber *waitTime = [NSNumber numberWithLongLong:1500];
  196. // set up request sender which triggers the callback with a wait time interval to be 1000
  197. // milliseconds
  198. OCMStub(
  199. [self.mockRequestSender
  200. sendClearcutHttpRequestForLogs:[OCMArg any]
  201. withCompletion:([OCMArg invokeBlockWithArgs:@YES, @NO, waitTime, nil])])
  202. .andDo(^(NSInvocation *invocation) {
  203. [expectation fulfill];
  204. });
  205. FIRIAMClearcutLogRecord *newRecord =
  206. [[FIRIAMClearcutLogRecord alloc] initWithExtensionJsonString:@"string"
  207. eventTimestampInSeconds:currentMoment];
  208. [uploader addNewLogRecord:newRecord];
  209. [self waitForExpectationsWithTimeout:10.0 handler:nil];
  210. // verify the update to nextValidSendTimeInMills is expected
  211. XCTAssertEqual(currentMoment * 1000 + 1500, uploader.nextValidSendTimeInMills);
  212. }
  213. - (void)disable_testWaitTimeFromRequestSenderAdjustedByMinWaitTimeInStrategy {
  214. // The next upload is now.
  215. NSTimeInterval currentMoment = 10000;
  216. OCMStub([self.mockTimeFetcher currentTimestampInSeconds]).andReturn(currentMoment);
  217. // using a real storage in this case
  218. FIRIAMClearcutLogStorage *logStorage =
  219. [[FIRIAMClearcutLogStorage alloc] initWithExpireAfterInSeconds:1000
  220. withTimeFetcher:self.mockTimeFetcher
  221. cachePath:self.cachePath];
  222. FIRIAMClearcutUploader *uploader =
  223. [[FIRIAMClearcutUploader alloc] initWithRequestSender:self.mockRequestSender
  224. timeFetcher:self.mockTimeFetcher
  225. logStorage:logStorage
  226. usingStrategy:self.defaultStrategy
  227. usingUserDefaults:self.mockUserDefaults];
  228. uploader.nextValidSendTimeInMills = (int64_t)currentMoment * 1000;
  229. XCTestExpectation *expectation = [self expectationWithDescription:@"Triggers send on sender"];
  230. // notice that waitTime is below minWaitTimeInMills in the default strategy
  231. NSNumber *waitTime =
  232. [NSNumber numberWithLongLong:self.defaultStrategy.minimalWaitTimeInMills - 200];
  233. // set up request sender which triggers the callback with a wait time interval to be 1000
  234. // milliseconds
  235. OCMStub(
  236. [self.mockRequestSender
  237. sendClearcutHttpRequestForLogs:[OCMArg any]
  238. withCompletion:([OCMArg invokeBlockWithArgs:@YES, @NO, waitTime, nil])])
  239. .andDo(^(NSInvocation *invocation) {
  240. [expectation fulfill];
  241. });
  242. FIRIAMClearcutLogRecord *newRecord =
  243. [[FIRIAMClearcutLogRecord alloc] initWithExtensionJsonString:@"string"
  244. eventTimestampInSeconds:currentMoment];
  245. [uploader addNewLogRecord:newRecord];
  246. [self waitForExpectationsWithTimeout:10.0 handler:nil];
  247. // verify the update to nextValidSendTimeInMills is expected
  248. XCTAssertEqual(currentMoment * 1000 + self.defaultStrategy.minimalWaitTimeInMills,
  249. uploader.nextValidSendTimeInMills);
  250. }
  251. - (void)testWaitTimeFromRequestSenderAdjustedByMaxWaitTimeInStrategy {
  252. // The next upload is now.
  253. NSTimeInterval currentMoment = 10000;
  254. OCMStub([self.mockTimeFetcher currentTimestampInSeconds]).andReturn(currentMoment);
  255. // using a real storage in this case
  256. FIRIAMClearcutLogStorage *logStorage =
  257. [[FIRIAMClearcutLogStorage alloc] initWithExpireAfterInSeconds:1000
  258. withTimeFetcher:self.mockTimeFetcher
  259. cachePath:self.cachePath];
  260. FIRIAMClearcutUploader *uploader =
  261. [[FIRIAMClearcutUploader alloc] initWithRequestSender:self.mockRequestSender
  262. timeFetcher:self.mockTimeFetcher
  263. logStorage:logStorage
  264. usingStrategy:self.defaultStrategy
  265. usingUserDefaults:self.mockUserDefaults];
  266. uploader.nextValidSendTimeInMills = (int64_t)currentMoment * 1000;
  267. XCTestExpectation *expectation = [self expectationWithDescription:@"Triggers send on sender"];
  268. // notice that waitTime is larger than maximumWaitTimeInMills in the default strategy
  269. NSNumber *waitTime =
  270. [NSNumber numberWithLongLong:self.defaultStrategy.maximumWaitTimeInMills + 200];
  271. // set up request sender which triggers the callback with a wait time interval to be 1000
  272. // milliseconds
  273. OCMStub(
  274. [self.mockRequestSender
  275. sendClearcutHttpRequestForLogs:[OCMArg any]
  276. withCompletion:([OCMArg invokeBlockWithArgs:@YES, @NO, waitTime, nil])])
  277. .andDo(^(NSInvocation *invocation) {
  278. [expectation fulfill];
  279. });
  280. FIRIAMClearcutLogRecord *newRecord =
  281. [[FIRIAMClearcutLogRecord alloc] initWithExtensionJsonString:@"string"
  282. eventTimestampInSeconds:currentMoment];
  283. [uploader addNewLogRecord:newRecord];
  284. [self waitForExpectationsWithTimeout:10.0 handler:nil];
  285. // verify the update to nextValidSendTimeInMills is expected
  286. XCTAssertEqual(currentMoment * 1000 + self.defaultStrategy.maximumWaitTimeInMills,
  287. uploader.nextValidSendTimeInMills);
  288. }
  289. - (void)testRepushLogsIfRequestSenderSaysSo {
  290. // The next upload is now.
  291. NSTimeInterval currentMoment = 10000;
  292. OCMStub([self.mockTimeFetcher currentTimestampInSeconds]).andReturn(currentMoment);
  293. // using a real storage in this case
  294. FIRIAMClearcutLogStorage *logStorage =
  295. [[FIRIAMClearcutLogStorage alloc] initWithExpireAfterInSeconds:1000
  296. withTimeFetcher:self.mockTimeFetcher
  297. cachePath:self.cachePath];
  298. FIRIAMClearcutUploader *uploader =
  299. [[FIRIAMClearcutUploader alloc] initWithRequestSender:self.mockRequestSender
  300. timeFetcher:self.mockTimeFetcher
  301. logStorage:logStorage
  302. usingStrategy:self.defaultStrategy
  303. usingUserDefaults:self.mockUserDefaults];
  304. uploader.nextValidSendTimeInMills = (int64_t)currentMoment * 1000;
  305. XCTestExpectation *expectation = [self expectationWithDescription:@"Triggers send on sender"];
  306. // notice that waitTime is larger than maximumWaitTimeInMills in the default strategy
  307. NSNumber *waitTime =
  308. [NSNumber numberWithLongLong:self.defaultStrategy.maximumWaitTimeInMills + 200];
  309. // Notice that it's invoking completion with falure flag and a flag to re-push those logs
  310. OCMStub(
  311. [self.mockRequestSender
  312. sendClearcutHttpRequestForLogs:[OCMArg any]
  313. withCompletion:([OCMArg invokeBlockWithArgs:@NO, @YES, waitTime, nil])])
  314. .andDo(^(NSInvocation *invocation) {
  315. [expectation fulfill];
  316. });
  317. FIRIAMClearcutLogRecord *newRecord =
  318. [[FIRIAMClearcutLogRecord alloc] initWithExtensionJsonString:@"string"
  319. eventTimestampInSeconds:currentMoment];
  320. [uploader addNewLogRecord:newRecord];
  321. [self waitForExpectationsWithTimeout:10.0 handler:nil];
  322. // we should still be able to fetch one log record from storage since it's re-pushed due
  323. // to send failure
  324. XCTAssertEqual([logStorage popStillValidRecordsForUpTo:10].count, 1);
  325. }
  326. @end