FIRCoreDiagnosticsTest.m 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. /*
  2. * Copyright 2019 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. #if TARGET_OS_IOS || TARGET_OS_TV
  18. #import <UIKit/UIKit.h>
  19. #endif // TARGET_OS_IOS || TARGET_OS_TV
  20. #import <GoogleDataTransport/GoogleDataTransport.h>
  21. #import <GoogleUtilities/GULAppEnvironmentUtil.h>
  22. #import <GoogleUtilities/GULHeartbeatDateStorage.h>
  23. #import <GoogleUtilities/GULUserDefaults.h>
  24. #import <OCMock/OCMock.h>
  25. #import <nanopb/pb_decode.h>
  26. #import <nanopb/pb_encode.h>
  27. #import "Interop/CoreDiagnostics/Public/FIRCoreDiagnosticsData.h"
  28. #import "Interop/CoreDiagnostics/Public/FIRCoreDiagnosticsInterop.h"
  29. #import "Firebase/CoreDiagnostics/FIRCDLibrary/Protogen/nanopb/firebasecore.nanopb.h"
  30. extern NSString *const kFIRLastCheckinDateKey;
  31. static NSString *const kGoogleAppID = @"1:123:ios:123abc";
  32. static NSString *const kBundleID = @"com.google.FirebaseSDKTests";
  33. static NSString *const kLibraryVersionID = @"1.2.3";
  34. static NSString *const kFIRCoreDiagnosticsHeartbeatTag = @"FIRCoreDiagnostics";
  35. #pragma mark - Testing interfaces
  36. @interface FIRCoreDiagnostics : NSObject
  37. // Initialization.
  38. + (instancetype)sharedInstance;
  39. - (instancetype)initWithTransport:(GDTCORTransport *)transport
  40. heartbeatDateStorage:(GULHeartbeatDateStorage *)heartbeatDateStorage;
  41. // Properties.
  42. @property(nonatomic, readonly) dispatch_queue_t diagnosticsQueue;
  43. @property(nonatomic, readonly) GDTCORTransport *transport;
  44. @property(nonatomic, readonly) GULHeartbeatDateStorage *heartbeatDateStorage;
  45. // Install string helpers.
  46. + (NSString *)installString;
  47. + (BOOL)writeString:(NSString *)string toURL:(NSURL *)filePathURL;
  48. + (NSString *)stringAtURL:(NSURL *)filePathURL;
  49. // Metadata helpers.
  50. + (NSString *)deviceModel;
  51. // nanopb helper functions.
  52. extern pb_bytes_array_t *FIREncodeString(NSString *string);
  53. extern pb_bytes_array_t *FIREncodeData(NSData *data);
  54. extern logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType FIRMapFromServiceStringToTypeEnum(
  55. NSString *serviceString);
  56. // Proto population functions.
  57. extern void FIRPopulateProtoWithInfoFromUserInfoParams(
  58. logs_proto_mobilesdk_ios_ICoreConfiguration *config,
  59. NSDictionary<NSString *, id> *diagnosticObjects);
  60. extern void FIRPopulateProtoWithCommonInfoFromApp(
  61. logs_proto_mobilesdk_ios_ICoreConfiguration *config,
  62. NSDictionary<NSString *, id> *diagnosticObjects);
  63. extern void FIRPopulateProtoWithInstalledServices(
  64. logs_proto_mobilesdk_ios_ICoreConfiguration *config);
  65. extern void FIRPopulateProtoWithNumberOfLinkedFrameworks(
  66. logs_proto_mobilesdk_ios_ICoreConfiguration *config);
  67. extern void FIRPopulateProtoWithInfoPlistValues(
  68. logs_proto_mobilesdk_ios_ICoreConfiguration *config);
  69. // FIRCoreDiagnosticsInterop.
  70. + (void)sendDiagnosticsData:(nonnull id<FIRCoreDiagnosticsData>)diagnosticsData;
  71. - (void)sendDiagnosticsData:(nonnull id<FIRCoreDiagnosticsData>)diagnosticsData;
  72. @end
  73. #pragma mark - Testing classes
  74. @interface FIRCoreDiagnosticsTestData : NSObject <FIRCoreDiagnosticsData>
  75. @end
  76. @implementation FIRCoreDiagnosticsTestData
  77. @synthesize diagnosticObjects = _diagnosticObjects;
  78. - (instancetype)init {
  79. self = [super init];
  80. if (self) {
  81. _diagnosticObjects =
  82. @{kFIRCDGoogleAppIDKey : kGoogleAppID, @"BUNDLE_ID" : kBundleID, kFIRCDllAppsCountKey : @1};
  83. }
  84. return self;
  85. }
  86. @end
  87. @interface FIRCoreDiagnosticsLog : NSObject <GDTCOREventDataObject>
  88. @property(nonatomic) logs_proto_mobilesdk_ios_ICoreConfiguration config;
  89. - (instancetype)initWithConfig:(logs_proto_mobilesdk_ios_ICoreConfiguration)config;
  90. @end
  91. #pragma mark - Tests
  92. @interface FIRCoreDiagnosticsTest : XCTestCase
  93. @property(nonatomic) id optionsInstanceMock;
  94. @property(nonatomic) NSDictionary<NSString *, id> *expectedUserInfo;
  95. @property(nonatomic) FIRCoreDiagnostics *diagnostics;
  96. @property(nonatomic) id mockDateStorage;
  97. @property(nonatomic) id mockTransport;
  98. @end
  99. @implementation FIRCoreDiagnosticsTest
  100. - (void)setUp {
  101. [super setUp];
  102. self.mockTransport = OCMClassMock([GDTCORTransport class]);
  103. OCMStub([self.mockTransport eventForTransport])
  104. .andReturn([[GDTCOREvent alloc] initWithMappingID:@"111" target:2]);
  105. self.mockDateStorage = OCMClassMock([GULHeartbeatDateStorage class]);
  106. self.diagnostics = [[FIRCoreDiagnostics alloc] initWithTransport:self.mockTransport
  107. heartbeatDateStorage:self.mockDateStorage];
  108. }
  109. - (void)tearDown {
  110. self.diagnostics = nil;
  111. self.mockTransport = nil;
  112. self.mockDateStorage = nil;
  113. [super tearDown];
  114. }
  115. /** Tests populating the proto correctly. */
  116. - (void)testProtoPopulation {
  117. logs_proto_mobilesdk_ios_ICoreConfiguration icoreConfiguration =
  118. logs_proto_mobilesdk_ios_ICoreConfiguration_init_default;
  119. FIRPopulateProtoWithCommonInfoFromApp(&icoreConfiguration, @{
  120. kFIRCDllAppsCountKey : @1,
  121. kFIRCDGoogleAppIDKey : kGoogleAppID,
  122. kFIRCDBundleIDKey : kBundleID,
  123. kFIRCDLibraryVersionIDKey : kLibraryVersionID,
  124. kFIRCDUsingOptionsFromDefaultPlistKey : @YES
  125. });
  126. icoreConfiguration.using_gdt = 1;
  127. icoreConfiguration.has_using_gdt = 1;
  128. FIRPopulateProtoWithInfoPlistValues(&icoreConfiguration);
  129. icoreConfiguration.configuration_type =
  130. logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_CORE;
  131. logs_proto_mobilesdk_ios_ICoreConfiguration icoreExpectedConfiguration =
  132. logs_proto_mobilesdk_ios_ICoreConfiguration_init_default;
  133. [self populateProto:&icoreExpectedConfiguration];
  134. FIRCoreDiagnosticsLog *log = [[FIRCoreDiagnosticsLog alloc] initWithConfig:icoreConfiguration];
  135. FIRCoreDiagnosticsLog *expectedLog =
  136. [[FIRCoreDiagnosticsLog alloc] initWithConfig:icoreExpectedConfiguration];
  137. XCTAssert([[log transportBytes] isEqualToData:[expectedLog transportBytes]]);
  138. // A pb_release here should not be necessary here, as FIRCoreDiagnosticsLog should do it.
  139. }
  140. // Populates the ICoreConfiguration proto.
  141. - (void)populateProto:(logs_proto_mobilesdk_ios_ICoreConfiguration *)config {
  142. NSDictionary<NSString *, id> *info = [[NSBundle mainBundle] infoDictionary];
  143. NSString *xcodeVersion = info[@"DTXcodeBuild"] ?: @"";
  144. NSString *sdkVersion = info[@"DTSDKBuild"] ?: @"";
  145. NSString *combinedVersions = [NSString stringWithFormat:@"%@-%@", xcodeVersion, sdkVersion];
  146. config->using_gdt = 1;
  147. config->has_using_gdt = 1;
  148. config->configuration_type = logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_CORE;
  149. config->icore_version = FIREncodeString(kLibraryVersionID);
  150. config->pod_name = logs_proto_mobilesdk_ios_ICoreConfiguration_PodName_FIREBASE;
  151. config->has_pod_name = 1;
  152. config->app_id = FIREncodeString(kGoogleAppID);
  153. config->bundle_id = FIREncodeString(kBundleID);
  154. config->device_model = FIREncodeString([GULAppEnvironmentUtil deviceModel]);
  155. config->os_version = FIREncodeString([GULAppEnvironmentUtil systemVersion]);
  156. config->app_count = 1;
  157. config->has_app_count = 1;
  158. config->use_default_app = 1;
  159. config->has_use_default_app = 1;
  160. config->has_dynamic_framework_count = 0; // Removed from payload.
  161. config->apple_framework_version = FIREncodeString(combinedVersions);
  162. NSString *minVersion = [[NSBundle mainBundle] infoDictionary][@"MinimumOSVersion"];
  163. if (minVersion) {
  164. config->min_supported_ios_version = FIREncodeString(minVersion);
  165. }
  166. config->using_zip_file = 0;
  167. config->has_using_zip_file = 1;
  168. config->deployment_type = logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_COCOAPODS;
  169. config->has_deployment_type = 1;
  170. config->deployed_in_app_store = 0;
  171. config->has_deployed_in_app_store = 1;
  172. config->swizzling_enabled = 1;
  173. config->has_swizzling_enabled = 1;
  174. }
  175. #pragma mark - Heartbeats
  176. - (void)testHeartbeatNotSentTheSameDay {
  177. NSCalendar *calendar = [NSCalendar currentCalendar];
  178. NSCalendarUnit unitFlags = NSCalendarUnitDay | NSCalendarUnitYear | NSCalendarUnitMonth;
  179. NSDateComponents *dateComponents = [calendar components:unitFlags fromDate:[NSDate date]];
  180. // Verify start of the day
  181. NSDate *startOfTheDay = [calendar dateFromComponents:dateComponents];
  182. OCMExpect([self.mockDateStorage heartbeatDateForTag:kFIRCoreDiagnosticsHeartbeatTag])
  183. .andReturn(startOfTheDay);
  184. OCMReject([self.mockDateStorage setHearbeatDate:[self OCMArgToCheckDateEqualTo:[OCMArg any]]
  185. forTag:kFIRCoreDiagnosticsHeartbeatTag]);
  186. [self assertEventSentWithHeartbeat:NO];
  187. // Verify middle of the day
  188. dateComponents.hour = 12;
  189. NSDate *middleOfTheDay = [calendar dateFromComponents:dateComponents];
  190. OCMExpect([self.mockDateStorage heartbeatDateForTag:kFIRCoreDiagnosticsHeartbeatTag])
  191. .andReturn(middleOfTheDay);
  192. OCMReject([self.mockDateStorage setHearbeatDate:[self OCMArgToCheckDateEqualTo:[OCMArg any]]
  193. forTag:kFIRCoreDiagnosticsHeartbeatTag]);
  194. [self assertEventSentWithHeartbeat:NO];
  195. // Verify end of the day
  196. dateComponents.hour = 0;
  197. dateComponents.day += 1;
  198. NSDate *startOfNextDay = [calendar dateFromComponents:dateComponents];
  199. NSDate *endOfTheDay = [startOfNextDay dateByAddingTimeInterval:-1];
  200. OCMExpect([self.mockDateStorage heartbeatDateForTag:kFIRCoreDiagnosticsHeartbeatTag])
  201. .andReturn(endOfTheDay);
  202. OCMReject([self.mockDateStorage setHearbeatDate:[self OCMArgToCheckDateEqualTo:[OCMArg any]]
  203. forTag:kFIRCoreDiagnosticsHeartbeatTag]);
  204. [self assertEventSentWithHeartbeat:NO];
  205. }
  206. - (void)testHeartbeatSentNoPreviousCheckin {
  207. OCMExpect([self.mockDateStorage heartbeatDateForTag:kFIRCoreDiagnosticsHeartbeatTag])
  208. .andReturn(nil);
  209. OCMExpect([self.mockDateStorage setHearbeatDate:[self OCMArgToCheckDateEqualTo:[NSDate date]]
  210. forTag:kFIRCoreDiagnosticsHeartbeatTag]);
  211. [self assertEventSentWithHeartbeat:YES];
  212. }
  213. - (void)testHeartbeatSentNextDayDefaultApp {
  214. NSDate *startOfToday = [[NSCalendar currentCalendar] startOfDayForDate:[NSDate date]];
  215. NSDate *endOfYesterday = [startOfToday dateByAddingTimeInterval:-1];
  216. OCMExpect([self.mockDateStorage heartbeatDateForTag:kFIRCoreDiagnosticsHeartbeatTag])
  217. .andReturn(endOfYesterday);
  218. OCMExpect([self.mockDateStorage setHearbeatDate:[self OCMArgToCheckDateEqualTo:[NSDate date]]
  219. forTag:kFIRCoreDiagnosticsHeartbeatTag]);
  220. [self assertEventSentWithHeartbeat:YES];
  221. }
  222. #pragma mark - Singleton
  223. - (void)testSharedInstanceDateStorageProperlyInitialized {
  224. FIRCoreDiagnostics *sharedInstance = [FIRCoreDiagnostics sharedInstance];
  225. XCTAssertNotNil(sharedInstance.heartbeatDateStorage);
  226. XCTAssert([sharedInstance.heartbeatDateStorage isKindOfClass:[GULHeartbeatDateStorage class]]);
  227. NSDate *date = [NSDate date];
  228. XCTAssertTrue([sharedInstance.heartbeatDateStorage
  229. setHearbeatDate:date
  230. forTag:kFIRCoreDiagnosticsHeartbeatTag]);
  231. XCTAssertEqualObjects(
  232. [sharedInstance.heartbeatDateStorage heartbeatDateForTag:kFIRCoreDiagnosticsHeartbeatTag],
  233. date);
  234. }
  235. #pragma mark - Helpers
  236. - (void)assertEventSentWithHeartbeat:(BOOL)isHeartbeat {
  237. [self expectEventToBeSentToTransportWithHeartbeat:isHeartbeat];
  238. [self.diagnostics sendDiagnosticsData:[[FIRCoreDiagnosticsTestData alloc] init]];
  239. OCMVerifyAllWithDelay(self.mockTransport, 0.5);
  240. OCMVerifyAllWithDelay(self.mockDateStorage, 0.5);
  241. }
  242. - (void)expectEventToBeSentToTransportWithHeartbeat:(BOOL)isHeartbeat {
  243. id eventValidation = [OCMArg checkWithBlock:^BOOL(GDTCOREvent *obj) {
  244. XCTAssert([obj isKindOfClass:[GDTCOREvent class]]);
  245. FIRCoreDiagnosticsLog *dataObject = obj.dataObject;
  246. XCTAssert([dataObject isKindOfClass:[FIRCoreDiagnosticsLog class]]);
  247. BOOL isSentEventHeartbeat =
  248. dataObject.config.sdk_name == logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ICORE;
  249. isSentEventHeartbeat = isSentEventHeartbeat && dataObject.config.has_sdk_name;
  250. XCTAssertEqual(isSentEventHeartbeat, isHeartbeat);
  251. return YES;
  252. }];
  253. OCMExpect([self.mockTransport sendTelemetryEvent:eventValidation]);
  254. }
  255. - (BOOL)isDate:(NSDate *)date1 approximatelyEqual:(NSDate *)date2 {
  256. NSTimeInterval precision = 10;
  257. NSTimeInterval diff = ABS([date1 timeIntervalSinceDate:date2]);
  258. return diff <= precision;
  259. }
  260. - (id)OCMArgToCheckDateEqualTo:(NSDate *)date {
  261. return [OCMArg checkWithBlock:^BOOL(id obj) {
  262. XCTAssert([obj isKindOfClass:[NSDate class]], "%@", self.name);
  263. return [self isDate:obj approximatelyEqual:date];
  264. }];
  265. }
  266. @end