97 lines
3.7 KiB
Swift
97 lines
3.7 KiB
Swift
//
|
|
// AuthenticationMiddlewareTests.swift
|
|
//
|
|
// Copyright 2026 Brendan Szymanski <hello@bscubed.dev>
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
//
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
//
|
|
|
|
import Foundation
|
|
import HTTPTypes
|
|
import OpenAPIRuntime
|
|
import Testing
|
|
|
|
@testable import LuminateServices
|
|
|
|
/// Records the request a middleware forwarded, so a test can inspect it after the fact.
|
|
private actor RequestRecorder {
|
|
/// The last forwarded request.
|
|
private(set) var request: HTTPRequest?
|
|
|
|
/// Stores a forwarded request.
|
|
///
|
|
/// - Parameter request: The request the middleware passed down the chain.
|
|
func record(_ request: HTTPRequest) {
|
|
self.request = request
|
|
}
|
|
}
|
|
|
|
/// Pins the exact `Authorization` header Jellyfin expects.
|
|
///
|
|
/// Jellyfin rejects the request outright if this string is malformed, and the failure surfaces as a
|
|
/// bare 401 with no explanation, so the format is asserted literally rather than by parsing.
|
|
@Suite struct AuthenticationMiddlewareTests {
|
|
/// Runs `middleware` over a throwaway request and returns the `Authorization` header it set.
|
|
///
|
|
/// - Parameter middleware: The middleware under test.
|
|
/// - Returns: The forwarded request's `Authorization` header, if it set one.
|
|
private func authorizationHeader(from middleware: AuthenticationMiddleware) async throws -> String? {
|
|
let recorder = RequestRecorder()
|
|
let request = HTTPRequest(method: .get, scheme: "http", authority: "localhost", path: "/System/Info/Public")
|
|
_ = try await middleware.intercept(
|
|
request,
|
|
body: nil,
|
|
baseURL: URL(string: "http://localhost")!,
|
|
operationID: "GetPublicSystemInfo"
|
|
) { forwarded, _, _ in
|
|
await recorder.record(forwarded)
|
|
return (HTTPResponse(status: .ok), nil)
|
|
}
|
|
return await recorder.request?.headerFields[.authorization]
|
|
}
|
|
|
|
@Test("Builds the MediaBrowser credential string with the token")
|
|
func authenticatedHeader() async throws {
|
|
let middleware = AuthenticationMiddleware(
|
|
clientName: "Luminate",
|
|
deviceName: "test-device",
|
|
deviceID: "test-device-id",
|
|
version: "0.1.0",
|
|
token: "test-token"
|
|
)
|
|
let header = try await authorizationHeader(from: middleware)
|
|
#expect(
|
|
header
|
|
== #"MediaBrowser Client="Luminate", Device="test-device", DeviceId="test-device-id", Version="0.1.0", Token="test-token""#
|
|
)
|
|
}
|
|
|
|
@Test("Sends an empty token before sign-in rather than omitting the header")
|
|
func unauthenticatedHeader() async throws {
|
|
let middleware = AuthenticationMiddleware(
|
|
clientName: "Luminate",
|
|
deviceName: "test-device",
|
|
deviceID: "test-device-id",
|
|
version: "0.1.0",
|
|
token: nil
|
|
)
|
|
let header = try await authorizationHeader(from: middleware)
|
|
#expect(
|
|
header
|
|
== #"MediaBrowser Client="Luminate", Device="test-device", DeviceId="test-device-id", Version="0.1.0", Token="""#
|
|
)
|
|
}
|
|
}
|