Skip to content

Commit

Permalink
[auth-swift] Fix heartbeats for SPM (#12237)
Browse files Browse the repository at this point in the history
  • Loading branch information
paulb777 authored Jan 4, 2024
1 parent e80e58e commit 20847a1
Show file tree
Hide file tree
Showing 9 changed files with 103 additions and 108 deletions.
3 changes: 1 addition & 2 deletions FirebaseAppCheck/Sources/Core/FIRHeartbeatLogger+AppCheck.m
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ @implementation FIRHeartbeatLogger (AppCheck)

- (GACAppCheckAPIRequestHook)requestHook {
return ^(NSMutableURLRequest *request) {
NSString *heartbeatsValue =
FIRHeaderValueFromHeartbeatsPayload([self flushHeartbeatsIntoPayload]);
NSString *heartbeatsValue = [self headerValue];
if (heartbeatsValue) {
[request setValue:heartbeatsValue forHTTPHeaderField:kFIRHeartbeatLoggerPayloadHeaderKey];
}
Expand Down
8 changes: 3 additions & 5 deletions FirebaseAuth/Sources/Swift/Auth/AuthDataResult.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,15 @@ import Foundation
private let kUserCodingKey = "user"
private let kCredentialCodingKey = "credential"

// TODO: All below here should be internal

/** @fn initWithUser:additionalUserInfo:
@brief Designated initializer.
@param user The signed in user reference.
@param additionalUserInfo The additional user info.
@param credential The updated OAuth credential if available.
*/
@objc public init(withUser user: User,
additionalUserInfo: AdditionalUserInfo?,
credential: OAuthCredential? = nil) {
init(withUser user: User,
additionalUserInfo: AdditionalUserInfo?,
credential: OAuthCredential? = nil) {
self.user = user
self.additionalUserInfo = additionalUserInfo
self.credential = credential
Expand Down
12 changes: 3 additions & 9 deletions FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -112,15 +112,9 @@ class AuthBackend: NSObject {
request.setValue(clientVersion, forHTTPHeaderField: "X-Client-Version")
request.setValue(Bundle.main.bundleIdentifier, forHTTPHeaderField: "X-Ios-Bundle-Identifier")
request.setValue(requestConfiguration.appID, forHTTPHeaderField: "X-Firebase-GMPID")
// TODO: Enable for SPM. Can we directly call the Swift library?
#if COCOAPODS
if let heartbeatLogger = requestConfiguration.heartbeatLogger {
request.setValue(
FIRHeaderValueFromHeartbeatsPayload(heartbeatLogger.flushHeartbeatsIntoPayload()),
forHTTPHeaderField: "X-Firebase-Client"
)
}
#endif
if let heartbeatLogger = requestConfiguration.heartbeatLogger {
request.setValue(heartbeatLogger.headerValue(), forHTTPHeaderField: "X-Firebase-Client")
}
request.httpMethod = requestConfiguration.httpMethod
let preferredLocalizations = Bundle.main.preferredLocalizations
if preferredLocalizations.count > 0 {
Expand Down
164 changes: 82 additions & 82 deletions FirebaseAuth/Tests/Unit/AuthBackendRPCImplentationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -529,66 +529,69 @@ class AuthBackendRPCImplementationTests: RPCBaseTests {
XCTAssertEqual(try XCTUnwrap(rpcResponse.receivedDictionary[kTestKey] as? String), kTestValue)
}

// TODO: enable heartbeat logger tests for SPM
#if COCOAPODS
private class FakeHeartbeatLogger: NSObject, FIRHeartbeatLoggerProtocol {
var onFlushHeartbeatsIntoPayloadHandler: (() -> _ObjC_HeartbeatsPayload)?

func log() {
// This API should not be used by the below tests because the Auth
// SDK does not log heartbeats in it's networking context.
fatalError("FakeHeartbeatLogger log should not be used in tests.")
private class FakeHeartbeatLogger: NSObject, FIRHeartbeatLoggerProtocol {
func headerValue() -> String? {
let payload = flushHeartbeatsIntoPayload()
guard !payload.isEmpty else {
return nil
}
return payload.headerValue()
}

func flushHeartbeatsIntoPayload() -> FirebaseCoreInternal._ObjC_HeartbeatsPayload {
guard let handler = onFlushHeartbeatsIntoPayloadHandler else {
fatalError("Missing Handler")
}
return handler()
}
var onFlushHeartbeatsIntoPayloadHandler: (() -> _ObjC_HeartbeatsPayload)?

func heartbeatCodeForToday() -> FIRDailyHeartbeatCode {
// This API should not be used by the below tests because the Auth
// SDK uses only the V2 heartbeat API (`flushHeartbeatsIntoPayload`) for
// getting heartbeats.
return FIRDailyHeartbeatCode.none
}
func log() {
// This API should not be used by the below tests because the Auth
// SDK does not log heartbeats in it's networking context.
fatalError("FakeHeartbeatLogger log should not be used in tests.")
}

/** @fn testRequest_IncludesHeartbeatPayload_WhenHeartbeatsNeedSending
@brief This test checks the behavior of @c postWithRequest:response:callback:
to verify that a heartbeats payload is attached as a header to an
outgoing request when there are stored heartbeats that need sending.
*/
func testRequest_IncludesHeartbeatPayload_WhenHeartbeatsNeedSending() async throws {
// Given
let fakeHeartbeatLogger = FakeHeartbeatLogger()
let requestConfiguration = AuthRequestConfiguration(apiKey: kFakeAPIKey,
appID: kFakeAppID,
heartbeatLogger: fakeHeartbeatLogger)

let request = FakeRequest(withRequestBody: [:], requestConfiguration: requestConfiguration)

// When
let nonEmptyHeartbeatsPayload = HeartbeatLoggingTestUtils.nonEmptyHeartbeatsPayload
fakeHeartbeatLogger.onFlushHeartbeatsIntoPayloadHandler = {
nonEmptyHeartbeatsPayload
func flushHeartbeatsIntoPayload() -> FirebaseCoreInternal._ObjC_HeartbeatsPayload {
guard let handler = onFlushHeartbeatsIntoPayloadHandler else {
fatalError("Missing Handler")
}
rpcIssuer.respondBlock = {
// Force return from async post
try self.rpcIssuer.respond(withJSON: [:])
}
_ = try? await rpcImplementation.call(with: request)
return handler()
}

// Then
let expectedHeader = FIRHeaderValueFromHeartbeatsPayload(
HeartbeatLoggingTestUtils.nonEmptyHeartbeatsPayload
)
let completeRequest = try XCTUnwrap(rpcIssuer.completeRequest)
let headerValue = completeRequest.value(forHTTPHeaderField: "X-Firebase-Client")
XCTAssertEqual(headerValue, expectedHeader)
func heartbeatCodeForToday() -> FIRDailyHeartbeatCode {
// This API should not be used by the below tests because the Auth
// SDK uses only the V2 heartbeat API (`flushHeartbeatsIntoPayload`) for
// getting heartbeats.
return FIRDailyHeartbeatCode.none
}
}

/** @fn testRequest_IncludesHeartbeatPayload_WhenHeartbeatsNeedSending
@brief This test checks the behavior of @c postWithRequest:response:callback:
to verify that a heartbeats payload is attached as a header to an
outgoing request when there are stored heartbeats that need sending.
*/
func testRequest_IncludesHeartbeatPayload_WhenHeartbeatsNeedSending() async throws {
// Given
let fakeHeartbeatLogger = FakeHeartbeatLogger()
let requestConfiguration = AuthRequestConfiguration(apiKey: kFakeAPIKey,
appID: kFakeAppID,
heartbeatLogger: fakeHeartbeatLogger)

let request = FakeRequest(withRequestBody: [:], requestConfiguration: requestConfiguration)

// When
let nonEmptyHeartbeatsPayload = HeartbeatLoggingTestUtils.nonEmptyHeartbeatsPayload
fakeHeartbeatLogger.onFlushHeartbeatsIntoPayloadHandler = {
nonEmptyHeartbeatsPayload
}
rpcIssuer.respondBlock = {
// Force return from async post
try self.rpcIssuer.respond(withJSON: [:])
}
#endif
_ = try? await rpcImplementation.call(with: request)

// Then
let expectedHeader = HeartbeatLoggingTestUtils.nonEmptyHeartbeatsPayload.headerValue()
let completeRequest = try XCTUnwrap(rpcIssuer.completeRequest)
let headerValue = completeRequest.value(forHTTPHeaderField: "X-Firebase-Client")
XCTAssertEqual(headerValue, expectedHeader)
}

/** @fn testRequest_IncludesAppCheckHeader
@brief This test checks the behavior of @c postWithRequest:response:callback:
Expand All @@ -614,38 +617,35 @@ class AuthBackendRPCImplementationTests: RPCBaseTests {
XCTAssertEqual(headerValue, fakeAppCheck.fakeAppCheckToken)
}

// TODO: enable for SPM
#if COCOAPODS
/** @fn testRequest_DoesNotIncludeAHeartbeatPayload_WhenNoHeartbeatsNeedSending
@brief This test checks the behavior of @c postWithRequest:response:callback:
to verify that a request header does not contain heartbeat data in the
case that there are no stored heartbeats that need sending.
*/
func testRequest_DoesNotIncludeAHeartbeatPayload_WhenNoHeartbeatsNeedSending() async throws {
// Given
let fakeHeartbeatLogger = FakeHeartbeatLogger()
let requestConfiguration = AuthRequestConfiguration(apiKey: kFakeAPIKey,
appID: kFakeAppID,
heartbeatLogger: fakeHeartbeatLogger)

let request = FakeRequest(withRequestBody: [:], requestConfiguration: requestConfiguration)

// When
let emptyHeartbeatsPayload = HeartbeatLoggingTestUtils.emptyHeartbeatsPayload
fakeHeartbeatLogger.onFlushHeartbeatsIntoPayloadHandler = {
emptyHeartbeatsPayload
}
rpcIssuer.respondBlock = {
// Force return from async post
try self.rpcIssuer.respond(withJSON: [:])
}
_ = try? await rpcImplementation.call(with: request)
/** @fn testRequest_DoesNotIncludeAHeartbeatPayload_WhenNoHeartbeatsNeedSending
@brief This test checks the behavior of @c postWithRequest:response:callback:
to verify that a request header does not contain heartbeat data in the
case that there are no stored heartbeats that need sending.
*/
func testRequest_DoesNotIncludeAHeartbeatPayload_WhenNoHeartbeatsNeedSending() async throws {
// Given
let fakeHeartbeatLogger = FakeHeartbeatLogger()
let requestConfiguration = AuthRequestConfiguration(apiKey: kFakeAPIKey,
appID: kFakeAppID,
heartbeatLogger: fakeHeartbeatLogger)

let request = FakeRequest(withRequestBody: [:], requestConfiguration: requestConfiguration)

// Then
let completeRequest = try XCTUnwrap(rpcIssuer.completeRequest)
XCTAssertNil(completeRequest.value(forHTTPHeaderField: "X-Firebase-Client"))
// When
let emptyHeartbeatsPayload = HeartbeatLoggingTestUtils.emptyHeartbeatsPayload
fakeHeartbeatLogger.onFlushHeartbeatsIntoPayloadHandler = {
emptyHeartbeatsPayload
}
#endif
rpcIssuer.respondBlock = {
// Force return from async post
try self.rpcIssuer.respond(withJSON: [:])
}
_ = try? await rpcImplementation.call(with: request)

// Then
let completeRequest = try XCTUnwrap(rpcIssuer.completeRequest)
XCTAssertNil(completeRequest.value(forHTTPHeaderField: "X-Firebase-Client"))
}

private class FakeRequest: AuthRPCRequest {
typealias Response = FakeResponse
Expand Down
4 changes: 2 additions & 2 deletions FirebaseCore/Extension/FIRHeartbeatLogger.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ typedef NS_ENUM(NSInteger, FIRDailyHeartbeatCode) {
- (void)log;

#ifndef FIREBASE_BUILD_CMAKE
/// Flushes heartbeats from storage into a structured payload of heartbeats.
- (FIRHeartbeatsPayload *)flushHeartbeatsIntoPayload;
/// Return the headerValue for the HeartbeatLogger.
- (NSString *_Nullable)headerValue;
#endif // FIREBASE_BUILD_CMAKE

/// Gets the heartbeat code for today.
Expand Down
4 changes: 4 additions & 0 deletions FirebaseCore/Sources/FIRHeartbeatLogger.m
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ - (void)log {
}

#ifndef FIREBASE_BUILD_CMAKE
- (NSString *_Nullable)headerValue {
return FIRHeaderValueFromHeartbeatsPayload([self flushHeartbeatsIntoPayload]);
}

- (FIRHeartbeatsPayload *)flushHeartbeatsIntoPayload {
FIRHeartbeatsPayload *payload = [_heartbeatController flush];
return payload;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,8 +278,7 @@ - (instancetype)initWithURLSession:(NSURLSession *)URLSession
[request setValue:authHeader forHTTPHeaderField:@"Authorization"];
}
// Heartbeat Header.
[request setValue:FIRHeaderValueFromHeartbeatsPayload(
[self.heartbeatLogger flushHeartbeatsIntoPayload])
[request setValue:[self.heartbeatLogger headerValue]
forHTTPHeaderField:kFIRInstallationsHeartbeatKey];

[additionalHeaders
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,7 @@ - (void)testGetFID {
// from the above Installations API call.
[self addTeardownBlock:^{
FBLWaitForPromisesWithTimeout(20);
XCTAssertNil(FIRHeaderValueFromHeartbeatsPayload(
[FIRApp.defaultApp.heartbeatLogger flushHeartbeatsIntoPayload]));
XCTAssertNil([FIRApp.defaultApp.heartbeatLogger headerValue]);
}];
}

Expand Down Expand Up @@ -126,8 +125,7 @@ - (void)testAuthToken {
// from the above Installations API call.
[self addTeardownBlock:^{
FBLWaitForPromisesWithTimeout(20);
XCTAssertNil(FIRHeaderValueFromHeartbeatsPayload(
[FIRApp.defaultApp.heartbeatLogger flushHeartbeatsIntoPayload]));
XCTAssertNil([FIRApp.defaultApp.heartbeatLogger headerValue]);
}];
}

Expand Down Expand Up @@ -158,8 +156,7 @@ - (void)testDeleteInstallation {
// from the above Installations API call.
[self addTeardownBlock:^{
FBLWaitForPromisesWithTimeout(20);
XCTAssertNil(FIRHeaderValueFromHeartbeatsPayload(
[FIRApp.defaultApp.heartbeatLogger flushHeartbeatsIntoPayload]));
XCTAssertNil([FIRApp.defaultApp.heartbeatLogger headerValue]);
}];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ - (void)log {
[self doesNotRecognizeSelector:_cmd];
}

- (NSString *_Nullable)headerValue {
return FIRHeaderValueFromHeartbeatsPayload([self flushHeartbeatsIntoPayload]);
}

@end

#pragma mark - FIRInstallationsAPIService + Internal
Expand Down

0 comments on commit 20847a1

Please sign in to comment.