Skip to content

Commit

Permalink
Refactor dependencies and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
grdsdev committed Oct 27, 2023
1 parent 9fb45ab commit ac9cc94
Show file tree
Hide file tree
Showing 8 changed files with 284 additions and 231 deletions.
15 changes: 7 additions & 8 deletions Sources/GoTrue/GoTrueClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ public actor GoTrueClient {
Dependencies.current.value!.sessionManager
}

private let codeVerifierStorage: CodeVerifierStorage
private var codeVerifierStorage: CodeVerifierStorage {
Dependencies.current.value!.codeVerifierStorage
}

private var eventEmitter: EventEmitter {
Dependencies.current.value!.eventEmitter
Expand Down Expand Up @@ -100,15 +102,12 @@ public actor GoTrueClient {
}

public init(configuration: Configuration) {
let sessionManager = DefaultSessionManager()

let codeVerifierStorage = DefaultCodeVerifierStorage()
let api = APIClient()

self.init(
configuration: configuration,
sessionManager: sessionManager,
codeVerifierStorage: codeVerifierStorage,
sessionManager: .live,
codeVerifierStorage: .live,
api: api,
eventEmitter: .live,
sessionStorage: .live
Expand All @@ -124,7 +123,6 @@ public actor GoTrueClient {
eventEmitter: EventEmitter,
sessionStorage: SessionStorage
) {
self.codeVerifierStorage = codeVerifierStorage
self.mfa = GoTrueMFA()

Dependencies.current.setValue(
Expand All @@ -138,7 +136,8 @@ public actor GoTrueClient {
refreshSession: { [weak self] in
try await self?.refreshSession(refreshToken: $0) ?? .empty
}
)
),
codeVerifierStorage: codeVerifierStorage
)
)
}
Expand Down
44 changes: 23 additions & 21 deletions Sources/GoTrue/Internal/CodeVerifierStorage.swift
Original file line number Diff line number Diff line change
@@ -1,30 +1,32 @@
import Foundation
@_spi(Internal) import _Helpers

protocol CodeVerifierStorage {
func getCodeVerifier() throws -> String?
func storeCodeVerifier(_ code: String) throws
func deleteCodeVerifier() throws
struct CodeVerifierStorage: Sendable {
var getCodeVerifier: @Sendable () throws -> String?
var storeCodeVerifier: @Sendable (_ code: String) throws -> Void
var deleteCodeVerifier: @Sendable () throws -> Void
}

struct DefaultCodeVerifierStorage: CodeVerifierStorage {
private var localStorage: GoTrueLocalStorage {
Dependencies.current.value!.configuration.localStorage
}

private let key = "supabase.code-verifier"

func getCodeVerifier() throws -> String? {
try localStorage.retrieve(key: key).flatMap {
String(data: $0, encoding: .utf8)
extension CodeVerifierStorage {
static var live: Self = {
var localStorage: GoTrueLocalStorage {
Dependencies.current.value!.configuration.localStorage
}
}

func storeCodeVerifier(_ code: String) throws {
try localStorage.store(key: key, value: Data(code.utf8))
}
let key = "supabase.code-verifier"

func deleteCodeVerifier() throws {
try localStorage.remove(key: key)
}
return Self(
getCodeVerifier: {
try localStorage.retrieve(key: key).flatMap {
String(data: $0, encoding: .utf8)
}
},
storeCodeVerifier: { code in
try localStorage.store(key: key, value: Data(code.utf8))
},
deleteCodeVerifier: {
try localStorage.remove(key: key)
}
)
}()
}
1 change: 1 addition & 0 deletions Sources/GoTrue/Internal/Dependencies.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ struct Dependencies: Sendable {
var eventEmitter: EventEmitter
var sessionStorage: SessionStorage
var sessionRefresher: SessionRefresher
var codeVerifierStorage: CodeVerifierStorage
}
21 changes: 16 additions & 5 deletions Sources/GoTrue/Internal/SessionManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,24 @@ struct SessionRefresher: Sendable {
var refreshSession: @Sendable (_ refreshToken: String) async throws -> Session
}

protocol SessionManager: Sendable {
func session() async throws -> Session
func update(_ session: Session) async throws
func remove() async
struct SessionManager: Sendable {
var session: @Sendable () async throws -> Session
var update: @Sendable (_ session: Session) async throws -> Void
var remove: @Sendable () async -> Void
}

actor DefaultSessionManager: SessionManager {
extension SessionManager {
static var live: Self = {
let manager = _LiveSessionManager()
return Self(
session: { try await manager.session() },
update: { try await manager.update($0) },
remove: { await manager.remove() }
)
}()
}

actor _LiveSessionManager {
private var task: Task<Session, Error>?

private var storage: SessionStorage {
Expand Down
47 changes: 3 additions & 44 deletions Tests/GoTrueTests/GoTrueClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,18 @@ import XCTest

final class GoTrueClientTests: XCTestCase {

fileprivate var sessionManager: SessionManagerMock!
fileprivate var codeVerifierStorage: CodeVerifierStorageMock!
fileprivate var api: APIClient!

func testOnAuthStateChange() async throws {
let session = Session.validSession

let sut = makeSUT()
sessionManager.sessionResult = .success(session)

let events = ActorIsolated([AuthChangeEvent]())
let expectation = self.expectation(description: "onAuthStateChangeEnd")

await withDependencies {
$0.eventEmitter = .live
$0.sessionManager.session = { session }
} operation: {
let authStateStream = await sut.onAuthStateChange()

Expand All @@ -49,9 +46,6 @@ final class GoTrueClientTests: XCTestCase {
}

private func makeSUT(fetch: GoTrueClient.FetchHandler? = nil) -> GoTrueClient {
sessionManager = SessionManagerMock()
codeVerifierStorage = CodeVerifierStorageMock()

let configuration = GoTrueClient.Configuration(
url: clientURL,
headers: ["apikey": "dummy.api.key"],
Expand All @@ -68,8 +62,8 @@ final class GoTrueClientTests: XCTestCase {

let sut = GoTrueClient(
configuration: configuration,
sessionManager: sessionManager,
codeVerifierStorage: codeVerifierStorage,
sessionManager: .mock,
codeVerifierStorage: .mock,
api: api,
eventEmitter: .mock,
sessionStorage: .mock
Expand All @@ -82,38 +76,3 @@ final class GoTrueClientTests: XCTestCase {
return sut
}
}

private final class SessionManagerMock: SessionManager, @unchecked Sendable {
private let lock = NSRecursiveLock()

var sessionRefresher: SessionRefresher?
func setSessionRefresher(_ refresher: GoTrue.SessionRefresher?) async {
lock.withLock {
sessionRefresher = refresher
}
}

var sessionResult: Result<Session, Error>!
func session() async throws -> GoTrue.Session {
try sessionResult.get()
}

func update(_ session: GoTrue.Session) async throws {}

func remove() async {}
}

final class CodeVerifierStorageMock: CodeVerifierStorage {
var codeVerifier: String?
func getCodeVerifier() throws -> String? {
codeVerifier
}

func storeCodeVerifier(_ code: String) throws {
codeVerifier = code
}

func deleteCodeVerifier() throws {
codeVerifier = nil
}
}
96 changes: 96 additions & 0 deletions Tests/GoTrueTests/Mocks/Mocks.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
//
// File.swift
//
//
// Created by Guilherme Souza on 27/10/23.
//

import Foundation
import XCTestDynamicOverlay
@_spi(Internal) import _Helpers

@testable import GoTrue

let clientURL = URL(string: "http://localhost:54321/auth/v1")!

extension CodeVerifierStorage {
static let mock = Self(
getCodeVerifier: unimplemented("getCodeVerifier"),
storeCodeVerifier: unimplemented("storeCodeVerifier"),
deleteCodeVerifier: unimplemented("deleteCodeVerifier")
)
}

extension SessionManager {
static let mock = Self(
session: unimplemented("session"),
update: unimplemented("update"),
remove: unimplemented("remove")
)
}

extension EventEmitter {
static let mock = Self(
attachListener: unimplemented("attachListener"),
emit: unimplemented("emit")
)

static let noop = Self(
attachListener: { (UUID(), AsyncStream.makeStream().stream) },
emit: { _, _ in }
)
}

extension SessionStorage {
static let mock = Self(
getSession: unimplemented("getSession"),
storeSession: unimplemented("storeSession"),
deleteSession: unimplemented("deleteSession")
)
}

extension SessionRefresher {
static let mock = Self(refreshSession: unimplemented("refreshSession"))
}

extension Dependencies {
static let mock = Dependencies(
configuration: GoTrueClient.Configuration(url: clientURL),
sessionManager: .mock,
api: APIClient(),
eventEmitter: .mock,
sessionStorage: .mock,
sessionRefresher: .mock,
codeVerifierStorage: .mock
)
}

func withDependencies(
_ mutation: (inout Dependencies) -> Void,
operation: () async throws -> Void
) async rethrows {
let current = Dependencies.current.value ?? .mock
var copy = current
mutation(&copy)
Dependencies.current.withValue { $0 = copy }
defer { Dependencies.current.setValue(current) }
try await operation()
}

extension Session {
static let validSession = Session(
accessToken: "accesstoken",
tokenType: "bearer",
expiresIn: 120,
refreshToken: "refreshtoken",
user: User(fromMockNamed: "user")
)

static let expiredSession = Session(
accessToken: "accesstoken",
tokenType: "bearer",
expiresIn: 60,
refreshToken: "refreshtoken",
user: User(fromMockNamed: "user")
)
}
Loading

0 comments on commit ac9cc94

Please sign in to comment.