Apple ha chiuso il treno 2.0.12: servono versione nuova, monogramma visibile sul nero e copertura dei casi dopo l'orario previsto. Co-authored-by: Cursor <cursoragent@cursor.com>
126 lines
4.8 KiB
Swift
126 lines
4.8 KiB
Swift
import XCTest
|
||
@testable import MatchLiveTv
|
||
|
||
/// Logica calendario/API reale + smoke parser (RTMP/overlay) senza dipendenze di rete.
|
||
final class ApiInstantTests: XCTestCase {
|
||
func testParseApiInstantRailsOffsetWithMillis() {
|
||
let now = iso("2026-06-06T16:00:00Z")
|
||
XCTAssertTrue(ApiInstant.isScheduledOnCalendar("2026-06-06T20:00:00.000+02:00", now: now))
|
||
}
|
||
|
||
func testIsScheduledOnCalendarKeepsThisMorningAfterKickoff() {
|
||
let kickoff = "2026-09-06T07:00:00Z"
|
||
let now = iso("2026-09-06T08:30:00Z")
|
||
XCTAssertTrue(ApiInstant.isScheduledOnCalendar(kickoff, now: now))
|
||
XCTAssertFalse(ApiInstant.isScheduledFuture(kickoff, now: now))
|
||
}
|
||
|
||
func testIsScheduledOnCalendarHidesYesterday() {
|
||
let yesterday = "2026-09-05T10:00:00Z"
|
||
let now = iso("2026-09-06T08:30:00Z")
|
||
XCTAssertFalse(ApiInstant.isScheduledOnCalendar(yesterday, now: now))
|
||
}
|
||
|
||
func testConcurrentStreamForbiddenShowsApiMessage() {
|
||
let body = """
|
||
{"error":"Hai già una diretta in corso con questo account. Chiudila prima di avviarne un’altra.","error_code":"user_concurrent_stream"}
|
||
"""
|
||
XCTAssertEqual(
|
||
APIError.http(403, body).errorDescription,
|
||
"Hai già una diretta in corso con questo account. Chiudila prima di avviarne un’altra."
|
||
)
|
||
}
|
||
|
||
private func iso(_ value: String) -> Date {
|
||
let f = ISO8601DateFormatter()
|
||
f.formatOptions = [.withInternetDateTime]
|
||
guard let date = f.date(from: value) else {
|
||
XCTFail("Data ISO non valida: \(value)")
|
||
return Date()
|
||
}
|
||
return date
|
||
}
|
||
func testOverlayKindMapping() {
|
||
XCTAssertEqual(OverlayKindMapping.fromApi("basket"), "basket")
|
||
XCTAssertEqual(OverlayKindMapping.fromApi("none"), "none")
|
||
XCTAssertEqual(OverlayKindMapping.fromApi(nil), "volley")
|
||
}
|
||
|
||
func testRTMPUrlParser() {
|
||
let target = RTMPUrlParserLogic.parse("rtmp://stream.example.com/live/match_abc-123")
|
||
XCTAssertEqual(target?.connectURL, "rtmp://stream.example.com/live")
|
||
XCTAssertEqual(target?.streamName, "match_abc-123")
|
||
}
|
||
|
||
func testRtmpIngestUrlRewritesDockerHostForLocalApi() {
|
||
let resolved = RtmpIngestUrlLogic.resolve(
|
||
"rtmp://mediamtx:1935/live/match_abc",
|
||
apiBaseUrl: "http://localhost:3000"
|
||
)
|
||
XCTAssertEqual(resolved, "rtmp://127.0.0.1:1935/live/match_abc")
|
||
}
|
||
|
||
func testRtmpIngestUrlKeepsProductionHost() {
|
||
let url = "rtmp://www.matchlivetv.it:1935/live/match_abc"
|
||
let resolved = RtmpIngestUrlLogic.resolve(url, apiBaseUrl: "https://www.matchlivetv.it")
|
||
XCTAssertEqual(resolved, url)
|
||
}
|
||
}
|
||
|
||
enum OverlayKindMapping {
|
||
static func fromApi(_ value: String?) -> String {
|
||
switch value?.lowercased() {
|
||
case "none", "timer": return "none"
|
||
case "basket": return "basket"
|
||
case "timed": return "timed"
|
||
case "racket": return "racket"
|
||
default: return "volley"
|
||
}
|
||
}
|
||
}
|
||
|
||
enum RTMPUrlParserLogic {
|
||
struct Target {
|
||
let connectURL: String
|
||
let streamName: String
|
||
}
|
||
|
||
static func parse(_ urlString: String) -> Target? {
|
||
guard let url = URL(string: urlString), url.scheme?.hasPrefix("rtmp") == true else { return nil }
|
||
let pathParts = url.path.split(separator: "/").map(String.init)
|
||
guard pathParts.count >= 2 else { return nil }
|
||
let app = pathParts.dropLast().joined(separator: "/")
|
||
let streamName = pathParts.last ?? ""
|
||
var connect = "rtmp://\(url.host ?? "")"
|
||
if let port = url.port { connect += ":\(port)" }
|
||
connect += "/\(app)"
|
||
return Target(connectURL: connect, streamName: streamName)
|
||
}
|
||
}
|
||
|
||
/// Copia della logica `RtmpIngestUrl` per test senza bundle Info.plist.
|
||
enum RtmpIngestUrlLogic {
|
||
private static let dockerInternalHosts: Set<String> = ["mediamtx", "host.docker.internal"]
|
||
|
||
static func resolve(_ urlString: String, apiBaseUrl: String) -> String {
|
||
guard var components = URLComponents(string: urlString),
|
||
let host = components.host?.lowercased() else {
|
||
return urlString
|
||
}
|
||
let apiHost = URL(string: apiBaseUrl)?.host?.lowercased()
|
||
let isLocalApi = apiHost.map { $0 == "localhost" || $0 == "127.0.0.1" || $0.hasSuffix(".local") } ?? false
|
||
guard dockerInternalHosts.contains(host) || isLocalApi else {
|
||
return urlString
|
||
}
|
||
let ingestHost: String
|
||
if let apiHost, apiHost != "localhost", apiHost != "127.0.0.1" {
|
||
ingestHost = apiHost
|
||
} else {
|
||
ingestHost = "127.0.0.1"
|
||
}
|
||
components.host = ingestHost
|
||
if components.port == nil { components.port = 1935 }
|
||
return components.url?.absoluteString ?? urlString
|
||
}
|
||
}
|