| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102 |
- import Foundation
- /// URL protocol that intercepts Chad Music API requests and returns canned responses.
- /// Activated only when `-MockNetwork` launch argument is present.
- final class MockURLProtocol: URLProtocol {
- /// Map of URL path suffixes to (statusCode, responseData)
- static var mockResponses: [String: (Int, Data)] = [:]
- static func registerMockResponses() {
- let stats = """
- {"tracks":1234,"albums":56,"artists":78,"duration":"3d 12h"}
- """
- let categories = """
- [{"item":"Rock","count":42},{"item":"Jazz","count":15},{"item":"Electronic","count":30}]
- """
- let years = """
- [{"item":2024,"count":12},{"item":2023,"count":18},{"item":2012,"count":5}]
- """
- let albums = """
- [{"id":"album-1","album":"Test Album","artist":"Test Artist","year":2024,"genre":"Rock","track_count":10,"cover":null,"publisher":null,"country":null,"type":"Album","status":"Official","total_duration":2400.0,"original_date":null,"mb_id":null},{"id":"album-2","album":"Second Album","artist":"Another Artist","year":2023,"genre":"Jazz","track_count":8,"cover":null,"publisher":null,"country":null,"type":"Album","status":"Official","total_duration":1800.0,"original_date":null,"mb_id":null}]
- """
- let tracks = """
- [{"id":"track-1","title":"First Track","artist":"Test Artist","album_artist":"Test Artist","album":"Test Album","duration":240.0,"no":1,"url":"/api/stream/track-1","bit_rate":320,"year":2024,"cover":null},{"id":"track-2","title":"Second Track","artist":"Test Artist","album_artist":"Test Artist","album":"Test Album","duration":180.0,"no":2,"url":"/api/stream/track-2","bit_rate":320,"year":2024,"cover":null}]
- """
- mockResponses["/api/stats"] = (200, Data(stats.utf8))
- mockResponses["/api/cat/artist"] = (200, Data(categories.utf8))
- mockResponses["/api/cat/genre"] = (200, Data(categories.utf8))
- mockResponses["/api/cat/year"] = (200, Data(years.utf8))
- mockResponses["/api/cat/publisher"] = (200, Data(categories.utf8))
- mockResponses["/api/cat/country"] = (200, Data(categories.utf8))
- mockResponses["/api/cat/type"] = (200, Data(categories.utf8))
- mockResponses["/api/cat/status"] = (200, Data(categories.utf8))
- mockResponses["/api/cat/album"] = (200, Data(albums.utf8))
- mockResponses["/api/albums"] = (200, Data(albums.utf8))
- mockResponses["/api/album/"] = (200, Data(tracks.utf8)) // prefix match for album tracks
- }
- /// Register a custom mock for a specific path (used by tests via launch environment)
- static func setMock(path: String, statusCode: Int, data: Data) {
- mockResponses[path] = (statusCode, data)
- }
- /// Register a malformed response to test error handling
- static func setMalformedMock(path: String) {
- mockResponses[path] = (200, Data("not valid json{{{".utf8))
- }
- // MARK: - URLProtocol overrides
- override class func canInit(with request: URLRequest) -> Bool {
- guard let url = request.url else { return false }
- let path = url.path
- return mockResponses.keys.contains(where: { path.contains($0) })
- }
- override class func canonicalRequest(for request: URLRequest) -> URLRequest {
- request
- }
- override func startLoading() {
- guard let url = request.url else {
- client?.urlProtocol(self, didFailWithError: URLError(.badURL))
- return
- }
- let path = url.path
- var matchedResponse: (Int, Data)?
- // Try exact match first, then prefix match
- if let exact = MockURLProtocol.mockResponses[path] {
- matchedResponse = exact
- } else {
- for (key, value) in MockURLProtocol.mockResponses where path.contains(key) {
- matchedResponse = value
- break
- }
- }
- guard let (statusCode, data) = matchedResponse else {
- client?.urlProtocol(self, didFailWithError: URLError(.fileDoesNotExist))
- return
- }
- let response = HTTPURLResponse(
- url: url,
- statusCode: statusCode,
- httpVersion: "HTTP/1.1",
- headerFields: ["Content-Type": "application/json"]
- )!
- client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
- client?.urlProtocol(self, didLoad: data)
- client?.urlProtocolDidFinishLoading(self)
- }
- override func stopLoading() {}
- }
|