upnp messages tests
This commit is contained in:
parent
e7332d94b3
commit
e2c23b80e6
2 changed files with 127 additions and 20 deletions
|
@ -1,32 +1,43 @@
|
||||||
package net.schowek.nextclouddlna.controller
|
package net.schowek.nextclouddlna.controller
|
||||||
|
|
||||||
|
import net.schowek.nextclouddlna.DlnaService
|
||||||
import net.schowek.nextclouddlna.dlna.media.MediaServer
|
import net.schowek.nextclouddlna.dlna.media.MediaServer
|
||||||
|
import org.jupnp.support.contentdirectory.DIDLParser
|
||||||
|
import org.jupnp.support.model.DIDLObject
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
import org.springframework.http.HttpEntity
|
||||||
|
import org.springframework.http.HttpHeaders
|
||||||
import org.springframework.http.HttpStatus
|
import org.springframework.http.HttpStatus
|
||||||
import org.springframework.http.ResponseEntity
|
|
||||||
import org.w3c.dom.Document
|
import org.w3c.dom.Document
|
||||||
import org.w3c.dom.Node
|
import org.w3c.dom.Node
|
||||||
|
import org.w3c.dom.NodeList
|
||||||
import org.xml.sax.InputSource
|
import org.xml.sax.InputSource
|
||||||
import support.IntegrationSpecification
|
import support.IntegrationSpecification
|
||||||
|
|
||||||
import javax.xml.parsers.DocumentBuilderFactory
|
import javax.xml.parsers.DocumentBuilderFactory
|
||||||
import javax.xml.xpath.XPath
|
import javax.xml.xpath.XPath
|
||||||
import javax.xml.xpath.XPathConstants
|
|
||||||
import javax.xml.xpath.XPathFactory
|
import javax.xml.xpath.XPathFactory
|
||||||
|
|
||||||
import static javax.xml.xpath.XPathConstants.NODE
|
import static javax.xml.xpath.XPathConstants.NODE
|
||||||
|
import static javax.xml.xpath.XPathConstants.NODESET
|
||||||
|
import static org.jupnp.support.model.WriteStatus.NOT_WRITABLE
|
||||||
|
|
||||||
class UpnpControllerIntTest extends IntegrationSpecification {
|
class UpnpControllerIntTest extends IntegrationSpecification {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private MediaServer mediaServer
|
private MediaServer mediaServer
|
||||||
|
@Autowired
|
||||||
|
private DlnaService dlnaService
|
||||||
|
|
||||||
|
def uid
|
||||||
|
|
||||||
|
def setup() {
|
||||||
|
uid = mediaServer.serviceIdentifier
|
||||||
|
}
|
||||||
|
|
||||||
def "should serve icon"() {
|
def "should serve icon"() {
|
||||||
given:
|
|
||||||
def uid = mediaServer.serviceIdentifier
|
|
||||||
|
|
||||||
when:
|
when:
|
||||||
ResponseEntity<byte[]> response = restTemplate().getForEntity(urlWithPort("/dev/${uid}/icon.png"), byte[]);
|
def response = restTemplate().getForEntity(urlWithPort("/dev/${uid}/icon.png"), byte[]);
|
||||||
|
|
||||||
then:
|
then:
|
||||||
response.statusCode == HttpStatus.OK
|
response.statusCode == HttpStatus.OK
|
||||||
|
@ -36,11 +47,8 @@ class UpnpControllerIntTest extends IntegrationSpecification {
|
||||||
}
|
}
|
||||||
|
|
||||||
def "should serve service descriptor"() {
|
def "should serve service descriptor"() {
|
||||||
given:
|
|
||||||
def uid = mediaServer.serviceIdentifier
|
|
||||||
|
|
||||||
when:
|
when:
|
||||||
ResponseEntity<String> response = restTemplate().getForEntity(urlWithPort("/dev/${uid}/desc"), String);
|
def response = restTemplate().getForEntity(urlWithPort("/dev/${uid}/desc"), String);
|
||||||
|
|
||||||
then:
|
then:
|
||||||
response.statusCode == HttpStatus.OK
|
response.statusCode == HttpStatus.OK
|
||||||
|
@ -48,6 +56,7 @@ class UpnpControllerIntTest extends IntegrationSpecification {
|
||||||
assert it['content-type'] == ['text/xml']
|
assert it['content-type'] == ['text/xml']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
when:
|
||||||
Document dom = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(
|
Document dom = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(
|
||||||
new InputSource(new StringReader(response.body))
|
new InputSource(new StringReader(response.body))
|
||||||
);
|
);
|
||||||
|
@ -58,8 +67,113 @@ class UpnpControllerIntTest extends IntegrationSpecification {
|
||||||
nodeValue(dom, "/root/device/presentationURL") == urlWithPort()
|
nodeValue(dom, "/root/device/presentationURL") == urlWithPort()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def "should serve content directory desc"() {
|
||||||
|
when:
|
||||||
|
def response = restTemplate().getForEntity(urlWithPort("/dev/${uid}/svc/upnp-org/ContentDirectory/desc"), String);
|
||||||
|
|
||||||
|
then:
|
||||||
|
response.statusCode == HttpStatus.OK
|
||||||
|
with(response.headers.each { it.key.toLowerCase() }) {
|
||||||
|
assert it['content-type'] == ['text/xml']
|
||||||
|
}
|
||||||
|
|
||||||
|
when:
|
||||||
|
Document dom = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(
|
||||||
|
new InputSource(new StringReader(response.body))
|
||||||
|
);
|
||||||
|
|
||||||
|
then:
|
||||||
|
with(node(dom, "/scpd/serviceStateTable/stateVariable[name='A_ARG_TYPE_BrowseFlag']/allowedValueList").childNodes) {
|
||||||
|
assert it.length == 2
|
||||||
|
assert it.item(0).textContent == "BrowseMetadata"
|
||||||
|
assert it.item(1).textContent == "BrowseDirectChildren"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def "should serve connectionMgr desc"() {
|
||||||
|
when:
|
||||||
|
def response = restTemplate().getForEntity(urlWithPort("/dev/${uid}/svc/upnp-org/ConnectionManager/desc"), String);
|
||||||
|
|
||||||
|
then:
|
||||||
|
response.statusCode == HttpStatus.OK
|
||||||
|
with(response.headers.each { it.key.toLowerCase() }) {
|
||||||
|
assert it['content-type'] == ['text/xml']
|
||||||
|
}
|
||||||
|
|
||||||
|
when:
|
||||||
|
Document dom = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(
|
||||||
|
new InputSource(new StringReader(response.body))
|
||||||
|
);
|
||||||
|
|
||||||
|
then:
|
||||||
|
with(nodeList(dom, "/scpd/actionList/action/name")) {
|
||||||
|
assert it.length == 3
|
||||||
|
it.item(0).textContent == "GetCurrentConnectionIDs"
|
||||||
|
it.item(1).textContent == "GetProtocolInfo"
|
||||||
|
it.item(2).textContent == "GetCurrentConnectionInfo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def "should handle upnp browse ROOT request"() {
|
||||||
|
given:
|
||||||
|
def requestBody = '<?xml version="1.0" encoding="utf-8"?>\n' +
|
||||||
|
'<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"\n' +
|
||||||
|
' s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">\n' +
|
||||||
|
' <s:Body>\n' +
|
||||||
|
' <u:Browse xmlns:u="urn:schemas-upnp-org:service:ContentDirectory:1">\n' +
|
||||||
|
' <ObjectID>0</ObjectID>\n' +
|
||||||
|
' <BrowseFlag>BrowseMetadata</BrowseFlag>\n' +
|
||||||
|
' <Filter>*</Filter>\n' +
|
||||||
|
' <StartingIndex>0</StartingIndex>\n' +
|
||||||
|
' <RequestedCount>0</RequestedCount>\n' +
|
||||||
|
' <SortCriteria></SortCriteria>\n' +
|
||||||
|
' </u:Browse>\n' +
|
||||||
|
' </s:Body>\n' +
|
||||||
|
'</s:Envelope>'
|
||||||
|
|
||||||
|
when:
|
||||||
|
def headers = new HttpHeaders([
|
||||||
|
'content-type': 'text/xml; charset="utf-8"',
|
||||||
|
'soapaction' : '"urn:schemas-upnp-org:service:ContentDirectory:1#Browse"'
|
||||||
|
]);
|
||||||
|
HttpEntity<String> request = new HttpEntity<String>(requestBody, headers);
|
||||||
|
def response = restTemplate().postForEntity(urlWithPort("/dev/$uid/svc/upnp-org/ContentDirectory/action"), request, String)
|
||||||
|
|
||||||
|
then:
|
||||||
|
response.statusCode == HttpStatus.OK
|
||||||
|
response.headers['content-type'].find() == "text/xml;charset=utf-8"
|
||||||
|
|
||||||
|
when:
|
||||||
|
Document dom = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(
|
||||||
|
new InputSource(new StringReader(response.body))
|
||||||
|
);
|
||||||
|
def didl = new DIDLParser().parse(nodeValue(dom, "/Envelope/Body/BrowseResponse/Result"))
|
||||||
|
|
||||||
|
then:
|
||||||
|
didl.containers.size() == 1
|
||||||
|
with(didl.containers[0]) {
|
||||||
|
assert id == "0"
|
||||||
|
assert parentID == "-1"
|
||||||
|
assert searchable
|
||||||
|
assert restricted
|
||||||
|
assert title == "ROOT"
|
||||||
|
assert writeStatus == NOT_WRITABLE
|
||||||
|
assert clazz.value == new DIDLObject.Class("object.container").value
|
||||||
|
assert childCount == 3 // johndoe, janedoe, family folder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private String nodeValue(Document dom, String pattern) {
|
private String nodeValue(Document dom, String pattern) {
|
||||||
|
return node(dom, "$pattern/text()").nodeValue
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node node(Document dom, String pattern) {
|
||||||
XPath xpath = XPathFactory.newInstance().newXPath();
|
XPath xpath = XPathFactory.newInstance().newXPath();
|
||||||
return (xpath.evaluate("$pattern/text()", dom, NODE) as Node).nodeValue
|
return (xpath.evaluate(pattern, dom, NODE) as Node)
|
||||||
|
}
|
||||||
|
|
||||||
|
private NodeList nodeList(Document dom, String pattern) {
|
||||||
|
XPath xpath = XPathFactory.newInstance().newXPath();
|
||||||
|
return (xpath.evaluate(pattern, dom, NODESET) as NodeList)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,23 +16,16 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
|
||||||
@SpringBootTest(webEnvironment = DEFINED_PORT)
|
@SpringBootTest(webEnvironment = DEFINED_PORT)
|
||||||
@ActiveProfiles("integration")
|
@ActiveProfiles("integration")
|
||||||
class IntegrationSpecification extends Specification {
|
class IntegrationSpecification extends Specification {
|
||||||
private TestRestTemplate restTemplate = new TestRestTemplate()
|
@Autowired
|
||||||
|
private TestRestTemplate restTemplate
|
||||||
|
|
||||||
// TODO BEAN
|
|
||||||
TestRestTemplate restTemplate() {
|
TestRestTemplate restTemplate() {
|
||||||
if (restTemplate == null) {
|
|
||||||
restTemplate = new TestRestTemplate()
|
|
||||||
}
|
|
||||||
return restTemplate
|
return restTemplate
|
||||||
}
|
}
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ServerInfoProvider serverInfoProvider
|
private ServerInfoProvider serverInfoProvider
|
||||||
|
|
||||||
def setup() {
|
|
||||||
System.err.println("SETUP PARENT")
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String urlWithPort(String uri = "") {
|
protected String urlWithPort(String uri = "") {
|
||||||
return "http://localhost:" + serverInfoProvider.port + uri;
|
return "http://localhost:" + serverInfoProvider.port + uri;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue