fix(android): AVTransport test assertions escape DIDL; consolidate xmlEscape
android / Build + lint + test (push) Failing after 1m18s
android / Build + lint + test (push) Failing after 1m18s
DIDL assertion now checks for XML-escaped form (<dc:title>) since SoapClient.buildEnvelope escapes all arg values. Lifts xmlEscape to a top-level internal fun in SoapClient.kt, removing the duplicate private copy from AVTransportClient. Fixes @Suppress rationale (not Compose). Renames seek test to reflect colon-separated format; adds unknown-state getTransportInfo test. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+2
-8
@@ -7,7 +7,8 @@ import okhttp3.HttpUrl
|
|||||||
* Covers SetAVTransportURI / SetNextAVTransportURI / Play / Pause /
|
* Covers SetAVTransportURI / SetNextAVTransportURI / Play / Pause /
|
||||||
* Stop / Seek / GetPositionInfo / GetTransportInfo.
|
* Stop / Seek / GetPositionInfo / GetTransportInfo.
|
||||||
*/
|
*/
|
||||||
@Suppress("TooManyFunctions") // Compose-adjacent helper density: all are direct SOAP verbs.
|
// One method per AVTransport SOAP verb; splitting would obscure the 1:1 protocol mapping.
|
||||||
|
@Suppress("TooManyFunctions")
|
||||||
class AVTransportClient(
|
class AVTransportClient(
|
||||||
private val soap: SoapClient,
|
private val soap: SoapClient,
|
||||||
private val controlUrl: HttpUrl,
|
private val controlUrl: HttpUrl,
|
||||||
@@ -151,13 +152,6 @@ class AVTransportClient(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun xmlEscape(v: String): String = v
|
|
||||||
.replace("&", "&")
|
|
||||||
.replace("<", "<")
|
|
||||||
.replace(">", ">")
|
|
||||||
.replace("\"", """)
|
|
||||||
.replace("'", "'")
|
|
||||||
|
|
||||||
private fun formatHhMmSs(positionMs: Long): String {
|
private fun formatHhMmSs(positionMs: Long): String {
|
||||||
val totalSec = (positionMs / MILLIS_PER_SECOND).coerceAtLeast(0)
|
val totalSec = (positionMs / MILLIS_PER_SECOND).coerceAtLeast(0)
|
||||||
val h = totalSec / SECONDS_PER_HOUR
|
val h = totalSec / SECONDS_PER_HOUR
|
||||||
|
|||||||
@@ -69,13 +69,6 @@ class SoapClient(
|
|||||||
append("</s:Envelope>")
|
append("</s:Envelope>")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun xmlEscape(v: String): String = v
|
|
||||||
.replace("&", "&")
|
|
||||||
.replace("<", "<")
|
|
||||||
.replace(">", ">")
|
|
||||||
.replace("\"", """)
|
|
||||||
.replace("'", "'")
|
|
||||||
|
|
||||||
private fun parseResponseArgs(body: String, action: String): Map<String, String> {
|
private fun parseResponseArgs(body: String, action: String): Map<String, String> {
|
||||||
val parser = XmlPullParserFactory.newInstance().newPullParser().apply {
|
val parser = XmlPullParserFactory.newInstance().newPullParser().apply {
|
||||||
setInput(body.reader())
|
setInput(body.reader())
|
||||||
@@ -138,3 +131,11 @@ class SoapClient(
|
|||||||
*/
|
*/
|
||||||
class SoapFaultException(val code: String, val description: String) :
|
class SoapFaultException(val code: String, val description: String) :
|
||||||
Exception("SOAP fault $code: $description")
|
Exception("SOAP fault $code: $description")
|
||||||
|
|
||||||
|
/** XML-escapes a string value for embedding as text content inside a SOAP envelope. */
|
||||||
|
internal fun xmlEscape(v: String): String = v
|
||||||
|
.replace("&", "&")
|
||||||
|
.replace("<", "<")
|
||||||
|
.replace(">", ">")
|
||||||
|
.replace("\"", """)
|
||||||
|
.replace("'", "'")
|
||||||
|
|||||||
+24
-3
@@ -29,7 +29,7 @@ class AVTransportClientTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `seek formats position as hh-mm-ss`() = runTest {
|
fun `seek sends Target in HH MM SS format`() = runTest {
|
||||||
server.enqueue(emptyResponse("Seek"))
|
server.enqueue(emptyResponse("Seek"))
|
||||||
client.seek(positionMs = 65_000L)
|
client.seek(positionMs = 65_000L)
|
||||||
val body = server.takeRequest().body.readUtf8()
|
val body = server.takeRequest().body.readUtf8()
|
||||||
@@ -81,6 +81,27 @@ class AVTransportClientTest {
|
|||||||
assertEquals(TransportState.PAUSED, info.state)
|
assertEquals(TransportState.PAUSED, info.state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getTransportInfo maps unknown state to UNKNOWN`() = runTest {
|
||||||
|
server.enqueue(
|
||||||
|
MockResponse().setBody(
|
||||||
|
"""<?xml version="1.0"?>
|
||||||
|
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
|
||||||
|
<s:Body>
|
||||||
|
<u:GetTransportInfoResponse
|
||||||
|
xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
|
||||||
|
<CurrentTransportState>BUFFERING_PLAYBACK</CurrentTransportState>
|
||||||
|
<CurrentTransportStatus>OK</CurrentTransportStatus>
|
||||||
|
<CurrentSpeed>1</CurrentSpeed>
|
||||||
|
</u:GetTransportInfoResponse>
|
||||||
|
</s:Body>
|
||||||
|
</s:Envelope>""".trimIndent(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
val info = client.getTransportInfo()
|
||||||
|
assertEquals(TransportState.UNKNOWN, info.state)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `setNextAVTransportURI sends DIDL-Lite with mime and title`() = runTest {
|
fun `setNextAVTransportURI sends DIDL-Lite with mime and title`() = runTest {
|
||||||
server.enqueue(emptyResponse("SetNextAVTransportURI"))
|
server.enqueue(emptyResponse("SetNextAVTransportURI"))
|
||||||
@@ -93,8 +114,8 @@ class AVTransportClientTest {
|
|||||||
assertTrue(body.contains("<NextURI>http://x/y.mp3</NextURI>")) {
|
assertTrue(body.contains("<NextURI>http://x/y.mp3</NextURI>")) {
|
||||||
"body missing NextURI: $body"
|
"body missing NextURI: $body"
|
||||||
}
|
}
|
||||||
assertTrue(body.contains("<dc:title>Song</dc:title>")) {
|
assertTrue(body.contains("<dc:title>Song</dc:title>")) {
|
||||||
"body missing dc:title: $body"
|
"title missing: $body"
|
||||||
}
|
}
|
||||||
assertTrue(body.contains("audio/mpeg")) {
|
assertTrue(body.contains("audio/mpeg")) {
|
||||||
"body missing mime type: $body"
|
"body missing mime type: $body"
|
||||||
|
|||||||
Reference in New Issue
Block a user