FIRIAMClearcutUploaderTests.m 18 KB

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