88 lines
3.5 KiB
Swift
88 lines
3.5 KiB
Swift
import Domain
|
|
import Foundation
|
|
@testable import SourceSubsonic
|
|
|
|
struct FixtureTransport: Sendable {
|
|
var droppedAlbum: String?
|
|
|
|
static func fixture(_ name: String) throws -> Data {
|
|
let url = try unwrap(Bundle.module.url(forResource: name, withExtension: "json", subdirectory: "Fixtures"))
|
|
return try Data(contentsOf: url)
|
|
}
|
|
|
|
static func empty(_ key: String) -> Data {
|
|
Data(#"{"subsonic-response":{"status":"ok","version":"1.16.1","\#(key)":{}}}"#.utf8)
|
|
}
|
|
|
|
static func method(of request: URLRequest) -> (String, [String: String]) {
|
|
let url = request.url ?? URL(filePath: "/")
|
|
let method = url.lastPathComponent.replacingOccurrences(of: ".view", with: "")
|
|
let items = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems ?? []
|
|
return (method, Dictionary(items.map { ($0.name, $0.value ?? "") }) { $1 })
|
|
}
|
|
|
|
func callAsFunction(_ request: URLRequest) async throws -> (Data, URLResponse) {
|
|
let (method, params) = Self.method(of: request)
|
|
let response = HTTPURLResponse(url: request.url ?? URL(filePath: "/"), statusCode: 200, httpVersion: nil, headerFields: nil)
|
|
return try (data(for: method, params: params), response ?? URLResponse())
|
|
}
|
|
|
|
private func data(for method: String, params: [String: String]) throws -> Data {
|
|
switch method {
|
|
case "getArtists": try Self.fixture("artists")
|
|
case "search3" where params["songOffset"] == "0": try Self.fixture("search3All")
|
|
case "search3": Self.empty("searchResult3")
|
|
case "getAlbumList2" where params["offset"] == "0": try albums()
|
|
case "getAlbumList2": Self.empty("albumList2")
|
|
case "getPlaylists": try Self.fixture("playlists")
|
|
case "getPlaylist": try Self.fixture("playlist")
|
|
case "getInternetRadioStations": try Self.fixture("radioStations")
|
|
case "getStarred2": try Self.fixture("starred2")
|
|
case "getLyricsBySongId": try Self.fixture("lyricsEmpty")
|
|
default: try Self.fixture("ping")
|
|
}
|
|
}
|
|
|
|
private func albums() throws -> Data {
|
|
let data = try Self.fixture("albumList2All")
|
|
guard let droppedAlbum else { return data }
|
|
var root = try unwrap(JSONSerialization.jsonObject(with: data) as? [String: Any])
|
|
var response = try unwrap(root["subsonic-response"] as? [String: Any])
|
|
var list = try unwrap(response["albumList2"] as? [String: Any])
|
|
let albums = try unwrap(list["album"] as? [[String: Any]])
|
|
list["album"] = albums.filter { $0["id"] as? String != droppedAlbum }
|
|
response["albumList2"] = list
|
|
root["subsonic-response"] = response
|
|
return try JSONSerialization.data(withJSONObject: root)
|
|
}
|
|
}
|
|
|
|
extension SubsonicSource {
|
|
static func fixture(transport: FixtureTransport = FixtureTransport(), id: SourceID = SourceID()) -> SubsonicSource {
|
|
let client = SubsonicClient(
|
|
baseURL: URL(string: "https://music.example.test")!,
|
|
username: "h",
|
|
password: "secret",
|
|
transport: { try await transport($0) }
|
|
)
|
|
return SubsonicSource(id: id, name: "Test", client: client)
|
|
}
|
|
|
|
func collect() async throws -> [Change] {
|
|
var collected: [Change] = []
|
|
for try await change in changes(since: nil) {
|
|
collected.append(change)
|
|
}
|
|
return collected
|
|
}
|
|
}
|
|
|
|
private func unwrap<T>(_ value: T?) throws -> T {
|
|
guard let value else { throw FixtureError.missing }
|
|
return value
|
|
}
|
|
|
|
private enum FixtureError: Error {
|
|
case missing
|
|
}
|