Skip to content

Commit e2431b9

Browse files
authored
test(auth): refactor auth tests (#357)
* test: refactor auth tests * Fix build on linux * make session refresher a struct * simplify dependencies * Comment out flaky test
1 parent 98c9815 commit e2431b9

22 files changed

+424
-722
lines changed

Sources/Auth/AuthAdmin.swift

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,9 @@
88
import _Helpers
99
import Foundation
1010

11-
public actor AuthAdmin {
12-
@Dependency(\.configuration)
13-
private var configuration: AuthClient.Configuration
14-
15-
@Dependency(\.api)
16-
private var api: APIClient
11+
public struct AuthAdmin: Sendable {
12+
var api: APIClient { Current.api }
13+
var encoder: JSONEncoder { Current.encoder }
1714

1815
/// Delete a user. Requires `service_role` key.
1916
/// - Parameter id: The id of the user you want to delete.
@@ -27,7 +24,9 @@ public actor AuthAdmin {
2724
Request(
2825
path: "/admin/users/\(id)",
2926
method: .delete,
30-
body: configuration.encoder.encode(DeleteUserRequest(shouldSoftDelete: shouldSoftDelete))
27+
body: encoder.encode(
28+
DeleteUserRequest(shouldSoftDelete: shouldSoftDelete)
29+
)
3130
)
3231
)
3332
}

Sources/Auth/AuthClient.swift

Lines changed: 18 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import _Helpers
2+
import ConcurrencyExtras
23
import Foundation
34

45
#if canImport(AuthenticationServices)
@@ -9,81 +10,13 @@ import Foundation
910
import FoundationNetworking
1011
#endif
1112

12-
public final class AuthClient: @unchecked Sendable {
13-
/// FetchHandler is a type alias for asynchronous network request handling.
14-
public typealias FetchHandler = @Sendable (
15-
_ request: URLRequest
16-
) async throws -> (Data, URLResponse)
17-
18-
/// Configuration struct represents the client configuration.
19-
public struct Configuration: Sendable {
20-
public let url: URL
21-
public var headers: [String: String]
22-
public let flowType: AuthFlowType
23-
public let redirectToURL: URL?
24-
public let localStorage: any AuthLocalStorage
25-
public let logger: (any SupabaseLogger)?
26-
public let encoder: JSONEncoder
27-
public let decoder: JSONDecoder
28-
public let fetch: FetchHandler
29-
30-
/// Initializes a AuthClient Configuration with optional parameters.
31-
///
32-
/// - Parameters:
33-
/// - url: The base URL of the Auth server.
34-
/// - headers: Custom headers to be included in requests.
35-
/// - flowType: The authentication flow type.
36-
/// - redirectToURL: Default URL to be used for redirect on the flows that requires it.
37-
/// - localStorage: The storage mechanism for local data.
38-
/// - logger: The logger to use.
39-
/// - encoder: The JSON encoder to use for encoding requests.
40-
/// - decoder: The JSON decoder to use for decoding responses.
41-
/// - fetch: The asynchronous fetch handler for network requests.
42-
public init(
43-
url: URL,
44-
headers: [String: String] = [:],
45-
flowType: AuthFlowType = Configuration.defaultFlowType,
46-
redirectToURL: URL? = nil,
47-
localStorage: any AuthLocalStorage,
48-
logger: (any SupabaseLogger)? = nil,
49-
encoder: JSONEncoder = AuthClient.Configuration.jsonEncoder,
50-
decoder: JSONDecoder = AuthClient.Configuration.jsonDecoder,
51-
fetch: @escaping FetchHandler = { try await URLSession.shared.data(for: $0) }
52-
) {
53-
let headers = headers.merging(Configuration.defaultHeaders) { l, _ in l }
54-
55-
self.url = url
56-
self.headers = headers
57-
self.flowType = flowType
58-
self.redirectToURL = redirectToURL
59-
self.localStorage = localStorage
60-
self.logger = logger
61-
self.encoder = encoder
62-
self.decoder = decoder
63-
self.fetch = fetch
64-
}
65-
}
66-
67-
@Dependency(\.configuration)
68-
private var configuration: Configuration
69-
70-
@Dependency(\.api)
71-
private var api: APIClient
72-
73-
@Dependency(\.eventEmitter)
74-
private var eventEmitter: EventEmitter
75-
76-
@Dependency(\.sessionManager)
77-
private var sessionManager: SessionManager
78-
79-
@Dependency(\.codeVerifierStorage)
80-
private var codeVerifierStorage: CodeVerifierStorage
81-
82-
@Dependency(\.currentDate)
83-
private var currentDate: @Sendable () -> Date
84-
85-
@Dependency(\.logger)
86-
private var logger: (any SupabaseLogger)?
13+
public final class AuthClient: Sendable {
14+
private var api: APIClient { Current.api }
15+
private var configuration: AuthClient.Configuration { Current.configuration }
16+
private var codeVerifierStorage: CodeVerifierStorage { Current.codeVerifierStorage }
17+
private var date: @Sendable () -> Date { Current.date }
18+
private var sessionManager: SessionManager { Current.sessionManager }
19+
private var eventEmitter: AuthStateChangeEventEmitter { Current.eventEmitter }
8720

8821
/// Returns the session, refreshing it if necessary.
8922
///
@@ -95,101 +28,22 @@ public final class AuthClient: @unchecked Sendable {
9528
}
9629

9730
/// Namespace for accessing multi-factor authentication API.
98-
public let mfa: AuthMFA
99-
31+
public let mfa = AuthMFA()
10032
/// Namespace for the GoTrue admin methods.
10133
/// - Warning: This methods requires `service_role` key, be careful to never expose `service_role`
10234
/// key in the client.
103-
public let admin: AuthAdmin
104-
105-
/// Initializes a AuthClient with optional parameters.
106-
///
107-
/// - Parameters:
108-
/// - url: The base URL of the Auth server.
109-
/// - headers: Custom headers to be included in requests.
110-
/// - flowType: The authentication flow type..
111-
/// - redirectToURL: Default URL to be used for redirect on the flows that requires it.
112-
/// - localStorage: The storage mechanism for local data..
113-
/// - logger: The logger to use.
114-
/// - encoder: The JSON encoder to use for encoding requests.
115-
/// - decoder: The JSON decoder to use for decoding responses.
116-
/// - fetch: The asynchronous fetch handler for network requests.
117-
public convenience init(
118-
url: URL,
119-
headers: [String: String] = [:],
120-
flowType: AuthFlowType = AuthClient.Configuration.defaultFlowType,
121-
redirectToURL: URL? = nil,
122-
localStorage: any AuthLocalStorage,
123-
logger: (any SupabaseLogger)? = nil,
124-
encoder: JSONEncoder = AuthClient.Configuration.jsonEncoder,
125-
decoder: JSONDecoder = AuthClient.Configuration.jsonDecoder,
126-
fetch: @escaping FetchHandler = { try await URLSession.shared.data(for: $0) }
127-
) {
128-
self.init(
129-
configuration: Configuration(
130-
url: url,
131-
headers: headers,
132-
flowType: flowType,
133-
redirectToURL: redirectToURL,
134-
localStorage: localStorage,
135-
logger: logger,
136-
encoder: encoder,
137-
decoder: decoder,
138-
fetch: fetch
139-
)
140-
)
141-
}
35+
public let admin = AuthAdmin()
14236

14337
/// Initializes a AuthClient with a specific configuration.
14438
///
14539
/// - Parameters:
14640
/// - configuration: The client configuration.
147-
public convenience init(configuration: Configuration) {
148-
let api = APIClient.live(
149-
configuration: configuration,
150-
http: HTTPClient(
151-
logger: configuration.logger,
152-
fetchHandler: configuration.fetch
153-
)
154-
)
155-
156-
self.init(
157-
configuration: configuration,
158-
sessionManager: .live,
159-
codeVerifierStorage: .live,
160-
api: api,
161-
eventEmitter: .live,
162-
sessionStorage: .live,
163-
logger: configuration.logger
164-
)
165-
}
166-
167-
/// This internal initializer is here only for easy injecting mock instances when testing.
168-
init(
169-
configuration: Configuration,
170-
sessionManager: SessionManager,
171-
codeVerifierStorage: CodeVerifierStorage,
172-
api: APIClient,
173-
eventEmitter: EventEmitter,
174-
sessionStorage: SessionStorage,
175-
logger: (any SupabaseLogger)?
176-
) {
177-
mfa = AuthMFA()
178-
admin = AuthAdmin()
179-
41+
public init(configuration: Configuration) {
18042
Current = Dependencies(
18143
configuration: configuration,
182-
sessionManager: sessionManager,
183-
api: api,
184-
eventEmitter: eventEmitter,
185-
sessionStorage: sessionStorage,
186-
sessionRefresher: SessionRefresher(
187-
refreshSession: { [weak self] in
188-
try await self?.refreshSession(refreshToken: $0) ?? .empty
189-
}
190-
),
191-
codeVerifierStorage: codeVerifierStorage,
192-
logger: logger
44+
sessionRefresher: SessionRefresher { [weak self] in
45+
try await self?.refreshSession(refreshToken: $0) ?? .empty
46+
}
19347
)
19448
}
19549

@@ -204,7 +58,7 @@ public final class AuthClient: @unchecked Sendable {
20458
public func onAuthStateChange(
20559
_ listener: @escaping AuthStateChangeListener
20660
) async -> some AuthStateChangeListenerRegistration {
207-
let token = eventEmitter.attachListener(listener)
61+
let token = eventEmitter.attach(listener)
20862
await emitInitialSession(forToken: token)
20963
return token
21064
}
@@ -782,7 +636,7 @@ public final class AuthClient: @unchecked Sendable {
782636
accessToken: accessToken,
783637
tokenType: tokenType,
784638
expiresIn: expiresIn,
785-
expiresAt: expiresAt ?? currentDate().addingTimeInterval(expiresIn).timeIntervalSince1970,
639+
expiresAt: expiresAt ?? date().addingTimeInterval(expiresIn).timeIntervalSince1970,
786640
refreshToken: refreshToken,
787641
user: user
788642
)
@@ -808,7 +662,7 @@ public final class AuthClient: @unchecked Sendable {
808662
/// - Returns: A new valid session.
809663
@discardableResult
810664
public func setSession(accessToken: String, refreshToken: String) async throws -> Session {
811-
let now = currentDate()
665+
let now = date()
812666
var expiresAt = now
813667
var hasExpired = true
814668
var session: Session
@@ -1187,7 +1041,7 @@ public final class AuthClient: @unchecked Sendable {
11871041

11881042
private func emitInitialSession(forToken token: ObservationToken) async {
11891043
let session = try? await session
1190-
eventEmitter.emit(.initialSession, session, token)
1044+
eventEmitter.emit(.initialSession, session: session, token: token)
11911045
}
11921046

11931047
private func prepareForPKCE() -> (codeChallenge: String?, codeChallengeMethod: String?) {
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
//
2+
// AuthClientConfiguration.swift
3+
//
4+
//
5+
// Created by Guilherme Souza on 29/04/24.
6+
//
7+
8+
import _Helpers
9+
import Foundation
10+
11+
#if canImport(FoundationNetworking)
12+
import FoundationNetworking
13+
#endif
14+
15+
extension AuthClient {
16+
/// FetchHandler is a type alias for asynchronous network request handling.
17+
public typealias FetchHandler = @Sendable (
18+
_ request: URLRequest
19+
) async throws -> (Data, URLResponse)
20+
21+
/// Configuration struct represents the client configuration.
22+
public struct Configuration: Sendable {
23+
public let url: URL
24+
public var headers: [String: String]
25+
public let flowType: AuthFlowType
26+
public let redirectToURL: URL?
27+
public let localStorage: any AuthLocalStorage
28+
public let logger: (any SupabaseLogger)?
29+
public let encoder: JSONEncoder
30+
public let decoder: JSONDecoder
31+
public let fetch: FetchHandler
32+
33+
/// Initializes a AuthClient Configuration with optional parameters.
34+
///
35+
/// - Parameters:
36+
/// - url: The base URL of the Auth server.
37+
/// - headers: Custom headers to be included in requests.
38+
/// - flowType: The authentication flow type.
39+
/// - redirectToURL: Default URL to be used for redirect on the flows that requires it.
40+
/// - localStorage: The storage mechanism for local data.
41+
/// - logger: The logger to use.
42+
/// - encoder: The JSON encoder to use for encoding requests.
43+
/// - decoder: The JSON decoder to use for decoding responses.
44+
/// - fetch: The asynchronous fetch handler for network requests.
45+
public init(
46+
url: URL,
47+
headers: [String: String] = [:],
48+
flowType: AuthFlowType = Configuration.defaultFlowType,
49+
redirectToURL: URL? = nil,
50+
localStorage: any AuthLocalStorage,
51+
logger: (any SupabaseLogger)? = nil,
52+
encoder: JSONEncoder = AuthClient.Configuration.jsonEncoder,
53+
decoder: JSONDecoder = AuthClient.Configuration.jsonDecoder,
54+
fetch: @escaping FetchHandler = { try await URLSession.shared.data(for: $0) }
55+
) {
56+
let headers = headers.merging(Configuration.defaultHeaders) { l, _ in l }
57+
58+
self.url = url
59+
self.headers = headers
60+
self.flowType = flowType
61+
self.redirectToURL = redirectToURL
62+
self.localStorage = localStorage
63+
self.logger = logger
64+
self.encoder = encoder
65+
self.decoder = decoder
66+
self.fetch = fetch
67+
}
68+
}
69+
70+
/// Initializes a AuthClient with optional parameters.
71+
///
72+
/// - Parameters:
73+
/// - url: The base URL of the Auth server.
74+
/// - headers: Custom headers to be included in requests.
75+
/// - flowType: The authentication flow type..
76+
/// - redirectToURL: Default URL to be used for redirect on the flows that requires it.
77+
/// - localStorage: The storage mechanism for local data..
78+
/// - logger: The logger to use.
79+
/// - encoder: The JSON encoder to use for encoding requests.
80+
/// - decoder: The JSON decoder to use for decoding responses.
81+
/// - fetch: The asynchronous fetch handler for network requests.
82+
public convenience init(
83+
url: URL,
84+
headers: [String: String] = [:],
85+
flowType: AuthFlowType = AuthClient.Configuration.defaultFlowType,
86+
redirectToURL: URL? = nil,
87+
localStorage: any AuthLocalStorage,
88+
logger: (any SupabaseLogger)? = nil,
89+
encoder: JSONEncoder = AuthClient.Configuration.jsonEncoder,
90+
decoder: JSONDecoder = AuthClient.Configuration.jsonDecoder,
91+
fetch: @escaping FetchHandler = { try await URLSession.shared.data(for: $0) }
92+
) {
93+
self.init(
94+
configuration: Configuration(
95+
url: url,
96+
headers: headers,
97+
flowType: flowType,
98+
redirectToURL: redirectToURL,
99+
localStorage: localStorage,
100+
logger: logger,
101+
encoder: encoder,
102+
decoder: decoder,
103+
fetch: fetch
104+
)
105+
)
106+
}
107+
}

0 commit comments

Comments
 (0)