diff --git a/Sources/PerfectFileMakerDataAPI/FMPSession.swift b/Sources/PerfectFileMakerDataAPI/FMPSession.swift index e5d9a4c..e4f7d76 100644 --- a/Sources/PerfectFileMakerDataAPI/FMPSession.swift +++ b/Sources/PerfectFileMakerDataAPI/FMPSession.swift @@ -28,9 +28,8 @@ actor FMPSession { private static func login(host: String, port: Int, database: String, username: String, password: String, urlSession: URLSession) async throws -> String { - let scheme = port == 443 ? "https" : "http" let db = database.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? database - guard let url = URL(string: "\(scheme)://\(host):\(port)/fmi/data/v1/databases/\(db)/sessions") else { + guard let url = URL(string: "https://\(host):\(port)/fmi/data/v1/databases/\(db)/sessions") else { throw FMPError.invalidResponse } var request = URLRequest(url: url) diff --git a/Sources/PerfectFileMakerDataAPI/FileMakerServer.swift b/Sources/PerfectFileMakerDataAPI/FileMakerServer.swift index bdfd9f7..5f023a3 100644 --- a/Sources/PerfectFileMakerDataAPI/FileMakerServer.swift +++ b/Sources/PerfectFileMakerDataAPI/FileMakerServer.swift @@ -49,8 +49,7 @@ public struct FileMakerServer: Sendable { /// Returns the names of all databases available to these credentials. public func databaseNames() async throws -> [String] { - let scheme = port == 443 ? "https" : "http" - guard let url = URL(string: "\(scheme)://\(host):\(port)/fmi/data/v1/databases") else { + guard let url = URL(string: "https://\(host):\(port)/fmi/data/v1/databases") else { throw FMPError.invalidResponse } var req = URLRequest(url: url) @@ -201,9 +200,8 @@ public struct FileMakerServer: Sendable { /// Closes the current session on the server and clears the cached token. public func logout() async throws { guard let token = await session.currentToken() else { return } - let scheme = port == 443 ? "https" : "http" let db = encoded(database) - guard let url = URL(string: "\(scheme)://\(host):\(port)/fmi/data/v1/databases/\(db)/sessions/\(token)") else { + guard let url = URL(string: "https://\(host):\(port)/fmi/data/v1/databases/\(db)/sessions/\(token)") else { return } var req = URLRequest(url: url) @@ -216,8 +214,7 @@ public struct FileMakerServer: Sendable { // MARK: - Private helpers var baseURL: String { - let scheme = port == 443 ? "https" : "http" - return "\(scheme)://\(host):\(port)/fmi/data/v1/databases/\(encoded(database))" + return "https://\(host):\(port)/fmi/data/v1/databases/\(encoded(database))" } func encoded(_ segment: String) -> String { diff --git a/Tests/PerfectFileMakerDataAPITests/PerfectFileMakerDataAPITests.swift b/Tests/PerfectFileMakerDataAPITests/PerfectFileMakerDataAPITests.swift index d74f776..0611953 100644 --- a/Tests/PerfectFileMakerDataAPITests/PerfectFileMakerDataAPITests.swift +++ b/Tests/PerfectFileMakerDataAPITests/PerfectFileMakerDataAPITests.swift @@ -1,5 +1,8 @@ import Testing import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif @testable import PerfectFileMakerDataAPI // MARK: - FMPQuery @@ -187,3 +190,51 @@ import Foundation #expect(FMPFieldType("calculation") == .unknown) } } + +// MARK: - Transport + +/// Records each request's URL and answers with HTTP 500, so a call fails right after its first request. +final class RecordingURLProtocol: URLProtocol { + nonisolated(unsafe) private static var urls: [URL] = [] + private static let lock = NSLock() + + static func reset() { lock.withLock { urls = [] } } + static func recorded() -> [URL] { lock.withLock { urls } } + + override class func canInit(with request: URLRequest) -> Bool { true } + override class func canonicalRequest(for request: URLRequest) -> URLRequest { request } + + override func startLoading() { + if let url = request.url { + Self.lock.withLock { Self.urls.append(url) } + } + let response = HTTPURLResponse(url: request.url!, statusCode: 500, httpVersion: nil, headerFields: nil)! + client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) + client?.urlProtocol(self, didLoad: Data("{}".utf8)) + client?.urlProtocolDidFinishLoading(self) + } + + override func stopLoading() {} +} + +@Suite("Transport", .serialized) struct TransportTests { + + @Test(arguments: [443, 8443, 80]) + func everyRequestUsesHTTPS(port: Int) async { + let config = URLSessionConfiguration.ephemeral + config.protocolClasses = [RecordingURLProtocol.self] + let server = FileMakerServer(host: "fm.example.com", port: port, database: "My DB", + username: "u", password: "p", + urlSession: URLSession(configuration: config)) + RecordingURLProtocol.reset() + + #expect(server.baseURL.hasPrefix("https://fm.example.com:\(port)/")) + _ = try? await server.databaseNames() // no session needed + _ = try? await server.getRecords(layout: "L") // logs in first + + let urls = RecordingURLProtocol.recorded() + #expect(urls.count == 2) + #expect(urls.allSatisfy { $0.scheme == "https" && $0.port == port }) + #expect(urls.last?.path == "/fmi/data/v1/databases/My DB/sessions") + } +}