diff --git a/Sources/Auth/AuthClient.swift b/Sources/Auth/AuthClient.swift index bc467eed..5d8f8024 100644 --- a/Sources/Auth/AuthClient.swift +++ b/Sources/Auth/AuthClient.swift @@ -1,5 +1,6 @@ import ConcurrencyExtras import Foundation +import IssueReporting #if canImport(AuthenticationServices) import AuthenticationServices @@ -1410,17 +1411,21 @@ public actor AuthClient { let session = try? await session eventEmitter.emit(.initialSession, session: session, token: token) - logger?.warning( - """ - Initial session emitted after attempting to refresh the local stored session. - This is incorrect behavior and will be fixed in the next major release since it’s a breaking change. - For now, if you want to opt-in to the new behavior, add the trait `EmitLocalSessionAsInitialSession` to your Package.swift file when importing the Supabase dependency. - The new behavior ensures that the locally stored session is always emitted, regardless of its validity or expiration. - If you rely on the initial session to opt users in, you need to add an additional check for `session.isExpired` in the session. - - Check https://github.com/supabase/supabase-swift/pull/822 for more information. - """ - ) + // Properly expecting issues during tests isn't working as expected, I think because the reportIssue is usually triggered inside an unstructured Task + // because of this I'm disabling issue reporting during tests, so we can use it only for advising developers when running their applications. + if !isTesting { + reportIssue( + """ + Initial session emitted after attempting to refresh the local stored session. + This is incorrect behavior and will be fixed in the next major release since it’s a breaking change. + For now, if you want to opt-in to the new behavior, add the trait `EmitLocalSessionAsInitialSession` to your Package.swift file when importing the Supabase dependency. + The new behavior ensures that the locally stored session is always emitted, regardless of its validity or expiration. + If you rely on the initial session to opt users in, you need to add an additional check for `session.isExpired` in the session. + + Check https://github.com/supabase/supabase-swift/pull/822 for more information. + """ + ) + } #endif } diff --git a/Sources/Helpers/Codable.swift b/Sources/Helpers/Codable.swift index e6b38877..1efaa6aa 100644 --- a/Sources/Helpers/Codable.swift +++ b/Sources/Helpers/Codable.swift @@ -7,6 +7,7 @@ import ConcurrencyExtras import Foundation +import XCTestDynamicOverlay extension JSONDecoder { /// Default `JSONDecoder` for decoding types from Supabase. @@ -21,7 +22,8 @@ extension JSONDecoder { } throw DecodingError.dataCorruptedError( - in: container, debugDescription: "Invalid date format: \(string)" + in: container, + debugDescription: "Invalid date format: \(string)" ) } return decoder @@ -36,6 +38,13 @@ extension JSONEncoder { let string = date.iso8601String try container.encode(string) } + + #if DEBUG + if isTesting { + encoder.outputFormatting = [.sortedKeys] + } + #endif + return encoder } } diff --git a/Sources/Realtime/RealtimeChannelV2.swift b/Sources/Realtime/RealtimeChannelV2.swift index 0cda4efd..2be951d9 100644 --- a/Sources/Realtime/RealtimeChannelV2.swift +++ b/Sources/Realtime/RealtimeChannelV2.swift @@ -295,7 +295,7 @@ public final class RealtimeChannelV2: Sendable, RealtimeChannelProtocol { } headers[.authorization] = "Bearer \(accessToken)" - let body = try await JSONEncoder().encode( + let body = try await JSONEncoder.supabase().encode( BroadcastMessagePayload( messages: [ BroadcastMessagePayload.Message( @@ -315,7 +315,8 @@ public final class RealtimeChannelV2: Sendable, RealtimeChannelProtocol { body: body ) - let response = try await withTimeout(interval: timeout ?? socket.options.timeoutInterval) { [self] in + let response = try await withTimeout(interval: timeout ?? socket.options.timeoutInterval) { + [self] in await Result { try await socket.http.send(request) } @@ -346,11 +347,17 @@ public final class RealtimeChannelV2: Sendable, RealtimeChannelProtocol { @MainActor public func broadcast(event: String, message: JSONObject) async { if status != .subscribed { - logger?.warning( - "Realtime broadcast() is automatically falling back to REST API. " - + "This behavior will be deprecated in the future. " - + "Please use httpSend() explicitly for REST delivery." - ) + // Properly expecting issues during tests isn't working as expected, I think because the reportIssue is usually triggered inside an unstructured Task + // because of this I'm disabling issue reporting during tests, so we can use it only for advising developers when running their applications. + if !isTesting { + reportIssue( + """ + Realtime broadcast() is automatically falling back to REST API. + This behavior will be deprecated in the future. + Please use httpSend() explicitly for REST delivery. + """ + ) + } var headers: HTTPFields = [.contentType: "application/json"] if let apiKey = socket.options.apikey { @@ -366,7 +373,7 @@ public final class RealtimeChannelV2: Sendable, RealtimeChannelProtocol { url: socket.broadcastURL, method: .post, headers: headers, - body: JSONEncoder().encode( + body: JSONEncoder.supabase().encode( BroadcastMessagePayload( messages: [ BroadcastMessagePayload.Message( @@ -751,4 +758,3 @@ public final class RealtimeChannelV2: Sendable, RealtimeChannelProtocol { push?.didReceive(status: PushStatus(rawValue: status) ?? .ok) } } - diff --git a/Tests/RealtimeTests/RealtimeTests.swift b/Tests/RealtimeTests/RealtimeTests.swift index f24aec6f..5febd126 100644 --- a/Tests/RealtimeTests/RealtimeTests.swift +++ b/Tests/RealtimeTests/RealtimeTests.swift @@ -241,7 +241,7 @@ final class RealtimeTests: XCTestCase { // Wait for the timeout for rejoining. await testClock.advance(by: .seconds(timeoutInterval)) - + // Wait for the retry delay (base delay is 1.0s, but we need to account for jitter) // The retry delay is calculated as: baseDelay * pow(2, attempt-1) + jitter // For attempt 2: 1.0 * pow(2, 1) = 2.0s + jitter (up to ±25% = ±0.5s) @@ -443,7 +443,7 @@ final class RealtimeTests: XCTestCase { await testClock.advance(by: .seconds(timeoutInterval)) subscribeTask.cancel() - + do { try await subscribeTask.value XCTFail("Expected cancellation error but got success") @@ -597,26 +597,16 @@ final class RealtimeTests: XCTestCase { try await channel.broadcast(event: "test", message: ["value": 42]) let request = await http.receivedRequests.last - assertInlineSnapshot(of: request?.urlRequest, as: .raw(pretty: true)) { - """ - POST http://localhost:54321/realtime/v1/api/broadcast - Authorization: Bearer custom.access.token - Content-Type: application/json - apiKey: anon.api.key - - { - "messages" : [ - { - "event" : "test", - "payload" : { - "value" : 42 - }, - "private" : false, - "topic" : "realtime:public:messages" - } - ] - } - """ + assertInlineSnapshot(of: request?.urlRequest, as: .curl) { + #""" + curl \ + --request POST \ + --header "Authorization: Bearer custom.access.token" \ + --header "Content-Type: application/json" \ + --header "apiKey: anon.api.key" \ + --data "{\"messages\":[{\"event\":\"test\",\"payload\":{\"value\":42},\"private\":false,\"topic\":\"realtime:public:messages\"}]}" \ + "http://localhost:54321/realtime/v1/api/broadcast" + """# } }