96 lines
3.8 KiB
Swift
96 lines
3.8 KiB
Swift
import XCTest
|
|
@testable import TVAnarchyCore
|
|
|
|
/// Pure-parser coverage for the ECP XML shapes — no device needed. Samples
|
|
/// mirror real Roku OS responses (the live stick on the LAN returns these
|
|
/// shapes; see also the SSDP `roku:ecp` discovery in docs/operations.md).
|
|
final class RokuTargetTests: XCTestCase {
|
|
|
|
func testMediaPlayerPlaying() {
|
|
let xml = """
|
|
<?xml version="1.0" encoding="UTF-8" ?>
|
|
<player error="false" state="play">
|
|
\t<plugin bandwidth="10000000 bps" id="13535" name="Plex - Free Movies & TV"/>
|
|
\t<format audio="aac" captions="none" container="hls" drm="none" video="hevc"/>
|
|
\t<buffering current="1000" max="1000" target="0"/>
|
|
\t<new_stream speed="128000 bps"/>
|
|
\t<position>62333 ms</position>
|
|
\t<duration>1809333 ms</duration>
|
|
\t<is_live>false</is_live>
|
|
</player>
|
|
"""
|
|
let s = RokuTarget.parseMediaPlayer(xml)
|
|
XCTAssertTrue(s.playing)
|
|
XCTAssertEqual(s.paused, false)
|
|
XCTAssertEqual(s.title, "Plex - Free Movies & TV")
|
|
XCTAssertEqual(s.position ?? 0, 62.333, accuracy: 0.001)
|
|
XCTAssertEqual(s.duration ?? 0, 1809.333, accuracy: 0.001)
|
|
}
|
|
|
|
func testMediaPlayerPaused() {
|
|
let xml = #"<player error="false" state="pause"><plugin id="12" name="Netflix"/><position>5000 ms</position></player>"#
|
|
let s = RokuTarget.parseMediaPlayer(xml)
|
|
XCTAssertFalse(s.playing)
|
|
XCTAssertEqual(s.paused, true)
|
|
XCTAssertEqual(s.title, "Netflix")
|
|
XCTAssertEqual(s.position ?? 0, 5.0, accuracy: 0.001)
|
|
XCTAssertNil(s.duration)
|
|
}
|
|
|
|
/// Home screen / idle: state="none", no plugin → an idle status, no title.
|
|
func testMediaPlayerIdle() {
|
|
let xml = #"<player error="false" state="none"/>"#
|
|
let s = RokuTarget.parseMediaPlayer(xml)
|
|
XCTAssertFalse(s.playing)
|
|
XCTAssertEqual(s.paused, false)
|
|
XCTAssertNil(s.title)
|
|
XCTAssertNil(s.position)
|
|
}
|
|
|
|
/// Garbage in → idle out, never a crash (the poll treats reachable-but-weird
|
|
/// as "reachable, unclear state").
|
|
func testMediaPlayerGarbage() {
|
|
let s = RokuTarget.parseMediaPlayer("not xml at all")
|
|
XCTAssertFalse(s.playing)
|
|
XCTAssertNil(s.title)
|
|
}
|
|
|
|
func testActiveAppChannel() {
|
|
let xml = """
|
|
<active-app>
|
|
\t<app id="837" type="appl" version="5.0.92">YouTube</app>
|
|
</active-app>
|
|
"""
|
|
let app = RokuTarget.parseActiveApp(xml)
|
|
XCTAssertEqual(app?.name, "YouTube")
|
|
XCTAssertEqual(app?.isHome, false)
|
|
}
|
|
|
|
/// The home screen reports type="home" — the poll must NOT surface it as a
|
|
/// now-playing title.
|
|
func testActiveAppHome() {
|
|
let xml = #"<active-app><app id="562859" type="home" version="14.10.5" ui-location="home">Roku Dynamic Menu</app></active-app>"#
|
|
let app = RokuTarget.parseActiveApp(xml)
|
|
XCTAssertEqual(app?.name, "Roku Dynamic Menu")
|
|
XCTAssertEqual(app?.isHome, true)
|
|
}
|
|
|
|
func testActiveAppMissing() {
|
|
XCTAssertNil(RokuTarget.parseActiveApp("<active-app></active-app>"))
|
|
}
|
|
|
|
/// Config decode: minimal `{ "host": … }` gets the ECP default port; a
|
|
/// device entry round-trips with the roku payload.
|
|
func testRokuConnDecodeDefaults() throws {
|
|
let conn = try JSONDecoder().decode(RokuConn.self, from: Data(#"{"host":"10.0.0.233"}"#.utf8))
|
|
XCTAssertEqual(conn.host, "10.0.0.233")
|
|
XCTAssertEqual(conn.port, 8060)
|
|
|
|
let dev = try JSONDecoder().decode(DeviceConfig.self, from: Data("""
|
|
{"id":"roku-tv","name":"Roku Stick","kind":"roku","roku":{"host":"10.0.0.233"}}
|
|
""".utf8))
|
|
XCTAssertEqual(dev.kind, .roku)
|
|
XCTAssertEqual(dev.roku?.port, 8060)
|
|
XCTAssertEqual(dev.type, .cellphone) // inferred: fleet "consumer"
|
|
}
|
|
}
|