From df3c562b9d1b94c0229186008fbdfcf471e32e51 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Sat, 20 Sep 2025 11:01:43 +0100 Subject: [PATCH] Swift testing --- .../TestClient+Auth.swift | 1 - Tests/HummingbirdAuthTests/AuthTests.swift | 68 +++++++------- Tests/HummingbirdAuthTests/BcryptTests.swift | 14 +-- Tests/HummingbirdAuthTests/OTPTests.swift | 45 ++++----- Tests/HummingbirdAuthTests/SessionTests.swift | 93 ++++++++++--------- 5 files changed, 110 insertions(+), 111 deletions(-) diff --git a/Sources/HummingbirdAuthTesting/TestClient+Auth.swift b/Sources/HummingbirdAuthTesting/TestClient+Auth.swift index 1066f23..e8e9088 100644 --- a/Sources/HummingbirdAuthTesting/TestClient+Auth.swift +++ b/Sources/HummingbirdAuthTesting/TestClient+Auth.swift @@ -15,7 +15,6 @@ import ExtrasBase64 import Hummingbird import HummingbirdTesting -import XCTest /// Used to generate various authentication types for Testing framework public struct TestAuthentication: Equatable { diff --git a/Tests/HummingbirdAuthTests/AuthTests.swift b/Tests/HummingbirdAuthTests/AuthTests.swift index 04b3ac3..6d3307e 100644 --- a/Tests/HummingbirdAuthTests/AuthTests.swift +++ b/Tests/HummingbirdAuthTests/AuthTests.swift @@ -19,10 +19,10 @@ import HummingbirdBasicAuth import HummingbirdBcrypt import HummingbirdTesting import NIOPosix -import XCTest +import Testing -final class AuthTests: XCTestCase { - func testBearer() async throws { +struct AuthTests { + @Test func testBearer() async throws { struct User: Sendable { let name: String } @@ -33,16 +33,16 @@ final class AuthTests: XCTestCase { let app = Application(responder: router.buildResponder()) try await app.test(.router) { client in try await client.execute(uri: "/", method: .get, auth: .bearer("1234567890")) { response in - let body = try XCTUnwrap(response.body) - XCTAssertEqual(String(buffer: body), "1234567890") + let body = response.body + #expect(String(buffer: body) == "1234567890") } try await client.execute(uri: "/", method: .get, auth: .basic(username: "adam", password: "1234")) { response in - XCTAssertEqual(response.status, .noContent) + #expect(response.status == .noContent) } } } - func testBasic() async throws { + @Test func testBasic() async throws { struct User: Sendable { let name: String } @@ -53,13 +53,13 @@ final class AuthTests: XCTestCase { let app = Application(responder: router.buildResponder()) try await app.test(.router) { client in try await client.execute(uri: "/", method: .get, auth: .basic(username: "adam", password: "password")) { response in - let body = try XCTUnwrap(response.body) - XCTAssertEqual(String(buffer: body), "adam:password") + let body = response.body + #expect(String(buffer: body) == "adam:password") } } } - func testBcryptThread() async throws { + @Test func testBcryptThread() async throws { struct User: Sendable { let name: String } @@ -88,15 +88,15 @@ final class AuthTests: XCTestCase { let app = Application(responder: router.buildResponder()) try await app.test(.router) { client in try await client.execute(uri: "/", method: .put, auth: .basic(username: "testuser", password: "testpassword123")) { response in - XCTAssertEqual(response.status, .ok) + #expect(response.status == .ok) } try await client.execute(uri: "/", method: .post, auth: .basic(username: "testuser", password: "testpassword123")) { response in - XCTAssertEqual(response.status, .ok) + #expect(response.status == .ok) } } } - func testAuth() async throws { + @Test func testAuth() async throws { struct User: Sendable { let name: String } @@ -108,24 +108,22 @@ final class AuthTests: XCTestCase { var context = context context.identity = User(name: "Test") - XCTAssertNotNil(context.identity) - XCTAssertEqual(context.identity?.name, "Test") + #expect(context.identity != nil) + #expect(context.identity?.name == "Test") context.identity = nil - XCTAssertNil(context.identity) - return .accepted } let app = Application(responder: router.buildResponder()) try await app.test(.router) { client in try await client.execute(uri: "/", method: .get) { response in - XCTAssertEqual(response.status, .accepted) + #expect(response.status == .accepted) } } } - func testLogin() async throws { + @Test func testLogin() async throws { struct User: Sendable { let name: String } @@ -144,12 +142,12 @@ final class AuthTests: XCTestCase { try await app.test(.router) { client in try await client.execute(uri: "/", method: .get) { response in - XCTAssertEqual(response.status, .ok) + #expect(response.status == .ok) } } } - func testClosureAuthenticator() async throws { + @Test func testClosureAuthenticator() async throws { struct User: Sendable { let name: String } @@ -174,16 +172,16 @@ final class AuthTests: XCTestCase { try await app.test(.router) { client in try await client.execute(uri: "/authenticate?user=john", method: .get) { response in - XCTAssertEqual(response.status, .ok) - XCTAssertEqual(response.body, ByteBuffer(string: "john")) + #expect(response.status == .ok) + #expect(response.body == ByteBuffer(string: "john")) } try await client.execute(uri: "/authenticate", method: .get) { response in - XCTAssertEqual(response.status, .unauthorized) + #expect(response.status == .unauthorized) } } } - func testIsAuthenticatedMiddleware() async throws { + @Test func testIsAuthenticatedMiddleware() async throws { struct User: Sendable { let name: String } @@ -208,15 +206,15 @@ final class AuthTests: XCTestCase { try await app.test(.router) { client in try await client.execute(uri: "/authenticated", method: .get) { response in - XCTAssertEqual(response.status, .ok) + #expect(response.status == .ok) } try await client.execute(uri: "/unauthenticated", method: .get) { response in - XCTAssertEqual(response.status, .unauthorized) + #expect(response.status == .unauthorized) } } } - func testBasicAuthenticator() async throws { + @Test func testBasicAuthenticator() async throws { struct User: PasswordAuthenticatable { let username: String let passwordHash: String? @@ -240,16 +238,16 @@ final class AuthTests: XCTestCase { let app = Application(responder: router.buildResponder()) try await app.test(.router) { client in try await client.execute(uri: "/", method: .get, auth: .basic(username: "admin", password: "password")) { response in - let body = try XCTUnwrap(response.body) - XCTAssertEqual(String(buffer: body), "admin") + let body = response.body + #expect(String(buffer: body) == "admin") } try await client.execute(uri: "/", method: .get, auth: .basic(username: "adam", password: "password")) { response in - XCTAssertEqual(response.status, .unauthorized) + #expect(response.status == .unauthorized) } } } - func testBasicAuthenticatorWithClosure() async throws { + @Test func testBasicAuthenticatorWithClosure() async throws { struct User: PasswordAuthenticatable { let username: String let passwordHash: String? @@ -270,11 +268,11 @@ final class AuthTests: XCTestCase { let app = Application(responder: router.buildResponder()) try await app.test(.router) { client in try await client.execute(uri: "/", method: .get, auth: .basic(username: "admin", password: "password")) { response in - let body = try XCTUnwrap(response.body) - XCTAssertEqual(String(buffer: body), "admin") + let body = response.body + #expect(String(buffer: body) == "admin") } try await client.execute(uri: "/", method: .get, auth: .basic(username: "adam", password: "password")) { response in - XCTAssertEqual(response.status, .unauthorized) + #expect(response.status == .unauthorized) } } } diff --git a/Tests/HummingbirdAuthTests/BcryptTests.swift b/Tests/HummingbirdAuthTests/BcryptTests.swift index a371c0d..0e8c103 100644 --- a/Tests/HummingbirdAuthTests/BcryptTests.swift +++ b/Tests/HummingbirdAuthTests/BcryptTests.swift @@ -18,20 +18,20 @@ import HummingbirdAuthTesting import HummingbirdBcrypt import HummingbirdTesting import NIOPosix -import XCTest +import Testing -final class BcryptTests: XCTestCase { - func testBcrypt() { +struct BcryptTests { + @Test func testBcrypt() { let hash = Bcrypt.hash("password") - XCTAssert(Bcrypt.verify("password", hash: hash)) + #expect(Bcrypt.verify("password", hash: hash)) } - func testBcryptFalse() { + @Test func testBcryptFalse() { let hash = Bcrypt.hash("password") - XCTAssertFalse(Bcrypt.verify("password1", hash: hash)) + #expect(!Bcrypt.verify("password1", hash: hash)) } - func testMultipleBcrypt() async throws { + @Test func testMultipleBcrypt() async throws { struct VerifyFailError: Error {} try await withThrowingTaskGroup(of: Void.self) { group in diff --git a/Tests/HummingbirdAuthTests/OTPTests.swift b/Tests/HummingbirdAuthTests/OTPTests.swift index 76c79b6..ebee3d5 100644 --- a/Tests/HummingbirdAuthTests/OTPTests.swift +++ b/Tests/HummingbirdAuthTests/OTPTests.swift @@ -12,32 +12,33 @@ // //===----------------------------------------------------------------------===// +import Foundation import HummingbirdOTP -import XCTest +import Testing -final class OTPTests: XCTestCase { +struct OTPTests { func randomBuffer(size: Int) -> [UInt8] { var data = [UInt8](repeating: 0, count: size) data = data.map { _ in UInt8.random(in: 0...255) } return data } - func testHOTP() { + @Test func testHOTP() { // test against RFC4226 example values https://tools.ietf.org/html/rfc4226#page-32 let secret = "12345678901234567890" - XCTAssertEqual(HOTP(secret: secret).compute(counter: 0), 755_224) - XCTAssertEqual(HOTP(secret: secret).compute(counter: 1), 287_082) - XCTAssertEqual(HOTP(secret: secret).compute(counter: 2), 359_152) - XCTAssertEqual(HOTP(secret: secret).compute(counter: 3), 969_429) - XCTAssertEqual(HOTP(secret: secret).compute(counter: 4), 338_314) - XCTAssertEqual(HOTP(secret: secret).compute(counter: 5), 254_676) - XCTAssertEqual(HOTP(secret: secret).compute(counter: 6), 287_922) - XCTAssertEqual(HOTP(secret: secret).compute(counter: 7), 162_583) - XCTAssertEqual(HOTP(secret: secret).compute(counter: 8), 399_871) - XCTAssertEqual(HOTP(secret: secret).compute(counter: 9), 520_489) + #expect(HOTP(secret: secret).compute(counter: 0) == 755_224) + #expect(HOTP(secret: secret).compute(counter: 1) == 287_082) + #expect(HOTP(secret: secret).compute(counter: 2) == 359_152) + #expect(HOTP(secret: secret).compute(counter: 3) == 969_429) + #expect(HOTP(secret: secret).compute(counter: 4) == 338_314) + #expect(HOTP(secret: secret).compute(counter: 5) == 254_676) + #expect(HOTP(secret: secret).compute(counter: 6) == 287_922) + #expect(HOTP(secret: secret).compute(counter: 7) == 162_583) + #expect(HOTP(secret: secret).compute(counter: 8) == 399_871) + #expect(HOTP(secret: secret).compute(counter: 9) == 520_489) } - func testTOTP() { + @Test func testTOTP() { // test against RFC6238 example values https://tools.ietf.org/html/rfc6238#page-15 let secret = "12345678901234567890" @@ -46,17 +47,17 @@ final class OTPTests: XCTestCase { dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'" dateFormatter.timeZone = TimeZone(secondsFromGMT: 0) - XCTAssertEqual(TOTP(secret: secret, length: 8).compute(date: dateFormatter.date(from: "1970-01-01T00:00:59Z")!), 94_287_082) - XCTAssertEqual(TOTP(secret: secret, length: 8).compute(date: dateFormatter.date(from: "2005-03-18T01:58:29Z")!), 7_081_804) - XCTAssertEqual(TOTP(secret: secret, length: 8).compute(date: dateFormatter.date(from: "2005-03-18T01:58:31Z")!), 14_050_471) - XCTAssertEqual(TOTP(secret: secret, length: 8).compute(date: dateFormatter.date(from: "2009-02-13T23:31:30Z")!), 89_005_924) - XCTAssertEqual(TOTP(secret: secret, length: 8).compute(date: dateFormatter.date(from: "2033-05-18T03:33:20Z")!), 69_279_037) - XCTAssertEqual(TOTP(secret: secret, length: 8).compute(date: dateFormatter.date(from: "2603-10-11T11:33:20Z")!), 65_353_130) + #expect(TOTP(secret: secret, length: 8).compute(date: dateFormatter.date(from: "1970-01-01T00:00:59Z")!) == 94_287_082) + #expect(TOTP(secret: secret, length: 8).compute(date: dateFormatter.date(from: "2005-03-18T01:58:29Z")!) == 7_081_804) + #expect(TOTP(secret: secret, length: 8).compute(date: dateFormatter.date(from: "2005-03-18T01:58:31Z")!) == 14_050_471) + #expect(TOTP(secret: secret, length: 8).compute(date: dateFormatter.date(from: "2009-02-13T23:31:30Z")!) == 89_005_924) + #expect(TOTP(secret: secret, length: 8).compute(date: dateFormatter.date(from: "2033-05-18T03:33:20Z")!) == 69_279_037) + #expect(TOTP(secret: secret, length: 8).compute(date: dateFormatter.date(from: "2603-10-11T11:33:20Z")!) == 65_353_130) } - func testAuthenticatorURL() { + @Test func testAuthenticatorURL() { let secret = "HB12345678901234567890" let url = TOTP(secret: secret, length: 8).createAuthenticatorURL(label: "TOTP test", issuer: "Hummingbird") - XCTAssertEqual(url, "otpauth://totp/TOTP%20test?secret=JBBDCMRTGQ2TMNZYHEYDCMRTGQ2TMNZYHEYA&issuer=Hummingbird&digits=8") + #expect(url == "otpauth://totp/TOTP%20test?secret=JBBDCMRTGQ2TMNZYHEYDCMRTGQ2TMNZYHEYA&issuer=Hummingbird&digits=8") } } diff --git a/Tests/HummingbirdAuthTests/SessionTests.swift b/Tests/HummingbirdAuthTests/SessionTests.swift index 2e01cd8..4e166b4 100644 --- a/Tests/HummingbirdAuthTests/SessionTests.swift +++ b/Tests/HummingbirdAuthTests/SessionTests.swift @@ -12,16 +12,17 @@ // //===----------------------------------------------------------------------===// +import Foundation import HummingbirdAuth import HummingbirdAuthTesting import HummingbirdTesting import NIOPosix -import XCTest +import Testing @testable import Hummingbird -final class SessionTests: XCTestCase { - func testSessionAuthenticator() async throws { +struct SessionTests { + @Test func testSessionAuthenticator() async throws { struct User: Sendable { let name: String } @@ -59,17 +60,17 @@ final class SessionTests: XCTestCase { try await app.test(.router) { client in let responseCookies = try await client.execute(uri: "/session", method: .put) { response -> String? in - XCTAssertEqual(response.status, .ok) + #expect(response.status == .ok) return response.headers[.setCookie] } - let cookies = try XCTUnwrap(responseCookies) + let cookies = try #require(responseCookies) try await client.execute(uri: "/session", method: .get, headers: [.cookie: cookies]) { response in - XCTAssertEqual(response.status, .ok) + #expect(response.status == .ok) } } } - func testSessionAuthenticatorClosure() async throws { + @Test func testSessionAuthenticatorClosure() async throws { struct User: Sendable { let name: String } @@ -101,17 +102,17 @@ final class SessionTests: XCTestCase { try await app.test(.router) { client in let responseCookies = try await client.execute(uri: "/session", method: .put) { response -> String? in - XCTAssertEqual(response.status, .ok) + #expect(response.status == .ok) return response.headers[.setCookie] } - let cookies = try XCTUnwrap(responseCookies) + let cookies = try #require(responseCookies) try await client.execute(uri: "/session", method: .get, headers: [.cookie: cookies]) { response in - XCTAssertEqual(response.status, .ok) + #expect(response.status == .ok) } } } - func testSessionUpdate() async throws { + @Test func testSessionUpdate() async throws { struct User: Codable, Sendable { var name: String } @@ -148,39 +149,39 @@ final class SessionTests: XCTestCase { try await app.test(.router) { client in var cookie = try await client.execute(uri: "/save?name=john", method: .post) { response -> String in - XCTAssertEqual(response.status, .ok) - return try XCTUnwrap(response.headers[.setCookie]) + #expect(response.status == .ok) + return try #require(response.headers[.setCookie]) } try await client.execute(uri: "/update?name=jane", method: .post, headers: [.cookie: cookie]) { response in - XCTAssertEqual(response.status, .ok) - XCTAssertNil(response.headers[.setCookie]) + #expect(response.status == .ok) + #expect(response.headers[.setCookie] == nil) } // get save username try await client.execute(uri: "/name", method: .get, headers: [.cookie: cookie]) { response in - XCTAssertEqual(response.status, .ok) - let buffer = try XCTUnwrap(response.body) - XCTAssertEqual(String(buffer: buffer), "jane") + #expect(response.status == .ok) + let buffer = response.body + #expect(String(buffer: buffer) == "jane") } cookie = try await client.execute(uri: "/updateExpires?name=joan", method: .post, headers: [.cookie: cookie]) { response in - XCTAssertEqual(response.status, .ok) + #expect(response.status == .ok) // if we update the cookie expiration date a set-cookie header should be returned - let newCookieHeader = try XCTUnwrap(response.headers[.setCookie]) + let newCookieHeader = try #require(response.headers[.setCookie]) // check we are updating the existing cookie - let cookieCookie = try XCTUnwrap(Cookie(from: cookie[...])) - let newCookie = try XCTUnwrap(Cookie(from: newCookieHeader[...])) - XCTAssertEqual(cookieCookie.value, newCookie.value) + let cookieCookie = try #require(Cookie(from: cookie[...])) + let newCookie = try #require(Cookie(from: newCookieHeader[...])) + #expect(cookieCookie.value == newCookie.value) return newCookieHeader } // get save username try await client.execute(uri: "/name", method: .get, headers: [.cookie: cookie]) { response in - XCTAssertEqual(response.status, .ok) - let buffer = try XCTUnwrap(response.body) - XCTAssertEqual(String(buffer: buffer), "joan") + #expect(response.status == .ok) + let body = response.body + #expect(String(buffer: body) == "joan") } } } - func testSessionDeletion() async throws { + @Test func testSessionDeletion() async throws { struct User: Codable, Sendable { let name: String } @@ -205,26 +206,26 @@ final class SessionTests: XCTestCase { try await app.test(.router) { client in let cookies = try await client.execute(uri: "/login?name=john", method: .post) { response -> String? in - XCTAssertEqual(response.status, .ok) + #expect(response.status == .ok) return response.headers[.setCookie] } // get username try await client.execute(uri: "/name", method: .get, headers: cookies.map { [.cookie: $0] } ?? [:]) { response in - XCTAssertEqual(response.status, .ok) - XCTAssertEqual(String(buffer: response.body), "john") + #expect(response.status == .ok) + #expect(String(buffer: response.body) == "john") } let cookies2 = try await client.execute(uri: "/logout", method: .post) { response -> String? in - XCTAssertEqual(response.status, .ok) + #expect(response.status == .ok) return response.headers[.setCookie] } // get username try await client.execute(uri: "/name", method: .get, headers: cookies2.map { [.cookie: $0] } ?? [:]) { response in - XCTAssertEqual(response.status, .unauthorized) + #expect(response.status == .unauthorized) } } } - func testSessionCookieParameters() async throws { + @Test func testSessionCookieParameters() async throws { struct User: Codable, Sendable { var name: String } @@ -256,20 +257,20 @@ final class SessionTests: XCTestCase { try await app.test(.router) { client in try await client.execute(uri: "/save?name=john", method: .post) { response in - XCTAssertEqual(response.status, .ok) - let setCookieHeader = try XCTUnwrap(response.headers[.setCookie]) - let cookie = try XCTUnwrap(Cookie(from: setCookieHeader[...])) - XCTAssertEqual(cookie.name, "TEST_SESSION_COOKIE") - XCTAssertEqual(cookie.domain, "https://test.com") - XCTAssertEqual(cookie.path, "/test") - XCTAssertEqual(cookie.secure, true) - XCTAssertEqual(cookie.sameSite, .strict) + #expect(response.status == .ok) + let setCookieHeader = try #require(response.headers[.setCookie]) + let cookie = try #require(Cookie(from: setCookieHeader[...])) + #expect(cookie.name == "TEST_SESSION_COOKIE") + #expect(cookie.domain == "https://test.com") + #expect(cookie.path == "/test") + #expect(cookie.secure == true) + #expect(cookie.sameSite == .strict) } } } /// Save session as one type and retrieve as another. - func testInvalidSession() async throws { + @Test func testInvalidSession() async throws { struct User: Codable, Sendable { let name: String } @@ -286,11 +287,11 @@ final class SessionTests: XCTestCase { try await app.test(.router) { client in try await client.execute(uri: "/test", method: .post, headers: [.cookie: cookie.description]) { response in - XCTAssertEqual(response.status, .noContent) - let setCookieHeader = try XCTUnwrap(response.headers[.setCookie]) + #expect(response.status == .noContent) + let setCookieHeader = try #require(response.headers[.setCookie]) let cookie = Cookie(from: setCookieHeader[...]) - let expires = try XCTUnwrap(cookie?.expires) - XCTAssertLessThanOrEqual(expires, .now) + let expires = try #require(cookie?.expires) + #expect(expires <= .now) } } }