Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[auth-swift] Fix heartbeats for SPM #12237

Merged
merged 3 commits into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading