tv-anarchy/Tests/TVAnarchyCoreTests/RokuTargetTests.swift
Natalie ca1871f5dd feat(@applications/tv-anarchy): add roku device support
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-09 21:37:34 -07:00

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 &amp; 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 &amp; 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"
}
}