Skip to content

Commit

Permalink
Merge pull request #2 from thanek/tests
Browse files Browse the repository at this point in the history
Tests
thanek authored Oct 17, 2023

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
2 parents ffa313b + 7241271 commit b05b026
Showing 43 changed files with 1,091 additions and 110 deletions.
38 changes: 36 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ plugins {
id 'org.jetbrains.kotlin.jvm' version '1.8.22'
id 'org.jetbrains.kotlin.plugin.spring' version '1.8.22'
id "org.jetbrains.kotlin.plugin.jpa" version '1.8.22'
id 'groovy'
}

group = 'net.schowek'
@@ -23,6 +24,11 @@ repositories {
mavenCentral()
}

configurations {
integrationTestImplementation.extendsFrom(testImplementation)
integrationTestRuntimeOnly.extendsFrom(testRuntimeOnly)
}

dependencies {
implementation('org.springframework.boot:spring-boot-starter-web') {
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
@@ -40,10 +46,17 @@ dependencies {
implementation 'org.jupnp:org.jupnp.support:2.7.1'
implementation 'org.apache.httpcomponents:httpclient:4.5.14'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.apache.groovy:groovy:4.0.15'

testImplementation('org.spockframework:spock-core:2.4-M1-groovy-4.0')
testImplementation('org.spockframework:spock-spring:2.4-M1-groovy-4.0')
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
testImplementation('com.h2database:h2')
}

tasks.withType(KotlinCompile) {
tasks.withType(KotlinCompile).configureEach {
kotlinOptions {
freeCompilerArgs += '-Xjsr305=strict'
jvmTarget = '17'
@@ -53,3 +66,24 @@ tasks.withType(KotlinCompile) {
tasks.named('test') {
useJUnitPlatform()
}

sourceSets {
integrationTest {
groovy.srcDir "$projectDir/src/integration/groovy"
resources.srcDir "$projectDir/src/integration/resources"
compileClasspath += main.output + test.output
runtimeClasspath += main.output + test.output
}
}

tasks.register('integrationTest', Test) {
useJUnitPlatform()
description = "Run integration tests"
group = "verification"
testClassesDirs = sourceSets.integrationTest.output.classesDirs
classpath = sourceSets.integrationTest.runtimeClasspath
mustRunAfter tasks.test
}

check.dependsOn integrationTest

Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package net.schowek.nextclouddlna.controller


import net.schowek.nextclouddlna.nextcloud.content.ContentTreeProvider
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import support.IntegrationSpecification

class ContentControllerIntTest extends IntegrationSpecification {
@Autowired
ContentTreeProvider contentTreeProvider

def "should serve nextcloud files"() {
given:
def items = contentTreeProvider.tree.items

when:
Map<Integer, ResponseEntity<byte[]>> results = items.keySet().collectEntries() {
[Integer.valueOf(it), restTemplate().getForEntity(urlWithPort("/c/$it"), byte[])]
}

then:
items.values().each { item ->
assert results.containsKey(item.id)
with(results.get(item.id)) { response ->
response.statusCode == HttpStatus.OK
with(response.headers.each { it.key.toLowerCase() }) {
assert it['content-type'] == [item.format.mime]
assert it['accept-ranges'] == ["bytes"]
assert it.containsKey('contentfeatures.dlna.org')
assert it.containsKey('transfermode.dlna.org')
assert it.containsKey('realtimeinfo.dlna.org')
}
response.body.length == item.fileLength
}
}
}

def "should return 404 if content does not exist"() {
when:
ResponseEntity<byte[]> response = restTemplate().getForEntity(urlWithPort("/c/blah-blah"), byte[]);

then:
response.statusCode == HttpStatus.NOT_FOUND
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
package net.schowek.nextclouddlna.controller


import net.schowek.nextclouddlna.dlna.media.MediaServer
import org.jupnp.support.model.DIDLObject
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.HttpStatus
import support.UpnpAwareSpecification

import static org.jupnp.support.model.Protocol.HTTP_GET
import static org.jupnp.support.model.WriteStatus.NOT_WRITABLE

class UpnpControllerIntTest extends UpnpAwareSpecification {

@Autowired
private MediaServer mediaServer

def uid

def setup() {
uid = mediaServer.serviceIdentifier
}

def "should serve icon"() {
when:
def response = restTemplate().getForEntity(urlWithPort("/dev/${uid}/icon.png"), byte[]);

then:
response.statusCode == HttpStatus.OK
with(response.headers.each { it.key.toLowerCase() }) {
assert it['content-type'] == ['application/octet-stream']
}
}

def "should serve service descriptor"() {
when:
def response = restTemplate().getForEntity(urlWithPort("/dev/${uid}/desc"), String);

then:
response.statusCode == HttpStatus.OK
response.headers['content-type'] == ['text/xml']

when:
def dom = createDocument(response)

then:
nodeValue(dom, "/root/device/friendlyName") == "nextcloud-dlna-int-test"
nodeValue(dom, "/root/device/UDN") == "uuid:${uid}"
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
response.headers['content-type'] == ['text/xml']

when:
def dom = createDocument(response)

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
response.headers['content-type'] == ['text/xml']

when:
def dom = createDocument(response)

then:
with(nodesList(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 metadata for ROOT request"() {
given:
def nodeId = "0"
def browseFlag = "BrowseMetadata"

when:
def response = performContentDirectoryAction(nodeId, browseFlag)

then:
response.statusCode == HttpStatus.OK
response.headers['content-type'] == ['text/xml;charset=utf-8']

when:
def didl = extractDIDLFromResponse(response)

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
}
didl.items.size() == 0
}

def "should handle upnp browse direct children for ROOT request"() {
given:
def nodeId = "0"
def browseFlag = "BrowseDirectChildren"

when:
def response = performContentDirectoryAction(nodeId, browseFlag)

then:
response.statusCode == HttpStatus.OK
response.headers['content-type'] == ['text/xml;charset=utf-8']

when:
def didl = extractDIDLFromResponse(response)

then:
didl.containers.size() == 3
didl.containers.each {
assert it.searchable
assert it.restricted
assert it.writeStatus == NOT_WRITABLE
assert it.clazz.value == new DIDLObject.Class("object.container").value
}

with(didl.containers[0]) {
assert id == "2"
assert parentID == "1"
assert title == "johndoe"
assert childCount == 3
}

with(didl.containers[1]) {
assert id == "387"
assert parentID == "384"
assert title == "janedoe"
assert childCount == 1
}

with(didl.containers[2]) {
assert id == "586"
assert parentID == "584"
assert title == "family folder"
assert childCount == 1
}

didl.items.size() == 0
}

def "should handle upnp browse metadata for johndoe's directory request"() {
given:
def nodeId = "2"
def browseFlag = "BrowseMetadata"

when:
def response = performContentDirectoryAction(nodeId, browseFlag)

then:
response.statusCode == HttpStatus.OK
response.headers['content-type'] == ['text/xml;charset=utf-8']

when:
def didl = extractDIDLFromResponse(response)

then:
didl.containers.size() == 1
with(didl.containers[0]) {
assert id == "2"
assert parentID == "1"
assert searchable
assert restricted
assert title == "johndoe"
assert writeStatus == NOT_WRITABLE
assert clazz.value == new DIDLObject.Class("object.container").value
assert childCount == 3
}
didl.items.size() == 0
}

def "should handle upnp browse direct children for johndoe's directory request"() {
given:
def nodeId = "2"
def browseFlag = "BrowseDirectChildren"

when:
def response = performContentDirectoryAction(nodeId, browseFlag)

then:
response.statusCode == HttpStatus.OK
response.headers['content-type'] == ['text/xml;charset=utf-8']

when:
def didl = extractDIDLFromResponse(response)

then:
didl.containers.size() == 1
with(didl.containers[0]) {
assert id == "15"
assert parentID == "2"
assert title == "photos"
assert childCount == 2
assert searchable
assert restricted
assert writeStatus == NOT_WRITABLE
assert clazz.value == new DIDLObject.Class("object.container").value
}

didl.items.size() == 2
with(didl.items[0]) {
assert it.id == "13"
assert it.parentID == "2"
assert title == "Nextcloud intro.mp4"
assert !restricted

with(resources[0]) {
assert protocolInfo.contentFormat == "video/mp4"
assert protocolInfo.protocol == HTTP_GET
assert size == 3963036
assert value == urlWithPort("/c/13")
}

with(resources[1]) { thumbnail ->
assert thumbnail.protocolInfo.contentFormat == "image/jpeg"
assert thumbnail.protocolInfo.protocol == HTTP_GET
assert thumbnail.protocolInfo.additionalInfo == "DLNA.ORG_PN=JPEG_TN"
assert thumbnail.size == 28820
assert thumbnail.value == urlWithPort("/c/273")
}
}

with(didl.items[1]) {
assert it.id == "14"
assert it.parentID == "2"
assert title == "Nextcloud.png"
assert !restricted

with(resources[0]) {
assert protocolInfo.contentFormat == "image/png"
assert protocolInfo.protocol == HTTP_GET
assert size == 50598
assert value == urlWithPort("/c/14")
}

with(resources[1]) { thumbnail ->
assert thumbnail.protocolInfo.contentFormat == "image/png"
assert thumbnail.protocolInfo.protocol == HTTP_GET
assert thumbnail.protocolInfo.additionalInfo == "DLNA.ORG_PN=PNG_TN"
assert thumbnail.size == 50545
assert thumbnail.value == urlWithPort("/c/164")
}
}
}
}
Loading

0 comments on commit b05b026

Please sign in to comment.