-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: Add authentication tests (#28)
Co-authored-by: danthorpe <[email protected]>
- Loading branch information
Showing
3 changed files
with
176 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,48 +1,57 @@ | ||
import Networking | ||
|
||
extension NetworkingComponent { | ||
public func mocked( | ||
_ request: HTTPRequestData, | ||
stub: StubbedResponseStream | ||
) -> some NetworkingComponent { | ||
modified(Mocked(request: request, with: stub)) | ||
} | ||
|
||
public func mocked( | ||
_ block: @escaping @Sendable (NetworkingComponent, HTTPRequestData) -> ResponseStream<HTTPResponseData> | ||
) -> some NetworkingComponent { | ||
modified(CustomMocked(block: block)) | ||
} | ||
public func mocked( | ||
_ stub: StubbedResponseStream, | ||
check: @escaping (HTTPRequestData) -> Bool | ||
) -> some NetworkingComponent { | ||
modified(Mocked(mock: check, with: stub)) | ||
} | ||
|
||
public func mocked( | ||
_ request: HTTPRequestData, | ||
stub: StubbedResponseStream | ||
) -> some NetworkingComponent { | ||
mocked(stub) { $0 ~= request } | ||
} | ||
} | ||
|
||
struct Mocked: NetworkingModifier { | ||
let mock: HTTPRequestData | ||
let stub: StubbedResponseStream | ||
let mock: (HTTPRequestData) -> Bool | ||
let stub: StubbedResponseStream | ||
|
||
@NetworkEnvironment(\.instrument) var instrument | ||
@NetworkEnvironment(\.instrument) var instrument | ||
|
||
init(request: HTTPRequestData, with stubbedResponse: StubbedResponseStream) { | ||
self.mock = request | ||
self.stub = stubbedResponse | ||
} | ||
init(mock: @escaping (HTTPRequestData) -> Bool, with stubbedResponse: StubbedResponseStream) { | ||
self.mock = mock | ||
self.stub = stubbedResponse | ||
} | ||
|
||
func send(upstream: NetworkingComponent, request: HTTPRequestData) -> ResponseStream<HTTPResponseData> { | ||
guard request ~= mock else { | ||
return upstream.send(request) | ||
} | ||
return ResponseStream { continuation in | ||
Task { | ||
await instrument?.measureElapsedTime("Mocked") | ||
await stub(request).redirect(into: continuation) | ||
} | ||
} | ||
func send(upstream: NetworkingComponent, request: HTTPRequestData) -> ResponseStream<HTTPResponseData> { | ||
guard mock(request) else { | ||
return upstream.send(request) | ||
} | ||
return ResponseStream { continuation in | ||
Task { | ||
await instrument?.measureElapsedTime("Mocked") | ||
await stub(request).redirect(into: continuation) | ||
} | ||
} | ||
} | ||
} | ||
|
||
extension NetworkingComponent { | ||
public func mocked( | ||
_ block: @escaping @Sendable (NetworkingComponent, HTTPRequestData) -> ResponseStream<HTTPResponseData> | ||
) -> some NetworkingComponent { | ||
modified(CustomMocked(block: block)) | ||
} | ||
} | ||
|
||
struct CustomMocked: NetworkingModifier { | ||
let block: @Sendable (NetworkingComponent, HTTPRequestData) -> ResponseStream<HTTPResponseData> | ||
let block: @Sendable (NetworkingComponent, HTTPRequestData) -> ResponseStream<HTTPResponseData> | ||
|
||
func send(upstream: NetworkingComponent, request: HTTPRequestData) -> ResponseStream<HTTPResponseData> { | ||
block(upstream, request) | ||
} | ||
func send(upstream: NetworkingComponent, request: HTTPRequestData) -> ResponseStream<HTTPResponseData> { | ||
block(upstream, request) | ||
} | ||
} |
127 changes: 127 additions & 0 deletions
127
Tests/NetworkingTests/Components/Authentication/AuthenticationTests.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
import AssertionExtras | ||
import ConcurrencyExtras | ||
import Dependencies | ||
import Foundation | ||
import HTTPTypes | ||
import Networking | ||
import TestSupport | ||
import XCTest | ||
|
||
final class AuthenticationTests: XCTestCase { | ||
|
||
override func invokeTest() { | ||
withDependencies { | ||
$0.shortID = .incrementing | ||
$0.continuousClock = TestClock() | ||
} operation: { | ||
super.invokeTest() | ||
} | ||
} | ||
|
||
func test__authentication() async throws { | ||
let reporter = TestReporter() | ||
let delegate = TestAuthenticationDelegate( | ||
fetch: { _ in | ||
BearerCredentials(token: "token") | ||
} | ||
) | ||
|
||
let bearerAuthentication = BearerAuthentication(delegate: delegate) | ||
|
||
var request = HTTPRequestData(authority: "example.com") | ||
request.authenticationMethod = .bearer | ||
let copy = request | ||
|
||
let network = TerminalNetworkingComponent() | ||
.mocked(.ok(), check: { _ in true }) | ||
.reported(by: reporter) | ||
.authenticated(with: bearerAuthentication) | ||
|
||
try await withMainSerialExecutor { | ||
try await withThrowingTaskGroup(of: HTTPResponseData.self) { group in | ||
|
||
for _ in 0..<4 { | ||
group.addTask { | ||
return try await network.data(copy) | ||
} | ||
} | ||
|
||
var responses: [HTTPResponseData] = [] | ||
for try await response in group { | ||
responses.append(response) | ||
} | ||
XCTAssertTrue(responses.allSatisfy { | ||
$0.request.headerFields[.authorization] == "Bearer token" | ||
}) | ||
} | ||
|
||
let reportedRequests = await reporter.requests | ||
XCTAssertEqual(reportedRequests.count, 4) | ||
XCTAssertTrue(reportedRequests.allSatisfy { | ||
$0.headerFields[.authorization] == "Bearer token" | ||
}) | ||
|
||
XCTAssertEqual(delegate.fetchCount, 1) | ||
} | ||
} | ||
|
||
func test__authentication__when_delegate_throws_error_on_fetch() async throws { | ||
struct CustomError: Error, Hashable { } | ||
|
||
let delegate = TestAuthenticationDelegate<BearerCredentials>( | ||
fetch: { _ in | ||
throw CustomError() | ||
} | ||
) | ||
|
||
let bearerAuthentication = BearerAuthentication(delegate: delegate) | ||
|
||
var request = HTTPRequestData(authority: "example.com") | ||
request.authenticationMethod = .bearer | ||
|
||
let network = TerminalNetworkingComponent() | ||
.authenticated(with: bearerAuthentication) | ||
|
||
await XCTAssertThrowsError( | ||
try await network.data(request), | ||
matches: AuthenticationError.fetchCredentialsFailed(request, .bearer, CustomError()) | ||
) | ||
} | ||
|
||
func test__authentication__refresh_token() async throws { | ||
|
||
var isUnauthorized = true | ||
let reporter = TestReporter() | ||
let delegate = TestAuthenticationDelegate( | ||
fetch: { _ in | ||
BearerCredentials(token: "token") | ||
}, | ||
refresh: { _, _ in | ||
BearerCredentials(token: "refreshed token") | ||
} | ||
) | ||
|
||
let bearerAuthentication = BearerAuthentication(delegate: delegate) | ||
|
||
var request = HTTPRequestData(authority: "example.com") | ||
request.authenticationMethod = .bearer | ||
|
||
let network = TerminalNetworkingComponent() | ||
.mocked(.ok(), check: { _ in true }) | ||
.mocked(.status(.unauthorized), check: { _ in | ||
defer { isUnauthorized.toggle() } | ||
return isUnauthorized | ||
}) | ||
.reported(by: reporter) | ||
.authenticated(with: bearerAuthentication) | ||
|
||
try await network.data(request) | ||
|
||
let reportedRequests = await reporter.requests | ||
XCTAssertEqual(reportedRequests.count, 2) | ||
XCTAssertTrue(reportedRequests[0].headerFields[.authorization] == "Bearer token") | ||
XCTAssertTrue(reportedRequests[1].headerFields[.authorization] == "Bearer refreshed token") | ||
XCTAssertEqual(delegate.fetchCount, 1) | ||
XCTAssertEqual(delegate.refreshCount, 1) | ||
} | ||
} |