Merge branch 'nitehu-feature/runtime-permissions' into develop

This commit is contained in:
Óscar García Amor 2020-06-17 10:25:35 +02:00
commit d0705cd933
133 changed files with 1512 additions and 886 deletions

View File

@ -12,7 +12,7 @@ jobs:
key: gradle-cache-{{ checksum "dependencies.gradle" }}
- run:
name: clean gradle.properties
command: echo "" > gradle.properties
command: echo -e "android.useAndroidX=true\nandroid.enableJetifier=true\n" > gradle.properties
- run:
name: checkstyle
command: ./gradlew -Pqc ktlintCheck

View File

@ -38,7 +38,7 @@ allprojects {
apply from: 'gradle_scripts/jacoco.gradle'
task wrapper(type: Wrapper) {
wrapper {
gradleVersion(versions.gradle)
distributionType("all")
}

View File

@ -20,14 +20,14 @@ private val artistSerializer get() = object : ObjectSerializer<Artist>(SERIALIZE
item: Artist
) {
output.writeString(item.id)
.writeString(item.name)
.writeString(item.index)
.writeString(item.coverArt)
.apply {
val albumCount = item.albumCount
if (albumCount != null) writeLong(albumCount) else writeNull()
}
.writeInt(item.closeness)
.writeString(item.name)
.writeString(item.index)
.writeString(item.coverArt)
.apply {
val albumCount = item.albumCount
if (albumCount != null) writeLong(albumCount) else writeNull()
}
.writeInt(item.closeness)
}
override fun deserializeObject(

View File

@ -20,9 +20,9 @@ private val indexesSerializer get() = object : ObjectSerializer<Indexes>(SERIALI
) {
val artistListSerializer = getArtistListSerializer()
output.writeLong(item.lastModified)
.writeString(item.ignoredArticles)
.writeObject<MutableList<Artist>>(context, item.shortcuts, artistListSerializer)
.writeObject<MutableList<Artist>>(context, item.artists, artistListSerializer)
.writeString(item.ignoredArticles)
.writeObject<MutableList<Artist>>(context, item.shortcuts, artistListSerializer)
.writeObject<MutableList<Artist>>(context, item.artists, artistListSerializer)
}
override fun deserializeObject(
@ -37,8 +37,10 @@ private val indexesSerializer get() = object : ObjectSerializer<Indexes>(SERIALI
val ignoredArticles = input.readString() ?: return null
val shortcutsList = input.readObject(context, artistListDeserializer) ?: return null
val artistsList = input.readObject(context, artistListDeserializer) ?: return null
return Indexes(lastModified, ignoredArticles, shortcutsList.toMutableList(),
artistsList.toMutableList())
return Indexes(
lastModified, ignoredArticles, shortcutsList.toMutableList(),
artistsList.toMutableList()
)
}
}

View File

@ -41,10 +41,10 @@ private val musicFolderSerializer = object : ObjectSerializer<MusicFolder>(SERIA
fun getMusicFolderSerializer(): DomainEntitySerializer<MusicFolder> = musicFolderSerializer
private val musicFolderListSerializer =
CollectionSerializers.getListSerializer(musicFolderSerializer)
CollectionSerializers.getListSerializer(musicFolderSerializer)
/**
* Serializer/deserializer for [List] of [MusicFolder] items.
*/
fun getMusicFolderListSerializer(): DomainEntitySerializer<List<MusicFolder>> =
musicFolderListSerializer
musicFolderListSerializer

View File

@ -2,11 +2,11 @@ package org.moire.ultrasonic.cache
import com.nhaarman.mockito_kotlin.mock
import com.twitter.serial.util.SerializationUtils
import java.io.File
import org.amshove.kluent.`it returns`
import org.junit.Before
import org.junit.Rule
import org.junit.rules.TemporaryFolder
import java.io.File
internal const val INTERNAL_DATA_FOLDER = "data"
internal const val INTERNAL_CACHE_FOLDER = "cache"

View File

@ -1,12 +1,12 @@
package org.moire.ultrasonic.cache
import java.io.File
import org.amshove.kluent.`should be equal to`
import org.amshove.kluent.`should contain`
import org.amshove.kluent.`should equal`
import org.junit.Test
import org.moire.ultrasonic.cache.serializers.getMusicFolderSerializer
import org.moire.ultrasonic.domain.MusicFolder
import java.io.File
/**
* Integration test for [PermanentFileStorage].

View File

@ -32,8 +32,8 @@ class ArtistSerializerTest : BaseStorageTest() {
@Test
fun `Should correctly serialize list of Artists`() {
val itemsList = listOf(
Artist(id = "1"),
Artist(id = "2", name = "some")
Artist(id = "1"),
Artist(id = "2", name = "some")
)
storage.store("some-name", itemsList, getArtistListSerializer())
@ -45,8 +45,8 @@ class ArtistSerializerTest : BaseStorageTest() {
fun `Should correctly deserialize list of Artists`() {
val name = "some-name"
val itemsList = listOf(
Artist(id = "1"),
Artist(id = "2", name = "some")
Artist(id = "1"),
Artist(id = "2", name = "some")
)
storage.store(name, itemsList, getArtistListSerializer())

View File

@ -12,11 +12,10 @@ import org.moire.ultrasonic.domain.Indexes
class IndexesSerializerTest : BaseStorageTest() {
@Test
fun `Should correctly serialize Indexes object`() {
val item = Indexes(220L, "", mutableListOf(
Artist("12")
), mutableListOf(
Artist("233", "some")
))
val item = Indexes(
220L, "", mutableListOf(Artist("12")),
mutableListOf(Artist("233", "some"))
)
storage.store("some-name", item, getIndexesSerializer())
@ -26,11 +25,10 @@ class IndexesSerializerTest : BaseStorageTest() {
@Test
fun `Should correctly deserialize Indexes object`() {
val name = "some-name"
val item = Indexes(220L, "", mutableListOf(
Artist("12")
), mutableListOf(
Artist("233", "some")
))
val item = Indexes(
220L, "", mutableListOf(Artist("12")),
mutableListOf(Artist("233", "some"))
)
storage.store(name, item, getIndexesSerializer())
val loadedItem = storage.load(name, getIndexesSerializer())

View File

@ -32,8 +32,8 @@ class MusicFolderSerializerTest : BaseStorageTest() {
@Test
fun `Should correctly serialize list of MusicFolders objects`() {
val itemsList = listOf(
MusicFolder("1", "1"),
MusicFolder("2", "2")
MusicFolder("1", "1"),
MusicFolder("2", "2")
)
storage.store("some-name", itemsList, getMusicFolderListSerializer())
@ -45,8 +45,8 @@ class MusicFolderSerializerTest : BaseStorageTest() {
fun `Should correctly deserialize list of MusicFolder objects`() {
val name = "some-name"
val itemsList = listOf(
MusicFolder("1", "1"),
MusicFolder("2", "2")
MusicFolder("1", "1"),
MusicFolder("2", "2")
)
storage.store(name, itemsList, getMusicFolderListSerializer())

View File

@ -1,9 +1,8 @@
package org.moire.ultrasonic.domain
import org.moire.ultrasonic.domain.MusicDirectory.Entry
import java.io.Serializable
import java.util.Date
import org.moire.ultrasonic.domain.MusicDirectory.Entry
data class Bookmark(
val position: Int = 0,

View File

@ -1,7 +1,7 @@
package org.moire.ultrasonic.domain
import org.moire.ultrasonic.domain.MusicDirectory.Entry
import java.io.Serializable
import org.moire.ultrasonic.domain.MusicDirectory.Entry
data class Share(
var id: String? = null,

View File

@ -7,7 +7,7 @@ import android.database.Cursor;
import android.util.SparseIntArray;
import android.view.View;
import android.view.ViewGroup;
import android.support.v4.widget.CursorAdapter;
import androidx.cursoradapter.widget.CursorAdapter;
/**

View File

@ -93,7 +93,7 @@ final class ActionBarHelperCompat {
Class supportActivity = activity.getClass();
Method getActionBar = supportActivity.getMethod("getSupportActionBar");
mActionBar = getActionBar.invoke(activity, null);
mActionBar = getActionBar.invoke(activity, (Object)null);
Class supportActionBar = mActionBar.getClass();
mHomeAsUpEnabled = supportActionBar.getMethod("setDisplayHomeAsUpEnabled", Boolean.TYPE);

View File

@ -1,7 +1,7 @@
package org.moire.ultrasonic.subsonic.loader.image
import okio.Okio
import java.io.InputStream
import okio.Okio
fun Any.loadResourceStream(name: String): InputStream {
val source = Okio.buffer(Okio.source(javaClass.classLoader.getResourceAsStream(name)))

View File

@ -7,6 +7,7 @@ import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever
import com.squareup.picasso.Picasso
import com.squareup.picasso.Request
import java.io.IOException
import org.amshove.kluent.`should equal`
import org.amshove.kluent.`should not be`
import org.amshove.kluent.`should throw`
@ -16,7 +17,6 @@ import org.junit.runner.RunWith
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
import org.moire.ultrasonic.api.subsonic.response.StreamResponse
import org.robolectric.RobolectricTestRunner
import java.io.IOException
@RunWith(RobolectricTestRunner::class)
class CoverArtRequestHandlerTest {

View File

@ -3,9 +3,9 @@ package org.moire.ultrasonic.subsonic.loader.image
import com.squareup.picasso.Picasso
import com.squareup.picasso.Request
import com.squareup.picasso.RequestHandler
import java.io.IOException
import okio.Okio
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
import java.io.IOException
/**
* Loads avatars from subsonic api.

View File

@ -3,9 +3,9 @@ package org.moire.ultrasonic.subsonic.loader.image
import com.squareup.picasso.Picasso.LoadedFrom.NETWORK
import com.squareup.picasso.Request
import com.squareup.picasso.RequestHandler
import java.io.IOException
import okio.Okio
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
import java.io.IOException
/**
* Loads cover arts from subsonic api.
@ -14,8 +14,8 @@ class CoverArtRequestHandler(private val apiClient: SubsonicAPIClient) : Request
override fun canHandleRequest(data: Request): Boolean {
return with(data.uri) {
scheme == SCHEME &&
authority == AUTHORITY &&
path == "/$COVER_ART_PATH"
authority == AUTHORITY &&
path == "/$COVER_ART_PATH"
}
}

View File

@ -3,7 +3,7 @@ package org.moire.ultrasonic.subsonic.loader.image
import android.net.Uri
internal const val SCHEME = "subsonic_api"
internal const val AUTHORITY = BuildConfig.APPLICATION_ID
internal const val AUTHORITY = BuildConfig.LIBRARY_PACKAGE_NAME
internal const val COVER_ART_PATH = "cover_art"
internal const val AVATAR_PATH = "avatar"
internal const val QUERY_ID = "id"

View File

@ -1,5 +1,11 @@
package org.moire.ultrasonic.api.subsonic
import java.io.InputStream
import java.nio.charset.Charset
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
import java.util.TimeZone
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import okio.Okio
@ -9,30 +15,29 @@ import org.amshove.kluent.`should not be`
import org.moire.ultrasonic.api.subsonic.response.SubsonicResponse
import org.moire.ultrasonic.api.subsonic.rules.MockWebServerRule
import retrofit2.Response
import java.io.InputStream
import java.nio.charset.Charset
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
import java.util.TimeZone
const val USERNAME = "some-user"
const val PASSWORD = "some-password"
val CLIENT_VERSION = SubsonicAPIVersions.V1_16_0
const val CLIENT_ID = "test-client"
val dateFormat by lazy(LazyThreadSafetyMode.NONE, {
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US)
})
val dateFormat by lazy(
LazyThreadSafetyMode.NONE,
{
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US)
}
)
fun MockWebServerRule.enqueueResponse(resourceName: String) {
mockWebServer.enqueueResponse(resourceName)
}
fun MockWebServer.enqueueResponse(resourceName: String) {
enqueue(MockResponse()
enqueue(
MockResponse()
.setBody(loadJsonResponse(resourceName))
.setHeader("Content-Type", "application/json;charset=UTF-8"))
.setHeader("Content-Type", "application/json;charset=UTF-8")
)
}
fun Any.loadJsonResponse(name: String): String {

View File

@ -31,7 +31,7 @@ class GetStreamUrlTest {
client = SubsonicAPIClient(config)
val baseExpectedUrl = mockWebServerRule.mockWebServer.url("").toString()
expectedUrl = "$baseExpectedUrl/rest/stream.view?id=$id&u=$USERNAME" +
"&c=$CLIENT_ID&f=json&v=${V1_6_0.restApiVersion}&p=enc:${PASSWORD.toHexBytes()}"
"&c=$CLIENT_ID&f=json&v=${V1_6_0.restApiVersion}&p=enc:${PASSWORD.toHexBytes()}"
}
@Test

View File

@ -26,8 +26,10 @@ class SubsonicApiCreatePlaylistTest : SubsonicAPIClientTest() {
fun `Should pass id param in request`() {
val id = "56"
mockWebServerRule.assertRequestParam(responseResourceName = "ping_ok.json",
expectedParam = "playlistId=$id") {
mockWebServerRule.assertRequestParam(
responseResourceName = "ping_ok.json",
expectedParam = "playlistId=$id"
) {
client.api.createPlaylist(id = id).execute()
}
}
@ -36,8 +38,10 @@ class SubsonicApiCreatePlaylistTest : SubsonicAPIClientTest() {
fun `Should pass name param in request`() {
val name = "some-name"
mockWebServerRule.assertRequestParam(responseResourceName = "ping_ok.json",
expectedParam = "name=$name") {
mockWebServerRule.assertRequestParam(
responseResourceName = "ping_ok.json",
expectedParam = "name=$name"
) {
client.api.createPlaylist(name = name).execute()
}
}
@ -46,8 +50,10 @@ class SubsonicApiCreatePlaylistTest : SubsonicAPIClientTest() {
fun `Should pass song id param in request`() {
val songId = listOf("4410", "852")
mockWebServerRule.assertRequestParam(responseResourceName = "ping_ok.json",
expectedParam = "songId=${songId[0]}&songId=${songId[1]}") {
mockWebServerRule.assertRequestParam(
responseResourceName = "ping_ok.json",
expectedParam = "songId=${songId[0]}&songId=${songId[1]}"
) {
client.api.createPlaylist(songIds = songId).execute()
}
}

View File

@ -1,10 +1,10 @@
package org.moire.ultrasonic.api.subsonic
import java.util.Calendar
import org.amshove.kluent.`should be equal to`
import org.amshove.kluent.`should equal`
import org.junit.Test
import org.moire.ultrasonic.api.subsonic.models.MusicDirectoryChild
import java.util.Calendar
/**
* Instrumentation test for [SubsonicAPIDefinition.createShare] call.
@ -30,9 +30,9 @@ class SubsonicApiCreateShareTest : SubsonicAPIClientTest() {
with(response.body()!!.shares[0]) {
id `should be equal to` "0"
url `should be equal to` "https://subsonic.com/ext/share/awdwo?jwt=" +
"eyJhbGciOiJIUzI1NiJ9." +
"eyJwYXRoIjoiL2V4dC9zaGFyZS9hd2R3byIsImV4cCI6MTU0MTYyNjQzMX0.iy8dkt_ZZc8hJ692" +
"UxorHdHWFU2RB-fMCmCA4IJ_dTw"
"eyJhbGciOiJIUzI1NiJ9." +
"eyJwYXRoIjoiL2V4dC9zaGFyZS9hd2R3byIsImV4cCI6MTU0MTYyNjQzMX0.iy8dkt_ZZc8hJ692" +
"UxorHdHWFU2RB-fMCmCA4IJ_dTw"
username `should be equal to` "admin"
created `should equal` parseDate("2017-11-07T21:33:51.748Z")
expires `should equal` parseDate("2018-11-07T21:33:51.748Z")
@ -40,20 +40,25 @@ class SubsonicApiCreateShareTest : SubsonicAPIClientTest() {
description `should be equal to` "Awesome link!"
visitCount `should be equal to` 0
items.size `should be equal to` 1
items[0] `should equal` MusicDirectoryChild(id = "4212", parent = "4186", isDir = false,
title = "Heaven Knows", album = "Going to Hell", artist = "The Pretty Reckless",
track = 3, year = 2014, genre = "Hard Rock", coverArt = "4186", size = 9025090,
contentType = "audio/mpeg", suffix = "mp3", duration = 225, bitRate = 320,
path = "The Pretty Reckless/Going to Hell/03 Heaven Knows.mp3", isVideo = false,
playCount = 2, discNumber = 1, created = parseDate("2016-10-23T21:30:40.000Z"),
albumId = "388", artistId = "238", type = "music")
items[0] `should equal` MusicDirectoryChild(
id = "4212", parent = "4186", isDir = false,
title = "Heaven Knows", album = "Going to Hell", artist = "The Pretty Reckless",
track = 3, year = 2014, genre = "Hard Rock", coverArt = "4186", size = 9025090,
contentType = "audio/mpeg", suffix = "mp3", duration = 225, bitRate = 320,
path = "The Pretty Reckless/Going to Hell/03 Heaven Knows.mp3", isVideo = false,
playCount = 2, discNumber = 1,
created = parseDate("2016-10-23T21:30:40.000Z"),
albumId = "388", artistId = "238", type = "music"
)
}
}
@Test
fun `Should pass ids in request param`() {
val idsList = listOf("some-id1", "some-id2")
mockWebServerRule.assertRequestParam(expectedParam = "id=${idsList[0]}&id=${idsList[1]}") {
mockWebServerRule.assertRequestParam(
expectedParam = "id=${idsList[0]}&id=${idsList[1]}"
) {
client.api.createShare(idsList).execute()
}
}
@ -64,7 +69,7 @@ class SubsonicApiCreateShareTest : SubsonicAPIClientTest() {
mockWebServerRule.assertRequestParam(expectedParam = "description=$description") {
client.api.createShare(idsToShare = listOf("id1", "id2"), description = description)
.execute()
.execute()
}
}

View File

@ -26,8 +26,10 @@ class SubsonicApiDeletePlaylistTest : SubsonicAPIClientTest() {
fun `Should pass id param in request`() {
val id = "534"
mockWebServerRule.assertRequestParam(responseResourceName = "ping_ok.json",
expectedParam = "id=$id") {
mockWebServerRule.assertRequestParam(
responseResourceName = "ping_ok.json",
expectedParam = "id=$id"
) {
client.api.deletePlaylist(id).execute()
}
}

View File

@ -1,5 +1,6 @@
package org.moire.ultrasonic.api.subsonic
import java.io.IOException
import org.amshove.kluent.`should equal`
import org.amshove.kluent.`should not be`
import org.amshove.kluent.`should throw`
@ -15,7 +16,6 @@ import org.moire.ultrasonic.api.subsonic.SubsonicError.UserNotAuthorizedForOpera
import org.moire.ultrasonic.api.subsonic.SubsonicError.WrongUsernameOrPassword
import org.moire.ultrasonic.api.subsonic.response.SubsonicResponse
import retrofit2.Response
import java.io.IOException
/**
* Integration test that checks validity of api errors parsing.
@ -150,8 +150,8 @@ class SubsonicApiErrorsTest : SubsonicAPIClientTest() {
}
private fun Response<SubsonicResponse>.assertError(expectedError: SubsonicError) =
with(body()!!) {
error `should not be` null
error `should equal` expectedError
}
with(body()!!) {
error `should not be` null
error `should equal` expectedError
}
}

View File

@ -30,14 +30,18 @@ class SubsonicApiGetAlbumList2Test : SubsonicAPIClientTest() {
assertResponseSuccessful(response)
with(response.body()!!.albumList) {
this.size `should be equal to` 2
this[0] `should equal` Album(id = "962", name = "Fury", artist = "Sick Puppies",
artistId = "473", coverArt = "al-962", songCount = 13, duration = 2591,
created = parseDate("2017-09-02T17:34:51.000Z"), year = 2016,
genre = "Alternative Rock")
this[1] `should equal` Album(id = "961", name = "Endless Forms Most Beautiful",
artist = "Nightwish", artistId = "559", coverArt = "al-961", songCount = 22,
duration = 9469, created = parseDate("2017-09-02T16:22:47.000Z"),
year = 2015, genre = "Symphonic Metal")
this[0] `should equal` Album(
id = "962", name = "Fury", artist = "Sick Puppies",
artistId = "473", coverArt = "al-962", songCount = 13, duration = 2591,
created = parseDate("2017-09-02T17:34:51.000Z"), year = 2016,
genre = "Alternative Rock"
)
this[1] `should equal` Album(
id = "961", name = "Endless Forms Most Beautiful",
artist = "Nightwish", artistId = "559", coverArt = "al-961", songCount = 22,
duration = 9469, created = parseDate("2017-09-02T16:22:47.000Z"),
year = 2015, genre = "Symphonic Metal"
)
}
}
@ -45,8 +49,10 @@ class SubsonicApiGetAlbumList2Test : SubsonicAPIClientTest() {
fun `Should pass type in request params`() {
val type = AlbumListType.SORTED_BY_NAME
mockWebServerRule.assertRequestParam(responseResourceName = "get_album_list_2_ok.json",
expectedParam = "type=${type.typeName}") {
mockWebServerRule.assertRequestParam(
responseResourceName = "get_album_list_2_ok.json",
expectedParam = "type=${type.typeName}"
) {
client.api.getAlbumList2(type = type).execute()
}
}
@ -55,8 +61,10 @@ class SubsonicApiGetAlbumList2Test : SubsonicAPIClientTest() {
fun `Should pass size in request param`() {
val size = 45
mockWebServerRule.assertRequestParam(responseResourceName = "get_album_list_2_ok.json",
expectedParam = "size=$size") {
mockWebServerRule.assertRequestParam(
responseResourceName = "get_album_list_2_ok.json",
expectedParam = "size=$size"
) {
client.api.getAlbumList2(STARRED, size = size).execute()
}
}
@ -65,8 +73,10 @@ class SubsonicApiGetAlbumList2Test : SubsonicAPIClientTest() {
fun `Should pass offset in request param`() {
val offset = 33
mockWebServerRule.assertRequestParam(responseResourceName = "get_album_list_2_ok.json",
expectedParam = "offset=$offset") {
mockWebServerRule.assertRequestParam(
responseResourceName = "get_album_list_2_ok.json",
expectedParam = "offset=$offset"
) {
client.api.getAlbumList2(STARRED, offset = offset).execute()
}
}
@ -75,8 +85,10 @@ class SubsonicApiGetAlbumList2Test : SubsonicAPIClientTest() {
fun `Should pass from year in request params`() {
val fromYear = 3030
mockWebServerRule.assertRequestParam(responseResourceName = "get_album_list_2_ok.json",
expectedParam = "fromYear=$fromYear") {
mockWebServerRule.assertRequestParam(
responseResourceName = "get_album_list_2_ok.json",
expectedParam = "fromYear=$fromYear"
) {
client.api.getAlbumList2(STARRED, fromYear = fromYear).execute()
}
}
@ -85,8 +97,10 @@ class SubsonicApiGetAlbumList2Test : SubsonicAPIClientTest() {
fun `Should pass toYear in request param`() {
val toYear = 2014
mockWebServerRule.assertRequestParam(responseResourceName = "get_album_list_2_ok.json",
expectedParam = "toYear=$toYear") {
mockWebServerRule.assertRequestParam(
responseResourceName = "get_album_list_2_ok.json",
expectedParam = "toYear=$toYear"
) {
client.api.getAlbumList2(STARRED, toYear = toYear).execute()
}
}
@ -95,8 +109,10 @@ class SubsonicApiGetAlbumList2Test : SubsonicAPIClientTest() {
fun `Should pass genre in request param`() {
val genre = "MathRock"
mockWebServerRule.assertRequestParam(responseResourceName = "get_album_list_2_ok.json",
expectedParam = "genre=$genre") {
mockWebServerRule.assertRequestParam(
responseResourceName = "get_album_list_2_ok.json",
expectedParam = "genre=$genre"
) {
client.api.getAlbumList2(STARRED, genre = genre).execute()
}
}
@ -105,8 +121,10 @@ class SubsonicApiGetAlbumList2Test : SubsonicAPIClientTest() {
fun `Should pass music folder id in request param`() {
val musicFolderId = "9422"
mockWebServerRule.assertRequestParam(responseResourceName = "get_album_list_2_ok.json",
expectedParam = "musicFolderId=$musicFolderId") {
mockWebServerRule.assertRequestParam(
responseResourceName = "get_album_list_2_ok.json",
expectedParam = "musicFolderId=$musicFolderId"
) {
client.api.getAlbumList2(STARRED, musicFolderId = musicFolderId).execute()
}
}

View File

@ -29,11 +29,13 @@ class SubsonicApiGetAlbumListRequestTest : SubsonicAPIClientTest() {
assertResponseSuccessful(response)
with(response.body()!!.albumList) {
size `should be equal to` 2
this[1] `should equal` MusicDirectoryChild(id = "9997", parent = "9996", isDir = true,
title = "Endless Forms Most Beautiful", album = "Endless Forms Most Beautiful",
artist = "Nightwish", year = 2015, genre = "Symphonic Metal",
coverArt = "9997", playCount = 11,
created = parseDate("2017-09-02T16:22:49.000Z"))
this[1] `should equal` MusicDirectoryChild(
id = "9997", parent = "9996", isDir = true,
title = "Endless Forms Most Beautiful", album = "Endless Forms Most Beautiful",
artist = "Nightwish", year = 2015, genre = "Symphonic Metal",
coverArt = "9997", playCount = 11,
created = parseDate("2017-09-02T16:22:49.000Z")
)
}
}
@ -41,8 +43,10 @@ class SubsonicApiGetAlbumListRequestTest : SubsonicAPIClientTest() {
fun `Should pass type in request params`() {
val listType = AlbumListType.HIGHEST
mockWebServerRule.assertRequestParam(responseResourceName = "get_album_list_ok.json",
expectedParam = "type=${listType.typeName}") {
mockWebServerRule.assertRequestParam(
responseResourceName = "get_album_list_ok.json",
expectedParam = "type=${listType.typeName}"
) {
client.api.getAlbumList(type = listType).execute()
}
}
@ -51,8 +55,10 @@ class SubsonicApiGetAlbumListRequestTest : SubsonicAPIClientTest() {
fun `Should pass size in request params`() {
val size = 45
mockWebServerRule.assertRequestParam(responseResourceName = "get_album_list_ok.json",
expectedParam = "size=$size") {
mockWebServerRule.assertRequestParam(
responseResourceName = "get_album_list_ok.json",
expectedParam = "size=$size"
) {
client.api.getAlbumList(type = BY_GENRE, size = size).execute()
}
}
@ -61,8 +67,10 @@ class SubsonicApiGetAlbumListRequestTest : SubsonicAPIClientTest() {
fun `Should pass offset in request params`() {
val offset = 3
mockWebServerRule.assertRequestParam(responseResourceName = "get_album_list_ok.json",
expectedParam = "offset=$offset") {
mockWebServerRule.assertRequestParam(
responseResourceName = "get_album_list_ok.json",
expectedParam = "offset=$offset"
) {
client.api.getAlbumList(type = BY_GENRE, offset = offset).execute()
}
}
@ -71,8 +79,10 @@ class SubsonicApiGetAlbumListRequestTest : SubsonicAPIClientTest() {
fun `Should pass from year in request params`() {
val fromYear = 2001
mockWebServerRule.assertRequestParam(responseResourceName = "get_album_list_ok.json",
expectedParam = "fromYear=$fromYear") {
mockWebServerRule.assertRequestParam(
responseResourceName = "get_album_list_ok.json",
expectedParam = "fromYear=$fromYear"
) {
client.api.getAlbumList(type = BY_GENRE, fromYear = fromYear).execute()
}
}
@ -81,8 +91,10 @@ class SubsonicApiGetAlbumListRequestTest : SubsonicAPIClientTest() {
fun `Should pass to year in request params`() {
val toYear = 2017
mockWebServerRule.assertRequestParam(responseResourceName = "get_album_list_ok.json",
expectedParam = "toYear=$toYear") {
mockWebServerRule.assertRequestParam(
responseResourceName = "get_album_list_ok.json",
expectedParam = "toYear=$toYear"
) {
client.api.getAlbumList(type = BY_GENRE, toYear = toYear).execute()
}
}
@ -91,8 +103,10 @@ class SubsonicApiGetAlbumListRequestTest : SubsonicAPIClientTest() {
fun `Should pass genre in request params`() {
val genre = "Rock"
mockWebServerRule.assertRequestParam(responseResourceName = "get_album_list_ok.json",
expectedParam = "genre=$genre") {
mockWebServerRule.assertRequestParam(
responseResourceName = "get_album_list_ok.json",
expectedParam = "genre=$genre"
) {
client.api.getAlbumList(type = BY_GENRE, genre = genre).execute()
}
}
@ -101,8 +115,10 @@ class SubsonicApiGetAlbumListRequestTest : SubsonicAPIClientTest() {
fun `Should pass music folder id in request params`() {
val folderId = "545"
mockWebServerRule.assertRequestParam(responseResourceName = "get_album_list_ok.json",
expectedParam = "musicFolderId=$folderId") {
mockWebServerRule.assertRequestParam(
responseResourceName = "get_album_list_ok.json",
expectedParam = "musicFolderId=$folderId"
) {
client.api.getAlbumList(type = BY_GENRE, musicFolderId = folderId).execute()
}
}

View File

@ -25,8 +25,10 @@ class SubsonicApiGetAlbumTest : SubsonicAPIClientTest() {
fun `Should add id to request params`() {
val id = "76"
mockWebServerRule.assertRequestParam(responseResourceName = "get_album_ok.json",
expectedParam = "id=$id") {
mockWebServerRule.assertRequestParam(
responseResourceName = "get_album_ok.json",
expectedParam = "id=$id"
) {
client.api.getAlbum(id).execute()
}
}
@ -50,22 +52,26 @@ class SubsonicApiGetAlbumTest : SubsonicAPIClientTest() {
year `should be equal to` 2008
genre `should be equal to` "Hard Rock"
songList.size `should be equal to` 15
songList[0] `should equal` MusicDirectoryChild(id = "6491", parent = "6475",
isDir = false, title = "Rock 'n' Roll Train", album = "Black Ice",
artist = "AC/DC", track = 1, year = 2008, genre = "Hard Rock",
coverArt = "6475", size = 7205451, contentType = "audio/mpeg", suffix = "mp3",
duration = 261, bitRate = 219,
path = "AC_DC/Black Ice/01 Rock 'n' Roll Train.mp3",
isVideo = false, playCount = 0, discNumber = 1,
created = parseDate("2016-10-23T15:31:20.000Z"),
albumId = "618", artistId = "362", type = "music")
songList[5] `should equal` MusicDirectoryChild(id = "6492", parent = "6475",
isDir = false, title = "Smash 'n' Grab", album = "Black Ice", artist = "AC/DC",
track = 6, year = 2008, genre = "Hard Rock", coverArt = "6475", size = 6697204,
contentType = "audio/mpeg", suffix = "mp3", duration = 246, bitRate = 216,
path = "AC_DC/Black Ice/06 Smash 'n' Grab.mp3", isVideo = false, playCount = 0,
discNumber = 1, created = parseDate("2016-10-23T15:31:20.000Z"),
albumId = "618", artistId = "362", type = "music")
songList[0] `should equal` MusicDirectoryChild(
id = "6491", parent = "6475",
isDir = false, title = "Rock 'n' Roll Train", album = "Black Ice",
artist = "AC/DC", track = 1, year = 2008, genre = "Hard Rock",
coverArt = "6475", size = 7205451, contentType = "audio/mpeg", suffix = "mp3",
duration = 261, bitRate = 219,
path = "AC_DC/Black Ice/01 Rock 'n' Roll Train.mp3",
isVideo = false, playCount = 0, discNumber = 1,
created = parseDate("2016-10-23T15:31:20.000Z"),
albumId = "618", artistId = "362", type = "music"
)
songList[5] `should equal` MusicDirectoryChild(
id = "6492", parent = "6475",
isDir = false, title = "Smash 'n' Grab", album = "Black Ice", artist = "AC/DC",
track = 6, year = 2008, genre = "Hard Rock", coverArt = "6475", size = 6697204,
contentType = "audio/mpeg", suffix = "mp3", duration = 246, bitRate = 216,
path = "AC_DC/Black Ice/06 Smash 'n' Grab.mp3", isVideo = false, playCount = 0,
discNumber = 1, created = parseDate("2016-10-23T15:31:20.000Z"),
albumId = "618", artistId = "362", type = "music"
)
}
}
}

View File

@ -25,8 +25,10 @@ class SubsonicApiGetArtistTest : SubsonicAPIClientTest() {
fun `Should pass id param in request`() {
val id = "929"
mockWebServerRule.assertRequestParam(responseResourceName = "get_artist_ok.json",
expectedParam = "id=$id") {
mockWebServerRule.assertRequestParam(
responseResourceName = "get_artist_ok.json",
expectedParam = "id=$id"
) {
client.api.getArtist(id).execute()
}
}
@ -44,14 +46,18 @@ class SubsonicApiGetArtistTest : SubsonicAPIClientTest() {
coverArt `should be equal to` "ar-362"
albumCount `should be equal to` 2
albumsList.size `should be equal to` 2
albumsList[0] `should equal` Album(id = "618", name = "Black Ice", artist = "AC/DC",
artistId = "362", coverArt = "al-618", songCount = 15, duration = 3331,
created = parseDate("2016-10-23T15:31:22.000Z"),
year = 2008, genre = "Hard Rock")
albumsList[1] `should equal` Album(id = "617", name = "Rock or Bust", artist = "AC/DC",
artistId = "362", coverArt = "al-617", songCount = 11, duration = 2095,
created = parseDate("2016-10-23T15:31:23.000Z"),
year = 2014, genre = "Hard Rock")
albumsList[0] `should equal` Album(
id = "618", name = "Black Ice", artist = "AC/DC",
artistId = "362", coverArt = "al-618", songCount = 15, duration = 3331,
created = parseDate("2016-10-23T15:31:22.000Z"),
year = 2008, genre = "Hard Rock"
)
albumsList[1] `should equal` Album(
id = "617", name = "Rock or Bust", artist = "AC/DC",
artistId = "362", coverArt = "al-617", songCount = 11, duration = 2095,
created = parseDate("2016-10-23T15:31:23.000Z"),
year = 2014, genre = "Hard Rock"
)
}
}
}

View File

@ -35,17 +35,26 @@ class SubsonicApiGetArtistsTest : SubsonicAPIClientTest() {
shortcutList `should equal` emptyList()
indexList.size `should be equal to` 2
indexList `should equal` listOf(
Index(name = "A", artists = listOf(
Artist(id = "362", name = "AC/DC", coverArt = "ar-362", albumCount = 2),
Artist(id = "254", name = "Acceptance", coverArt = "ar-254",
albumCount = 1)
)),
Index(name = "T", artists = listOf(
Artist(id = "516", name = "Tangerine Dream", coverArt = "ar-516",
albumCount = 1),
Artist(id = "242", name = "Taproot", coverArt = "ar-242",
albumCount = 2)
))
Index(
name = "A",
artists = listOf(
Artist(id = "362", name = "AC/DC", coverArt = "ar-362", albumCount = 2),
Artist(id = "254", name = "Acceptance", coverArt = "ar-254", albumCount = 1)
)
),
Index(
name = "T",
artists = listOf(
Artist(
id = "516", name = "Tangerine Dream", coverArt = "ar-516",
albumCount = 1
),
Artist(
id = "242", name = "Taproot", coverArt = "ar-242",
albumCount = 2
)
)
)
)
}
}
@ -55,8 +64,10 @@ class SubsonicApiGetArtistsTest : SubsonicAPIClientTest() {
mockWebServerRule.enqueueResponse("get_artists_ok.json")
val musicFolderId = "101"
mockWebServerRule.assertRequestParam(responseResourceName = "get_artists_ok.json",
expectedParam = "musicFolderId=$musicFolderId") {
mockWebServerRule.assertRequestParam(
responseResourceName = "get_artists_ok.json",
expectedParam = "musicFolderId=$musicFolderId"
) {
client.api.getArtists(musicFolderId).execute()
}
}

View File

@ -40,8 +40,10 @@ class SubsonicApiGetAvatarTest : SubsonicAPIClientTest() {
@Test
fun `Should return successful call stream`() {
mockWebServerRule.mockWebServer.enqueue(MockResponse()
.setBody(mockWebServerRule.loadJsonResponse("ping_ok.json")))
mockWebServerRule.mockWebServer.enqueue(
MockResponse()
.setBody(mockWebServerRule.loadJsonResponse("ping_ok.json"))
)
val response = client.stream("some")

View File

@ -32,15 +32,17 @@ class SubsonicApiGetBookmarksTest : SubsonicAPIClientTest() {
comment `should be equal to` "Look at this"
created `should equal` parseDate("2017-11-18T15:22:22.144Z")
changed `should equal` parseDate("2017-11-18T15:22:22.144Z")
entry `should equal` MusicDirectoryChild(id = "10349", parent = "10342",
isDir = false, title = "Amerika", album = "Home of the Strange",
artist = "Young the Giant", track = 1, year = 2016, genre = "Indie Rock",
coverArt = "10342", size = 9628673, contentType = "audio/mpeg",
suffix = "mp3", duration = 240, bitRate = 320,
path = "Young the Giant/Home of the Strange/01 Amerika.mp3",
isVideo = false, playCount = 2, discNumber = 1,
created = parseDate("2017-11-01T17:46:52.000Z"),
albumId = "984", artistId = "571", type = "music")
entry `should equal` MusicDirectoryChild(
id = "10349", parent = "10342",
isDir = false, title = "Amerika", album = "Home of the Strange",
artist = "Young the Giant", track = 1, year = 2016, genre = "Indie Rock",
coverArt = "10342", size = 9628673, contentType = "audio/mpeg",
suffix = "mp3", duration = 240, bitRate = 320,
path = "Young the Giant/Home of the Strange/01 Amerika.mp3",
isVideo = false, playCount = 2, discNumber = 1,
created = parseDate("2017-11-01T17:46:52.000Z"),
albumId = "984", artistId = "571", type = "music"
)
}
}
}

View File

@ -27,10 +27,14 @@ class SubsonicApiGetChatMessagesTest : SubsonicAPIClientTest() {
assertResponseSuccessful(response)
with(response.body()!!.chatMessages) {
size `should be equal to` 2
this[0] `should equal` ChatMessage(username = "sindre", time = 1269771845310,
message = "Sindre was here")
this[1] `should equal` ChatMessage(username = "ben", time = 1269771842504,
message = "Ben too")
this[0] `should equal` ChatMessage(
username = "sindre", time = 1269771845310,
message = "Sindre was here"
)
this[1] `should equal` ChatMessage(
username = "ben", time = 1269771842504,
message = "Ben too"
)
}
}

View File

@ -40,8 +40,10 @@ class SubsonicApiGetCoverArtTest : SubsonicAPIClientTest() {
@Test
fun `Should return successful call stream`() {
mockWebServerRule.mockWebServer.enqueue(MockResponse()
.setBody(mockWebServerRule.loadJsonResponse("ping_ok.json")))
mockWebServerRule.mockWebServer.enqueue(
MockResponse()
.setBody(mockWebServerRule.loadJsonResponse("ping_ok.json"))
)
val response = client.getCoverArt("some-id")

View File

@ -23,19 +23,27 @@ class SubsonicApiGetIndexesTest : SubsonicAPIClientTest() {
lastModified `should equal` 1491069027523
ignoredArticles `should equal` "The El La Los Las Le Les"
shortcutList `should equal` listOf(
Artist(id = "889", name = "podcasts"),
Artist(id = "890", name = "audiobooks")
Artist(id = "889", name = "podcasts"),
Artist(id = "890", name = "audiobooks")
)
indexList `should equal` mutableListOf(
Index("A", listOf(
Artist(id = "50", name = "Ace Of Base",
starred = parseDate("2017-04-02T20:16:29.815Z")),
Artist(id = "379", name = "A Perfect Circle")
)),
Index("H", listOf(
Artist(id = "299", name = "Haddaway"),
Artist(id = "297", name = "Halestorm")
))
Index(
"A",
listOf(
Artist(
id = "50", name = "Ace Of Base",
starred = parseDate("2017-04-02T20:16:29.815Z")
),
Artist(id = "379", name = "A Perfect Circle")
)
),
Index(
"H",
listOf(
Artist(id = "299", name = "Haddaway"),
Artist(id = "297", name = "Halestorm")
)
)
)
}
}
@ -44,8 +52,10 @@ class SubsonicApiGetIndexesTest : SubsonicAPIClientTest() {
fun `Should add music folder id as a query param for getIndexes api call`() {
val musicFolderId = "9"
mockWebServerRule.assertRequestParam(responseResourceName = "get_indexes_ok.json",
expectedParam = "musicFolderId=$musicFolderId") {
mockWebServerRule.assertRequestParam(
responseResourceName = "get_indexes_ok.json",
expectedParam = "musicFolderId=$musicFolderId"
) {
client.api.getIndexes(musicFolderId, null).execute()
}
}
@ -54,8 +64,10 @@ class SubsonicApiGetIndexesTest : SubsonicAPIClientTest() {
fun `Should add ifModifiedSince as a query param for getIndexes api call`() {
val ifModifiedSince = System.currentTimeMillis()
mockWebServerRule.assertRequestParam(responseResourceName = "get_indexes_ok.json",
expectedParam = "ifModifiedSince=$ifModifiedSince") {
mockWebServerRule.assertRequestParam(
responseResourceName = "get_indexes_ok.json",
expectedParam = "ifModifiedSince=$ifModifiedSince"
) {
client.api.getIndexes(null, ifModifiedSince).execute()
}
}

View File

@ -18,10 +18,12 @@ class SubsonicApiGetLicenseTest : SubsonicAPIClientTest() {
assertResponseSuccessful(response)
with(response.body()!!) {
assertBaseResponseOk()
license `should equal` License(valid = true,
trialExpires = parseDate("2016-11-23T20:17:15.206Z"),
email = "someone@example.net",
licenseExpires = parseDate("8994-08-17T07:12:55.807Z"))
license `should equal` License(
valid = true,
trialExpires = parseDate("2016-11-23T20:17:15.206Z"),
email = "someone@example.net",
licenseExpires = parseDate("8994-08-17T07:12:55.807Z")
)
}
}

View File

@ -25,7 +25,7 @@ class SubsonicApiGetLyricsTest : SubsonicAPIClientTest() {
artist `should be equal to` "Amorphis"
title `should be equal to` "Alone"
text `should be equal to` "Tear dimmed rememberance\nIn a womb of time\nBreath upon " +
"me\nPossessed by the"
"me\nPossessed by the"
}
}
@ -33,8 +33,10 @@ class SubsonicApiGetLyricsTest : SubsonicAPIClientTest() {
fun `Should pass artist param in request`() {
val artist = "some-artist"
mockWebServerRule.assertRequestParam(responseResourceName = "get_lyrics_ok.json",
expectedParam = "artist=$artist") {
mockWebServerRule.assertRequestParam(
responseResourceName = "get_lyrics_ok.json",
expectedParam = "artist=$artist"
) {
client.api.getLyrics(artist = artist).execute()
}
}
@ -43,8 +45,10 @@ class SubsonicApiGetLyricsTest : SubsonicAPIClientTest() {
fun `Should pass title param in request`() {
val title = "some-title"
mockWebServerRule.assertRequestParam(responseResourceName = "get_lyrics_ok.json",
expectedParam = "title=$title") {
mockWebServerRule.assertRequestParam(
responseResourceName = "get_lyrics_ok.json",
expectedParam = "title=$title"
) {
client.api.getLyrics(title = title).execute()
}
}

View File

@ -26,8 +26,10 @@ class SubsonicApiGetMusicDirectoryTest : SubsonicAPIClientTest() {
fun `GetMusicDirectory should add directory id to query params`() {
val directoryId = "124"
mockWebServerRule.assertRequestParam(responseResourceName = "get_music_directory_ok.json",
expectedParam = "id=$directoryId") {
mockWebServerRule.assertRequestParam(
responseResourceName = "get_music_directory_ok.json",
expectedParam = "id=$directoryId"
) {
client.api.getMusicDirectory(directoryId).execute()
}
}
@ -50,22 +52,26 @@ class SubsonicApiGetMusicDirectoryTest : SubsonicAPIClientTest() {
starred `should equal` null
playCount `should be equal to` 1
childList.size `should be` 2
childList[0] `should equal` MusicDirectoryChild(id = "4844", parent = "4836",
isDir = false, title = "Crash", album = "12 Stones", artist = "12 Stones",
track = 1, year = 2002, genre = "Alternative Rock", coverArt = "4836",
size = 5348318L, contentType = "audio/mpeg", suffix = "mp3", duration = 222,
bitRate = 192, path = "12 Stones/12 Stones/01 Crash.mp3", isVideo = false,
playCount = 0, discNumber = 1,
created = parseDate("2016-10-23T15:19:10.000Z"),
albumId = "454", artistId = "288", type = "music")
childList[1] `should equal` MusicDirectoryChild(id = "4845", parent = "4836",
isDir = false, title = "Broken", album = "12 Stones", artist = "12 Stones",
track = 2, year = 2002, genre = "Alternative Rock", coverArt = "4836",
size = 4309043L, contentType = "audio/mpeg", suffix = "mp3", duration = 179,
bitRate = 192, path = "12 Stones/12 Stones/02 Broken.mp3", isVideo = false,
playCount = 0, discNumber = 1,
created = parseDate("2016-10-23T15:19:09.000Z"),
albumId = "454", artistId = "288", type = "music")
childList[0] `should equal` MusicDirectoryChild(
id = "4844", parent = "4836",
isDir = false, title = "Crash", album = "12 Stones", artist = "12 Stones",
track = 1, year = 2002, genre = "Alternative Rock", coverArt = "4836",
size = 5348318L, contentType = "audio/mpeg", suffix = "mp3", duration = 222,
bitRate = 192, path = "12 Stones/12 Stones/01 Crash.mp3", isVideo = false,
playCount = 0, discNumber = 1,
created = parseDate("2016-10-23T15:19:10.000Z"),
albumId = "454", artistId = "288", type = "music"
)
childList[1] `should equal` MusicDirectoryChild(
id = "4845", parent = "4836",
isDir = false, title = "Broken", album = "12 Stones", artist = "12 Stones",
track = 2, year = 2002, genre = "Alternative Rock", coverArt = "4836",
size = 4309043L, contentType = "audio/mpeg", suffix = "mp3", duration = 179,
bitRate = 192, path = "12 Stones/12 Stones/02 Broken.mp3", isVideo = false,
playCount = 0, discNumber = 1,
created = parseDate("2016-10-23T15:19:09.000Z"),
albumId = "454", artistId = "288", type = "music"
)
}
}
}

View File

@ -18,8 +18,9 @@ class SubsonicApiGetMusicFoldersTest : SubsonicAPIClientTest() {
with(response.body()!!) {
assertBaseResponseOk()
musicFolders `should equal` listOf(
MusicFolder("0", "Music"),
MusicFolder("2", "Test"))
MusicFolder("0", "Music"),
MusicFolder("2", "Test")
)
}
}

View File

@ -39,15 +39,17 @@ class SubsonicApiGetPlaylistTest : SubsonicAPIClientTest() {
changed `should equal` parseDate("2017-08-27T11:17:26.218Z")
coverArt `should be equal to` "pl-0"
entriesList.size `should be equal to` 2
entriesList[1] `should equal` MusicDirectoryChild(id = "4215", parent = "4186",
isDir = false, title = "Going to Hell", album = "Going to Hell",
artist = "The Pretty Reckless", track = 2, year = 2014,
genre = "Hard Rock", coverArt = "4186", size = 11089627,
contentType = "audio/mpeg", suffix = "mp3", duration = 277, bitRate = 320,
path = "The Pretty Reckless/Going to Hell/02 Going to Hell.mp3",
isVideo = false, playCount = 0, discNumber = 1,
created = parseDate("2016-10-23T21:30:41.000Z"),
albumId = "388", artistId = "238", type = "music")
entriesList[1] `should equal` MusicDirectoryChild(
id = "4215", parent = "4186",
isDir = false, title = "Going to Hell", album = "Going to Hell",
artist = "The Pretty Reckless", track = 2, year = 2014,
genre = "Hard Rock", coverArt = "4186", size = 11089627,
contentType = "audio/mpeg", suffix = "mp3", duration = 277, bitRate = 320,
path = "The Pretty Reckless/Going to Hell/02 Going to Hell.mp3",
isVideo = false, playCount = 0, discNumber = 1,
created = parseDate("2016-10-23T21:30:41.000Z"),
albumId = "388", artistId = "238", type = "music"
)
}
}
@ -55,8 +57,10 @@ class SubsonicApiGetPlaylistTest : SubsonicAPIClientTest() {
fun `Should pass id as request param`() {
val playlistId = "453"
mockWebServerRule.assertRequestParam(responseResourceName = "get_playlist_ok.json",
expectedParam = "id=$playlistId") {
mockWebServerRule.assertRequestParam(
responseResourceName = "get_playlist_ok.json",
expectedParam = "id=$playlistId"
) {
client.api.getPlaylist(playlistId).execute()
}
}

View File

@ -29,12 +29,14 @@ class SubsonicApiGetPlaylistsTest : SubsonicAPIClientTest() {
assertResponseSuccessful(response)
with(response.body()!!.playlists) {
size `should be equal to` 1
this[0] `should equal` Playlist(id = "0", name = "Aug 27, 2017 11:17 AM",
owner = "admin", public = false, songCount = 16, duration = 3573,
comment = "Some comment",
created = parseDate("2017-08-27T11:17:26.216Z"),
changed = parseDate("2017-08-27T11:17:26.218Z"),
coverArt = "pl-0")
this[0] `should equal` Playlist(
id = "0", name = "Aug 27, 2017 11:17 AM",
owner = "admin", public = false, songCount = 16, duration = 3573,
comment = "Some comment",
created = parseDate("2017-08-27T11:17:26.216Z"),
changed = parseDate("2017-08-27T11:17:26.218Z"),
coverArt = "pl-0"
)
}
}
@ -42,8 +44,10 @@ class SubsonicApiGetPlaylistsTest : SubsonicAPIClientTest() {
fun `Should pass username as a parameter`() {
val username = "SomeUsername"
mockWebServerRule.assertRequestParam(responseResourceName = "get_playlists_ok.json",
expectedParam = "username=$username") {
mockWebServerRule.assertRequestParam(
responseResourceName = "get_playlists_ok.json",
expectedParam = "username=$username"
) {
client.api.getPlaylists(username = username).execute()
}
}

View File

@ -34,33 +34,35 @@ class SubsonicApiGetPodcastsTest : SubsonicAPIClientTest() {
url `should be equal to` "http://feeds.codenewbie.org/cnpodcast.xml"
title `should be equal to` "CodeNewbie"
description `should be equal to` "Stories and interviews from people on their coding " +
"journey."
"journey."
coverArt `should be equal to` "pod-2"
originalImageUrl `should be equal to` "http://codenewbie.blubrry.com/wp-content/" +
"uploads/powerpress/220808.jpg"
"uploads/powerpress/220808.jpg"
status `should be equal to` "completed"
errorMessage `should be equal to` ""
episodeList.size `should be equal to` 10
episodeList[0] `should equal` MusicDirectoryChild(id = "148", parent = "9959",
isDir = false,
title = "S1:EP3 How to teach yourself computer science (Vaidehi Joshi)",
album = "CodeNewbie", artist = "podcasts", coverArt = "9959",
size = 38274221, contentType = "audio/mpeg", suffix = "mp3",
duration = 2397, bitRate = 128, isVideo = false, playCount = 0,
created = parseDate("2017-08-30T09:33:39.000Z"), type = "podcast",
streamId = "9982", channelId = "2",
description = "Vaidehi decided to take on a year-long challenge. " +
"She'd pick a computer science topic every week, do tons of research " +
"and write a technical blog post explaining it in simple terms and " +
"beautiful illustrations. And then she actually did it. She tells us " +
"about her project, basecs, how it's changed her as a developer, and " +
"how she handles the trolls and negativity from people who don't " +
"appreciate her work. Show Notes Technical Writer position at " +
"CodeNewbie basecs 100 Days of Code Conway's Game of Life Hexes and " +
"Other Magical Numbers (Vaidehi's blog post) Bits, Bytes, Building " +
"With Binary (Vaidehi's blog post) Rust",
status = "completed",
publishDate = parseDate("2017-08-29T00:01:01.000Z"))
episodeList[0] `should equal` MusicDirectoryChild(
id = "148", parent = "9959",
isDir = false,
title = "S1:EP3 How to teach yourself computer science (Vaidehi Joshi)",
album = "CodeNewbie", artist = "podcasts", coverArt = "9959",
size = 38274221, contentType = "audio/mpeg", suffix = "mp3",
duration = 2397, bitRate = 128, isVideo = false, playCount = 0,
created = parseDate("2017-08-30T09:33:39.000Z"), type = "podcast",
streamId = "9982", channelId = "2",
description = "Vaidehi decided to take on a year-long challenge. " +
"She'd pick a computer science topic every week, do tons of research " +
"and write a technical blog post explaining it in simple terms and " +
"beautiful illustrations. And then she actually did it. She tells us " +
"about her project, basecs, how it's changed her as a developer, and " +
"how she handles the trolls and negativity from people who don't " +
"appreciate her work. Show Notes Technical Writer position at " +
"CodeNewbie basecs 100 Days of Code Conway's Game of Life Hexes and " +
"Other Magical Numbers (Vaidehi's blog post) Bits, Bytes, Building " +
"With Binary (Vaidehi's blog post) Rust",
status = "completed",
publishDate = parseDate("2017-08-29T00:01:01.000Z")
)
}
}
@ -68,8 +70,10 @@ class SubsonicApiGetPodcastsTest : SubsonicAPIClientTest() {
fun `Should pass include episodes in request`() {
val includeEpisodes = true
mockWebServerRule.assertRequestParam(responseResourceName = "get_podcasts_ok.json",
expectedParam = "includeEpisodes=$includeEpisodes") {
mockWebServerRule.assertRequestParam(
responseResourceName = "get_podcasts_ok.json",
expectedParam = "includeEpisodes=$includeEpisodes"
) {
client.api.getPodcasts(includeEpisodes = includeEpisodes).execute()
}
}
@ -78,8 +82,10 @@ class SubsonicApiGetPodcastsTest : SubsonicAPIClientTest() {
fun `Should pass id in request param`() {
val id = "249"
mockWebServerRule.assertRequestParam(responseResourceName = "get_podcasts_ok.json",
expectedParam = "id=$id") {
mockWebServerRule.assertRequestParam(
responseResourceName = "get_podcasts_ok.json",
expectedParam = "id=$id"
) {
client.api.getPodcasts(id = id).execute()
}
}

View File

@ -27,14 +27,16 @@ class SubsonicApiGetRandomSongsTest : SubsonicAPIClientTest() {
assertResponseSuccessful(response)
with(response.body()!!.songsList) {
size `should be equal to` 3
this[1] `should equal` MusicDirectoryChild(id = "3061", parent = "3050", isDir = false,
title = "Sure as Hell", album = "Who Are You Now?", artist = "This Providence",
track = 1, year = 2009, genre = "Indie Rock", coverArt = "3050",
size = 1969692, contentType = "audio/mpeg", suffix = "mp3", duration = 110,
bitRate = 142, path = "This Providence/Who Are You Now_/01 Sure as Hell.mp3",
isVideo = false, playCount = 0, discNumber = 1,
created = parseDate("2016-10-23T21:32:46.000Z"), albumId = "272",
artistId = "152", type = "music")
this[1] `should equal` MusicDirectoryChild(
id = "3061", parent = "3050", isDir = false,
title = "Sure as Hell", album = "Who Are You Now?", artist = "This Providence",
track = 1, year = 2009, genre = "Indie Rock", coverArt = "3050",
size = 1969692, contentType = "audio/mpeg", suffix = "mp3", duration = 110,
bitRate = 142, path = "This Providence/Who Are You Now_/01 Sure as Hell.mp3",
isVideo = false, playCount = 0, discNumber = 1,
created = parseDate("2016-10-23T21:32:46.000Z"), albumId = "272",
artistId = "152", type = "music"
)
}
}
@ -42,8 +44,10 @@ class SubsonicApiGetRandomSongsTest : SubsonicAPIClientTest() {
fun `Should pass size in request param`() {
val size = 384433
mockWebServerRule.assertRequestParam(responseResourceName = "get_random_songs_ok.json",
expectedParam = "size=$size") {
mockWebServerRule.assertRequestParam(
responseResourceName = "get_random_songs_ok.json",
expectedParam = "size=$size"
) {
client.api.getRandomSongs(size = size).execute()
}
}
@ -52,8 +56,10 @@ class SubsonicApiGetRandomSongsTest : SubsonicAPIClientTest() {
fun `Should pass genre in request param`() {
val genre = "PostRock"
mockWebServerRule.assertRequestParam(responseResourceName = "get_random_songs_ok.json",
expectedParam = "genre=$genre") {
mockWebServerRule.assertRequestParam(
responseResourceName = "get_random_songs_ok.json",
expectedParam = "genre=$genre"
) {
client.api.getRandomSongs(genre = genre).execute()
}
}
@ -62,8 +68,10 @@ class SubsonicApiGetRandomSongsTest : SubsonicAPIClientTest() {
fun `Should pass from year in request param`() {
val fromYear = 1919
mockWebServerRule.assertRequestParam(responseResourceName = "get_random_songs_ok.json",
expectedParam = "fromYear=$fromYear") {
mockWebServerRule.assertRequestParam(
responseResourceName = "get_random_songs_ok.json",
expectedParam = "fromYear=$fromYear"
) {
client.api.getRandomSongs(fromYear = fromYear).execute()
}
}
@ -72,8 +80,10 @@ class SubsonicApiGetRandomSongsTest : SubsonicAPIClientTest() {
fun `Should pass to year in request params`() {
val toYear = 2012
mockWebServerRule.assertRequestParam(responseResourceName = "get_random_songs_ok.json",
expectedParam = "toYear=$toYear") {
mockWebServerRule.assertRequestParam(
responseResourceName = "get_random_songs_ok.json",
expectedParam = "toYear=$toYear"
) {
client.api.getRandomSongs(toYear = toYear).execute()
}
}
@ -82,8 +92,10 @@ class SubsonicApiGetRandomSongsTest : SubsonicAPIClientTest() {
fun `Should pass music folder id in request param`() {
val musicFolderId = "4919"
mockWebServerRule.assertRequestParam(responseResourceName = "get_random_songs_ok.json",
expectedParam = "musicFolderId=$musicFolderId") {
mockWebServerRule.assertRequestParam(
responseResourceName = "get_random_songs_ok.json",
expectedParam = "musicFolderId=$musicFolderId"
) {
client.api.getRandomSongs(musicFolderId = musicFolderId).execute()
}
}

View File

@ -29,8 +29,8 @@ class SubsonicApiGetSharesTest : SubsonicAPIClientTest() {
with(response.body()!!.shares[0]) {
id `should be equal to` "0"
url `should be equal to` "https://subsonic.com/ext/share/awdwo?jwt=eyJhbGciOiJIUzI1" +
"NiJ9.eyJwYXRoIjoiL2V4dC9zaGFyZS9hd2R3byIsImV4cCI6MTU0MTYyNjQzMX0.iy8dkt_ZZc8" +
"hJ692UxorHdHWFU2RB-fMCmCA4IJ_dTw"
"NiJ9.eyJwYXRoIjoiL2V4dC9zaGFyZS9hd2R3byIsImV4cCI6MTU0MTYyNjQzMX0.iy8dkt_ZZc8" +
"hJ692UxorHdHWFU2RB-fMCmCA4IJ_dTw"
username `should be equal to` "admin"
created `should equal` parseDate("2017-11-07T21:33:51.748Z")
expires `should equal` parseDate("2018-11-07T21:33:51.748Z")
@ -38,13 +38,15 @@ class SubsonicApiGetSharesTest : SubsonicAPIClientTest() {
visitCount `should be equal to` 0
description `should be equal to` "Awesome link!"
items.size `should be equal to` 1
items[0] `should equal` MusicDirectoryChild(id = "4212", parent = "4186", isDir = false,
title = "Heaven Knows", album = "Going to Hell", artist = "The Pretty Reckless",
track = 3, year = 2014, genre = "Hard Rock", coverArt = "4186", size = 9025090,
contentType = "audio/mpeg", suffix = "mp3", duration = 225, bitRate = 320,
path = "The Pretty Reckless/Going to Hell/03 Heaven Knows.mp3", isVideo = false,
playCount = 2, discNumber = 1, created = parseDate("2016-10-23T21:30:40.000Z"),
albumId = "388", artistId = "238", type = "music")
items[0] `should equal` MusicDirectoryChild(
id = "4212", parent = "4186", isDir = false,
title = "Heaven Knows", album = "Going to Hell", artist = "The Pretty Reckless",
track = 3, year = 2014, genre = "Hard Rock", coverArt = "4186", size = 9025090,
contentType = "audio/mpeg", suffix = "mp3", duration = 225, bitRate = 320,
path = "The Pretty Reckless/Going to Hell/03 Heaven Knows.mp3", isVideo = false,
playCount = 2, discNumber = 1, created = parseDate("2016-10-23T21:30:40.000Z"),
albumId = "388", artistId = "238", type = "music"
)
}
}
}

View File

@ -27,23 +27,27 @@ class SubsonicApiGetSongsByGenreTest : SubsonicAPIClientTest() {
assertResponseSuccessful(response)
response.body()!!.songsList.size `should be equal to` 2
with(response.body()!!.songsList) {
this[0] `should equal` MusicDirectoryChild(id = "575", parent = "576", isDir = false,
title = "Time Machine (Vadim Zhukov Remix)", album = "668",
artist = "Tasadi", year = 2008, genre = "Trance", size = 22467672,
contentType = "audio/mpeg", suffix = "mp3", duration = 561, bitRate = 320,
path = "Tasadi/668/00 Time Machine (Vadim Zhukov Remix).mp3",
isVideo = false, playCount = 0, created = parseDate("2016-10-23T21:58:29.000Z"),
albumId = "0", artistId = "0", type = "music")
this[1] `should equal` MusicDirectoryChild(id = "621", parent = "622", isDir = false,
title = "My Heart (Vadim Zhukov Remix)", album = "668",
artist = "DJ Polyakov PPK Feat Kate Cameron", year = 2009, genre = "Trance",
size = 26805932, contentType = "audio/mpeg", suffix = "mp3", duration = 670,
bitRate = 320,
path = "DJ Polyakov PPK Feat Kate Cameron/668/00 My Heart (Vadim Zhukov " +
"Remix).mp3",
isVideo = false, playCount = 2,
created = parseDate("2016-10-23T21:58:29.000Z"),
albumId = "5", artistId = "4", type = "music")
this[0] `should equal` MusicDirectoryChild(
id = "575", parent = "576", isDir = false,
title = "Time Machine (Vadim Zhukov Remix)", album = "668",
artist = "Tasadi", year = 2008, genre = "Trance", size = 22467672,
contentType = "audio/mpeg", suffix = "mp3", duration = 561, bitRate = 320,
path = "Tasadi/668/00 Time Machine (Vadim Zhukov Remix).mp3",
isVideo = false, playCount = 0, created = parseDate("2016-10-23T21:58:29.000Z"),
albumId = "0", artistId = "0", type = "music"
)
this[1] `should equal` MusicDirectoryChild(
id = "621", parent = "622", isDir = false,
title = "My Heart (Vadim Zhukov Remix)", album = "668",
artist = "DJ Polyakov PPK Feat Kate Cameron", year = 2009, genre = "Trance",
size = 26805932, contentType = "audio/mpeg", suffix = "mp3", duration = 670,
bitRate = 320,
path = "DJ Polyakov PPK Feat Kate Cameron/668/00 My Heart (Vadim Zhukov " +
"Remix).mp3",
isVideo = false, playCount = 2,
created = parseDate("2016-10-23T21:58:29.000Z"),
albumId = "5", artistId = "4", type = "music"
)
}
}

View File

@ -30,8 +30,10 @@ class SubsonicApiGetStarred2Test : SubsonicAPIClientTest() {
with(response.body()!!.starred2) {
albumList `should equal` emptyList()
artistList.size `should be equal to` 1
artistList[0] `should equal` Artist(id = "364", name = "Parov Stelar",
starred = parseDate("2017-08-12T18:32:58.768Z"))
artistList[0] `should equal` Artist(
id = "364", name = "Parov Stelar",
starred = parseDate("2017-08-12T18:32:58.768Z")
)
songList `should equal` emptyList()
}
}
@ -40,8 +42,10 @@ class SubsonicApiGetStarred2Test : SubsonicAPIClientTest() {
fun `Should pass music folder id in request param`() {
val musicFolderId = "441"
mockWebServerRule.assertRequestParam(responseResourceName = "get_starred_2_ok.json",
expectedParam = "musicFolderId=$musicFolderId") {
mockWebServerRule.assertRequestParam(
responseResourceName = "get_starred_2_ok.json",
expectedParam = "musicFolderId=$musicFolderId"
) {
client.api.getStarred2(musicFolderId = musicFolderId).execute()
}
}

View File

@ -29,8 +29,10 @@ class SubsonicApiGetStarredTest : SubsonicAPIClientTest() {
with(response.body()!!.starred) {
albumList `should equal` emptyList()
artistList.size `should be equal to` 1
artistList[0] `should equal` Artist(id = "364", name = "Parov Stelar",
starred = parseDate("2017-08-12T18:32:58.768Z"))
artistList[0] `should equal` Artist(
id = "364", name = "Parov Stelar",
starred = parseDate("2017-08-12T18:32:58.768Z")
)
songList `should equal` emptyList()
}
}
@ -39,8 +41,10 @@ class SubsonicApiGetStarredTest : SubsonicAPIClientTest() {
fun `Should pass music folder id in request param`() {
val musicFolderId = "441"
mockWebServerRule.assertRequestParam(responseResourceName = "get_starred_ok.json",
expectedParam = "musicFolderId=$musicFolderId") {
mockWebServerRule.assertRequestParam(
responseResourceName = "get_starred_ok.json",
expectedParam = "musicFolderId=$musicFolderId"
) {
client.api.getStarred(musicFolderId = musicFolderId).execute()
}
}

View File

@ -27,13 +27,15 @@ class SubsonicApiGetVideosListTest : SubsonicAPIClientTest() {
assertResponseSuccessful(response)
with(response.body()!!.videosList) {
size `should be equal to` 1
this[0] `should equal` MusicDirectoryChild(id = "10402", parent = "10401",
isDir = false, title = "MVI_0512", album = "Incoming", size = 21889646,
contentType = "video/avi", suffix = "avi",
transcodedContentType = "video/x-flv", transcodedSuffix = "flv",
path = "Incoming/MVI_0512.avi", isVideo = true,
playCount = 0, created = parseDate("2017-11-19T12:34:33.000Z"),
type = "video")
this[0] `should equal` MusicDirectoryChild(
id = "10402", parent = "10401",
isDir = false, title = "MVI_0512", album = "Incoming", size = 21889646,
contentType = "video/avi", suffix = "avi",
transcodedContentType = "video/x-flv", transcodedSuffix = "flv",
path = "Incoming/MVI_0512.avi", isVideo = true,
playCount = 0, created = parseDate("2017-11-19T12:34:33.000Z"),
type = "video"
)
}
}
}

View File

@ -51,15 +51,17 @@ class SubsonicApiJukeboxControlTest : SubsonicAPIClientTest() {
gain `should be equal to` 0.88f
position `should be equal to` 2
playlistEntries.size `should be equal to` 2
playlistEntries[1] `should equal` MusicDirectoryChild(id = "4215", parent = "4186",
isDir = false, title = "Going to Hell", album = "Going to Hell",
artist = "The Pretty Reckless", track = 2, year = 2014, genre = "Hard Rock",
coverArt = "4186", size = 11089627, contentType = "audio/mpeg",
suffix = "mp3", duration = 277, bitRate = 320,
path = "The Pretty Reckless/Going to Hell/02 Going to Hell.mp3",
isVideo = false, playCount = 0, discNumber = 1,
created = parseDate("2016-10-23T21:30:41.000Z"), albumId = "388",
artistId = "238", type = "music")
playlistEntries[1] `should equal` MusicDirectoryChild(
id = "4215", parent = "4186",
isDir = false, title = "Going to Hell", album = "Going to Hell",
artist = "The Pretty Reckless", track = 2, year = 2014, genre = "Hard Rock",
coverArt = "4186", size = 11089627, contentType = "audio/mpeg",
suffix = "mp3", duration = 277, bitRate = 320,
path = "The Pretty Reckless/Going to Hell/02 Going to Hell.mp3",
isVideo = false, playCount = 0, discNumber = 1,
created = parseDate("2016-10-23T21:30:41.000Z"), albumId = "388",
artistId = "238", type = "music"
)
}
}

View File

@ -1,10 +1,5 @@
package org.moire.ultrasonic.api.subsonic
import okhttp3.mockwebserver.MockWebServer
import org.amshove.kluent.`should throw`
import org.junit.After
import org.junit.Before
import org.junit.Test
import java.io.InputStream
import java.net.InetAddress
import java.security.KeyStore
@ -14,6 +9,11 @@ import javax.net.ssl.KeyManagerFactory
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLHandshakeException
import javax.net.ssl.TrustManagerFactory
import okhttp3.mockwebserver.MockWebServer
import org.amshove.kluent.`should throw`
import org.junit.After
import org.junit.Before
import org.junit.Test
private const val PORT = 8443
private const val HOST = "localhost"
@ -26,8 +26,11 @@ class SubsonicApiSSLTest {
@Before
fun setUp() {
val sslContext = createSSLContext(loadResourceStream("self-signed.pem"),
loadResourceStream("self-signed.p12"), "")
val sslContext = createSSLContext(
loadResourceStream("self-signed.pem"),
loadResourceStream("self-signed.p12"),
""
)
mockWebServer.useHttps(sslContext.socketFactory, false)
mockWebServer.start(InetAddress.getByName(HOST), PORT)
}
@ -47,11 +50,13 @@ class SubsonicApiSSLTest {
trustStore.load(null)
certificatePemStream.use {
cert = (CertificateFactory.getInstance("X.509")
.generateCertificate(certificatePemStream)) as X509Certificate
cert = (
CertificateFactory.getInstance("X.509")
.generateCertificate(certificatePemStream)
) as X509Certificate
}
val alias = cert?.subjectX500Principal?.name
?: throw IllegalStateException("Failed to load certificate")
?: throw IllegalStateException("Failed to load certificate")
trustStore.setCertificateEntry(alias, cert)
val tmf = TrustManagerFactory.getInstance("X509")

View File

@ -1,7 +1,7 @@
package org.moire.ultrasonic.api.subsonic
import org.junit.Test
import java.util.Calendar
import org.junit.Test
/**
* Integration test for [SubsonicAPIClient] for scrobble call.
@ -27,8 +27,10 @@ class SubsonicApiScrobbleTest : SubsonicAPIClientTest() {
fun `Should pass id param in request`() {
val id = "some-id"
mockWebServerRule.assertRequestParam(responseResourceName = "ping_ok.json",
expectedParam = "id=$id") {
mockWebServerRule.assertRequestParam(
responseResourceName = "ping_ok.json",
expectedParam = "id=$id"
) {
client.api.scrobble(id = id).execute()
}
}
@ -37,8 +39,10 @@ class SubsonicApiScrobbleTest : SubsonicAPIClientTest() {
fun `Should pass time param in request`() {
val time = Calendar.getInstance().timeInMillis
mockWebServerRule.assertRequestParam(responseResourceName = "ping_ok.json",
expectedParam = "time=$time") {
mockWebServerRule.assertRequestParam(
responseResourceName = "ping_ok.json",
expectedParam = "time=$time"
) {
client.api.scrobble(id = "some-id", time = time).execute()
}
}
@ -47,8 +51,10 @@ class SubsonicApiScrobbleTest : SubsonicAPIClientTest() {
fun `Should pass submission param in request`() {
val submission = false
mockWebServerRule.assertRequestParam(responseResourceName = "ping_ok.json",
expectedParam = "submission=$submission") {
mockWebServerRule.assertRequestParam(
responseResourceName = "ping_ok.json",
expectedParam = "submission=$submission"
) {
client.api.scrobble(id = "some-id", submission = submission).execute()
}
}

View File

@ -1,12 +1,12 @@
package org.moire.ultrasonic.api.subsonic
import java.util.Calendar
import org.amshove.kluent.`should be equal to`
import org.amshove.kluent.`should equal`
import org.amshove.kluent.`should not be`
import org.junit.Test
import org.moire.ultrasonic.api.subsonic.models.MusicDirectoryChild
import org.moire.ultrasonic.api.subsonic.models.SearchResult
import java.util.Calendar
/**
* Integration test for [SubsonicAPIClient] for search call.
@ -33,17 +33,19 @@ class SubsonicApiSearchTest : SubsonicAPIClientTest() {
offset `should be equal to` 10
totalHits `should be equal to` 53
matchList.size `should be equal to` 1
matchList[0] `should equal` MusicDirectoryChild(id = "5831", parent = "5766",
isDir = false, title = "You'll Be Under My Wheels",
album = "Need for Speed Most Wanted", artist = "The Prodigy",
track = 17, year = 2005, genre = "Rap", coverArt = "5766",
size = 5607024, contentType = "audio/mpeg", suffix = "mp3", duration = 233,
bitRate = 192,
path = "Compilations/Need for Speed Most Wanted/17 You'll Be Under My Wheels" +
".mp3",
isVideo = false, playCount = 0, discNumber = 1,
created = parseDate("2016-10-23T20:09:02.000Z"), albumId = "568",
artistId = "505", type = "music")
matchList[0] `should equal` MusicDirectoryChild(
id = "5831", parent = "5766",
isDir = false, title = "You'll Be Under My Wheels",
album = "Need for Speed Most Wanted", artist = "The Prodigy",
track = 17, year = 2005, genre = "Rap", coverArt = "5766",
size = 5607024, contentType = "audio/mpeg", suffix = "mp3", duration = 233,
bitRate = 192,
path = "Compilations/Need for Speed Most Wanted/17 You'll Be Under My Wheels" +
".mp3",
isVideo = false, playCount = 0, discNumber = 1,
created = parseDate("2016-10-23T20:09:02.000Z"), albumId = "568",
artistId = "505", type = "music"
)
}
}
@ -51,8 +53,10 @@ class SubsonicApiSearchTest : SubsonicAPIClientTest() {
fun `Should pass artist param`() {
val artist = "some-artist"
mockWebServerRule.assertRequestParam(responseResourceName = "search_ok.json",
expectedParam = "artist=$artist") {
mockWebServerRule.assertRequestParam(
responseResourceName = "search_ok.json",
expectedParam = "artist=$artist"
) {
client.api.search(artist = artist).execute()
}
}
@ -61,8 +65,10 @@ class SubsonicApiSearchTest : SubsonicAPIClientTest() {
fun `Should pass album param`() {
val album = "some-album"
mockWebServerRule.assertRequestParam(responseResourceName = "search_ok.json",
expectedParam = "album=$album") {
mockWebServerRule.assertRequestParam(
responseResourceName = "search_ok.json",
expectedParam = "album=$album"
) {
client.api.search(album = album).execute()
}
}
@ -71,8 +77,10 @@ class SubsonicApiSearchTest : SubsonicAPIClientTest() {
fun `Should pass title param`() {
val title = "some-title"
mockWebServerRule.assertRequestParam(responseResourceName = "search_ok.json",
expectedParam = "title=$title") {
mockWebServerRule.assertRequestParam(
responseResourceName = "search_ok.json",
expectedParam = "title=$title"
) {
client.api.search(title = title).execute()
}
}
@ -81,8 +89,10 @@ class SubsonicApiSearchTest : SubsonicAPIClientTest() {
fun `Should contain any param`() {
val any = "AnyString"
mockWebServerRule.assertRequestParam(responseResourceName = "search_ok.json",
expectedParam = "any=$any") {
mockWebServerRule.assertRequestParam(
responseResourceName = "search_ok.json",
expectedParam = "any=$any"
) {
client.api.search(any = any).execute()
}
}
@ -91,8 +101,10 @@ class SubsonicApiSearchTest : SubsonicAPIClientTest() {
fun `Should contain count param`() {
val count = 11
mockWebServerRule.assertRequestParam(responseResourceName = "search_ok.json",
expectedParam = "count=$count") {
mockWebServerRule.assertRequestParam(
responseResourceName = "search_ok.json",
expectedParam = "count=$count"
) {
client.api.search(count = count).execute()
}
}
@ -101,8 +113,10 @@ class SubsonicApiSearchTest : SubsonicAPIClientTest() {
fun `Should contain offset param`() {
val offset = 54
mockWebServerRule.assertRequestParam(responseResourceName = "search_ok.json",
expectedParam = "offset=$offset") {
mockWebServerRule.assertRequestParam(
responseResourceName = "search_ok.json",
expectedParam = "offset=$offset"
) {
client.api.search(offset = offset).execute()
}
}
@ -111,8 +125,10 @@ class SubsonicApiSearchTest : SubsonicAPIClientTest() {
fun `Should contain newerThan param`() {
val newerThan = Calendar.getInstance()
mockWebServerRule.assertRequestParam(responseResourceName = "search_ok.json",
expectedParam = "newerThan=${newerThan.time.time}") {
mockWebServerRule.assertRequestParam(
responseResourceName = "search_ok.json",
expectedParam = "newerThan=${newerThan.time.time}"
) {
client.api.search(newerThan = newerThan.time.time).execute()
}
}

View File

@ -32,26 +32,32 @@ class SubsonicApiSearchThreeTest : SubsonicAPIClientTest() {
assertResponseSuccessful(response)
with(response.body()!!.searchResult) {
artistList.size `should be equal to` 1
artistList[0] `should equal` Artist(id = "505", name = "The Prodigy",
coverArt = "ar-505", albumCount = 5)
artistList[0] `should equal` Artist(
id = "505", name = "The Prodigy",
coverArt = "ar-505", albumCount = 5
)
albumList.size `should be equal to` 1
albumList[0] `should equal` Album(id = "855",
name = "Always Outnumbered, Never Outgunned",
artist = "The Prodigy", artistId = "505", coverArt = "al-855", songCount = 12,
duration = 3313, created = parseDate("2016-10-23T20:57:27.000Z"),
year = 2004, genre = "Electronic")
albumList[0] `should equal` Album(
id = "855",
name = "Always Outnumbered, Never Outgunned",
artist = "The Prodigy", artistId = "505", coverArt = "al-855", songCount = 12,
duration = 3313, created = parseDate("2016-10-23T20:57:27.000Z"),
year = 2004, genre = "Electronic"
)
songList.size `should be equal to` 1
songList[0] `should equal` MusicDirectoryChild(id = "5831", parent = "5766",
isDir = false,
title = "You'll Be Under My Wheels", album = "Need for Speed Most Wanted",
artist = "The Prodigy", track = 17, year = 2005, genre = "Rap",
coverArt = "5766", size = 5607024, contentType = "audio/mpeg",
suffix = "mp3", duration = 233, bitRate = 192,
path = "Compilations/Need for Speed Most Wanted/17 You'll Be Under My Wheels" +
".mp3",
isVideo = false, playCount = 0, discNumber = 1,
created = parseDate("2016-10-23T20:09:02.000Z"), albumId = "568",
artistId = "505", type = "music")
songList[0] `should equal` MusicDirectoryChild(
id = "5831", parent = "5766",
isDir = false,
title = "You'll Be Under My Wheels", album = "Need for Speed Most Wanted",
artist = "The Prodigy", track = 17, year = 2005, genre = "Rap",
coverArt = "5766", size = 5607024, contentType = "audio/mpeg",
suffix = "mp3", duration = 233, bitRate = 192,
path = "Compilations/Need for Speed Most Wanted/17 You'll Be Under My Wheels" +
".mp3",
isVideo = false, playCount = 0, discNumber = 1,
created = parseDate("2016-10-23T20:09:02.000Z"), albumId = "568",
artistId = "505", type = "music"
)
}
}
@ -59,8 +65,10 @@ class SubsonicApiSearchThreeTest : SubsonicAPIClientTest() {
fun `Should pass query as request param`() {
val query = "some-wip-query"
mockWebServerRule.assertRequestParam(responseResourceName = "search3_ok.json",
expectedParam = "query=$query") {
mockWebServerRule.assertRequestParam(
responseResourceName = "search3_ok.json",
expectedParam = "query=$query"
) {
client.api.search3(query = query).execute()
}
}
@ -69,8 +77,10 @@ class SubsonicApiSearchThreeTest : SubsonicAPIClientTest() {
fun `Should pass artist count as request param`() {
val artistCount = 67
mockWebServerRule.assertRequestParam(responseResourceName = "search3_ok.json",
expectedParam = "artistCount=$artistCount") {
mockWebServerRule.assertRequestParam(
responseResourceName = "search3_ok.json",
expectedParam = "artistCount=$artistCount"
) {
client.api.search3("some", artistCount = artistCount).execute()
}
}
@ -79,8 +89,10 @@ class SubsonicApiSearchThreeTest : SubsonicAPIClientTest() {
fun `Should pass artist offset as request param`() {
val artistOffset = 34
mockWebServerRule.assertRequestParam(responseResourceName = "search3_ok.json",
expectedParam = "artistOffset=$artistOffset") {
mockWebServerRule.assertRequestParam(
responseResourceName = "search3_ok.json",
expectedParam = "artistOffset=$artistOffset"
) {
client.api.search3("some", artistOffset = artistOffset).execute()
}
}
@ -89,8 +101,10 @@ class SubsonicApiSearchThreeTest : SubsonicAPIClientTest() {
fun `Should pass album count as request param`() {
val albumCount = 21
mockWebServerRule.assertRequestParam(responseResourceName = "search3_ok.json",
expectedParam = "albumCount=$albumCount") {
mockWebServerRule.assertRequestParam(
responseResourceName = "search3_ok.json",
expectedParam = "albumCount=$albumCount"
) {
client.api.search3("some", albumCount = albumCount).execute()
}
}
@ -99,8 +113,10 @@ class SubsonicApiSearchThreeTest : SubsonicAPIClientTest() {
fun `Should pass album offset as request param`() {
val albumOffset = 43
mockWebServerRule.assertRequestParam(responseResourceName = "search3_ok.json",
expectedParam = "albumOffset=$albumOffset") {
mockWebServerRule.assertRequestParam(
responseResourceName = "search3_ok.json",
expectedParam = "albumOffset=$albumOffset"
) {
client.api.search3("some", albumOffset = albumOffset).execute()
}
}
@ -109,8 +125,10 @@ class SubsonicApiSearchThreeTest : SubsonicAPIClientTest() {
fun `Should pass song count as request param`() {
val songCount = 15
mockWebServerRule.assertRequestParam(responseResourceName = "search3_ok.json",
expectedParam = "songCount=$songCount") {
mockWebServerRule.assertRequestParam(
responseResourceName = "search3_ok.json",
expectedParam = "songCount=$songCount"
) {
client.api.search3("some", songCount = songCount).execute()
}
}
@ -119,8 +137,10 @@ class SubsonicApiSearchThreeTest : SubsonicAPIClientTest() {
fun `Should pass music folder id as request param`() {
val musicFolderId = "43"
mockWebServerRule.assertRequestParam(responseResourceName = "search3_ok.json",
expectedParam = "musicFolderId=$musicFolderId") {
mockWebServerRule.assertRequestParam(
responseResourceName = "search3_ok.json",
expectedParam = "musicFolderId=$musicFolderId"
) {
client.api.search3("some", musicFolderId = musicFolderId).execute()
}
}

View File

@ -33,23 +33,27 @@ class SubsonicApiSearchTwoTest : SubsonicAPIClientTest() {
artistList.size `should be equal to` 1
artistList[0] `should equal` Artist(id = "522", name = "The Prodigy")
albumList.size `should be equal to` 1
albumList[0] `should equal` MusicDirectoryChild(id = "8867", parent = "522",
isDir = true, title = "Always Outnumbered, Never Outgunned",
album = "Always Outnumbered, Never Outgunned", artist = "The Prodigy",
year = 2004, genre = "Electronic", coverArt = "8867", playCount = 0,
created = parseDate("2016-10-23T20:57:27.000Z"))
albumList[0] `should equal` MusicDirectoryChild(
id = "8867", parent = "522",
isDir = true, title = "Always Outnumbered, Never Outgunned",
album = "Always Outnumbered, Never Outgunned", artist = "The Prodigy",
year = 2004, genre = "Electronic", coverArt = "8867", playCount = 0,
created = parseDate("2016-10-23T20:57:27.000Z")
)
songList.size `should be equal to` 1
songList[0] `should equal` MusicDirectoryChild(id = "5831", parent = "5766",
isDir = false,
title = "You'll Be Under My Wheels", album = "Need for Speed Most Wanted",
artist = "The Prodigy", track = 17, year = 2005, genre = "Rap",
coverArt = "5766", size = 5607024, contentType = "audio/mpeg",
suffix = "mp3", duration = 233, bitRate = 192,
path = "Compilations/Need for Speed Most Wanted/17 You'll Be Under My Wheels" +
".mp3",
isVideo = false, playCount = 0, discNumber = 1,
created = parseDate("2016-10-23T20:09:02.000Z"),
albumId = "568", artistId = "505", type = "music")
songList[0] `should equal` MusicDirectoryChild(
id = "5831", parent = "5766",
isDir = false,
title = "You'll Be Under My Wheels", album = "Need for Speed Most Wanted",
artist = "The Prodigy", track = 17, year = 2005, genre = "Rap",
coverArt = "5766", size = 5607024, contentType = "audio/mpeg",
suffix = "mp3", duration = 233, bitRate = 192,
path = "Compilations/Need for Speed Most Wanted/17 You'll Be Under My Wheels" +
".mp3",
isVideo = false, playCount = 0, discNumber = 1,
created = parseDate("2016-10-23T20:09:02.000Z"),
albumId = "568", artistId = "505", type = "music"
)
}
}
@ -57,8 +61,10 @@ class SubsonicApiSearchTwoTest : SubsonicAPIClientTest() {
fun `Should pass query id in request param`() {
val query = "some"
mockWebServerRule.assertRequestParam(responseResourceName = "search2_ok.json",
expectedParam = "query=$query") {
mockWebServerRule.assertRequestParam(
responseResourceName = "search2_ok.json",
expectedParam = "query=$query"
) {
client.api.search2(query).execute()
}
}
@ -67,8 +73,10 @@ class SubsonicApiSearchTwoTest : SubsonicAPIClientTest() {
fun `Should pass artist count in request param`() {
val artistCount = 45
mockWebServerRule.assertRequestParam(responseResourceName = "search2_ok.json",
expectedParam = "artistCount=$artistCount") {
mockWebServerRule.assertRequestParam(
responseResourceName = "search2_ok.json",
expectedParam = "artistCount=$artistCount"
) {
client.api.search2("some", artistCount = artistCount).execute()
}
}
@ -77,8 +85,10 @@ class SubsonicApiSearchTwoTest : SubsonicAPIClientTest() {
fun `Should pass artist offset in request param`() {
val artistOffset = 13
mockWebServerRule.assertRequestParam(responseResourceName = "search2_ok.json",
expectedParam = "artistOffset=$artistOffset") {
mockWebServerRule.assertRequestParam(
responseResourceName = "search2_ok.json",
expectedParam = "artistOffset=$artistOffset"
) {
client.api.search2("some", artistOffset = artistOffset).execute()
}
}
@ -87,8 +97,10 @@ class SubsonicApiSearchTwoTest : SubsonicAPIClientTest() {
fun `Should pass album count in request param`() {
val albumCount = 30
mockWebServerRule.assertRequestParam(responseResourceName = "search2_ok.json",
expectedParam = "albumCount=$albumCount") {
mockWebServerRule.assertRequestParam(
responseResourceName = "search2_ok.json",
expectedParam = "albumCount=$albumCount"
) {
client.api.search2("some", albumCount = albumCount).execute()
}
}
@ -97,8 +109,10 @@ class SubsonicApiSearchTwoTest : SubsonicAPIClientTest() {
fun `Should pass album offset in request param`() {
val albumOffset = 91
mockWebServerRule.assertRequestParam(responseResourceName = "search2_ok.json",
expectedParam = "albumOffset=$albumOffset") {
mockWebServerRule.assertRequestParam(
responseResourceName = "search2_ok.json",
expectedParam = "albumOffset=$albumOffset"
) {
client.api.search2("some", albumOffset = albumOffset).execute()
}
}
@ -107,8 +121,10 @@ class SubsonicApiSearchTwoTest : SubsonicAPIClientTest() {
fun `Should pass song count in request param`() {
val songCount = 22
mockWebServerRule.assertRequestParam(responseResourceName = "search2_ok.json",
expectedParam = "songCount=$songCount") {
mockWebServerRule.assertRequestParam(
responseResourceName = "search2_ok.json",
expectedParam = "songCount=$songCount"
) {
client.api.search2("some", songCount = songCount).execute()
}
}
@ -117,8 +133,10 @@ class SubsonicApiSearchTwoTest : SubsonicAPIClientTest() {
fun `Should pass music folder id in request param`() {
val musicFolderId = "565"
mockWebServerRule.assertRequestParam(responseResourceName = "search2_ok.json",
expectedParam = "musicFolderId=$musicFolderId") {
mockWebServerRule.assertRequestParam(
responseResourceName = "search2_ok.json",
expectedParam = "musicFolderId=$musicFolderId"
) {
client.api.search2("some", musicFolderId = musicFolderId).execute()
}
}

View File

@ -36,8 +36,10 @@ class SubsonicApiSetRatingTest : SubsonicAPIClientTest() {
val id = "110"
val rating = 5
mockWebServerRule.assertRequestParam(responseResourceName = "ping_ok.json",
expectedParam = "id=$id") {
mockWebServerRule.assertRequestParam(
responseResourceName = "ping_ok.json",
expectedParam = "id=$id"
) {
client.api.setRating(id, rating).execute()
}
}
@ -47,8 +49,10 @@ class SubsonicApiSetRatingTest : SubsonicAPIClientTest() {
val id = "110"
val rating = 5
mockWebServerRule.assertRequestParam(responseResourceName = "ping_ok.json",
expectedParam = "rating=$rating") {
mockWebServerRule.assertRequestParam(
responseResourceName = "ping_ok.json",
expectedParam = "rating=$rating"
) {
client.api.setRating(id, rating).execute()
}
}

View File

@ -29,8 +29,10 @@ class SubsonicApiStarTest : SubsonicAPIClientTest() {
fun `Should pass id param`() {
val id = "110"
mockWebServerRule.assertRequestParam(responseResourceName = "ping_ok.json",
expectedParam = "id=$id") {
mockWebServerRule.assertRequestParam(
responseResourceName = "ping_ok.json",
expectedParam = "id=$id"
) {
client.api.star(id = id).execute()
}
}
@ -39,8 +41,10 @@ class SubsonicApiStarTest : SubsonicAPIClientTest() {
fun `Should pass artist id param`() {
val artistId = "123"
mockWebServerRule.assertRequestParam(responseResourceName = "ping_ok.json",
expectedParam = "artistId=$artistId") {
mockWebServerRule.assertRequestParam(
responseResourceName = "ping_ok.json",
expectedParam = "artistId=$artistId"
) {
client.api.star(artistId = artistId).execute()
}
}
@ -49,8 +53,10 @@ class SubsonicApiStarTest : SubsonicAPIClientTest() {
fun `Should pass album id param`() {
val albumId = "1001"
mockWebServerRule.assertRequestParam(responseResourceName = "ping_ok.json",
expectedParam = "albumId=$albumId") {
mockWebServerRule.assertRequestParam(
responseResourceName = "ping_ok.json",
expectedParam = "albumId=$albumId"
) {
client.api.star(albumId = albumId).execute()
}
}

View File

@ -40,8 +40,10 @@ class SubsonicApiStreamTest : SubsonicAPIClientTest() {
@Test
fun `Should return successfull call stream`() {
mockWebServerRule.mockWebServer.enqueue(MockResponse()
.setBody(mockWebServerRule.loadJsonResponse("ping_ok.json")))
mockWebServerRule.mockWebServer.enqueue(
MockResponse()
.setBody(mockWebServerRule.loadJsonResponse("ping_ok.json"))
)
val response = client.stream("some-id")
@ -67,8 +69,10 @@ class SubsonicApiStreamTest : SubsonicAPIClientTest() {
fun `Should pass max bit rate as param`() {
val maxBitRate = 360
mockWebServerRule.assertRequestParam("ping_ok.json",
"maxBitRate=$maxBitRate") {
mockWebServerRule.assertRequestParam(
"ping_ok.json",
"maxBitRate=$maxBitRate"
) {
client.api.stream("some-id", maxBitRate = maxBitRate).execute()
}
}
@ -77,8 +81,10 @@ class SubsonicApiStreamTest : SubsonicAPIClientTest() {
fun `Should pass format as param`() {
val format = "aac"
mockWebServerRule.assertRequestParam("ping_ok.json",
"format=$format") {
mockWebServerRule.assertRequestParam(
"ping_ok.json",
"format=$format"
) {
client.api.stream("some-id", format = format).execute()
}
}
@ -87,8 +93,10 @@ class SubsonicApiStreamTest : SubsonicAPIClientTest() {
fun `Should pass time offset as param`() {
val timeOffset = 155
mockWebServerRule.assertRequestParam("ping_ok.json",
"timeOffset=$timeOffset") {
mockWebServerRule.assertRequestParam(
"ping_ok.json",
"timeOffset=$timeOffset"
) {
client.api.stream("some-id", timeOffset = timeOffset).execute()
}
}
@ -97,8 +105,10 @@ class SubsonicApiStreamTest : SubsonicAPIClientTest() {
fun `Should pass video size as param`() {
val videoSize = "44144"
mockWebServerRule.assertRequestParam("ping_ok.json",
"size=$videoSize") {
mockWebServerRule.assertRequestParam(
"ping_ok.json",
"size=$videoSize"
) {
client.api.stream("some-id", videoSize = videoSize).execute()
}
}
@ -107,8 +117,10 @@ class SubsonicApiStreamTest : SubsonicAPIClientTest() {
fun `Should pass estimate content length as param`() {
val estimateContentLength = true
mockWebServerRule.assertRequestParam("ping_ok.json",
"estimateContentLength=$estimateContentLength") {
mockWebServerRule.assertRequestParam(
"ping_ok.json",
"estimateContentLength=$estimateContentLength"
) {
client.api.stream("some-id", estimateContentLength = estimateContentLength).execute()
}
}
@ -117,8 +129,10 @@ class SubsonicApiStreamTest : SubsonicAPIClientTest() {
fun `Should pass converted as param`() {
val converted = false
mockWebServerRule.assertRequestParam("ping_ok.json",
"converted=$converted") {
mockWebServerRule.assertRequestParam(
"ping_ok.json",
"converted=$converted"
) {
client.api.stream("some-id", converted = converted).execute()
}
}

View File

@ -20,17 +20,22 @@ class SubsonicApiUnstarTest : SubsonicAPIClientTest() {
@Test
fun `Should parse error response`() {
checkErrorCallParsed(mockWebServerRule, {
client.api.unstar().execute()
})
checkErrorCallParsed(
mockWebServerRule,
{
client.api.unstar().execute()
}
)
}
@Test
fun `Should pass id param`() {
val id = "545"
mockWebServerRule.assertRequestParam(responseResourceName = "ping_ok.json",
expectedParam = "id=$id") {
mockWebServerRule.assertRequestParam(
responseResourceName = "ping_ok.json",
expectedParam = "id=$id"
) {
client.api.unstar(id = id).execute()
}
}
@ -39,8 +44,10 @@ class SubsonicApiUnstarTest : SubsonicAPIClientTest() {
fun `Should pass artistId param`() {
val artistId = "644"
mockWebServerRule.assertRequestParam(responseResourceName = "ping_ok.json",
expectedParam = "artistId=$artistId") {
mockWebServerRule.assertRequestParam(
responseResourceName = "ping_ok.json",
expectedParam = "artistId=$artistId"
) {
client.api.unstar(artistId = artistId).execute()
}
}
@ -49,8 +56,10 @@ class SubsonicApiUnstarTest : SubsonicAPIClientTest() {
fun `Should pass albumId param`() {
val albumId = "3344"
mockWebServerRule.assertRequestParam(responseResourceName = "ping_ok.json",
expectedParam = "albumId=$albumId") {
mockWebServerRule.assertRequestParam(
responseResourceName = "ping_ok.json",
expectedParam = "albumId=$albumId"
) {
client.api.unstar(albumId = albumId).execute()
}
}

View File

@ -26,8 +26,10 @@ class SubsonicApiUpdatePlaylistTest : SubsonicAPIClientTest() {
fun `Should pass playlist id param in request`() {
val id = "5453"
mockWebServerRule.assertRequestParam(responseResourceName = "ping_ok.json",
expectedParam = "playlistId=$id") {
mockWebServerRule.assertRequestParam(
responseResourceName = "ping_ok.json",
expectedParam = "playlistId=$id"
) {
client.api.updatePlaylist(id = id).execute()
}
}
@ -36,8 +38,10 @@ class SubsonicApiUpdatePlaylistTest : SubsonicAPIClientTest() {
fun `Should pass name param in request`() {
val name = "some-name"
mockWebServerRule.assertRequestParam(responseResourceName = "ping_ok.json",
expectedParam = "name=$name") {
mockWebServerRule.assertRequestParam(
responseResourceName = "ping_ok.json",
expectedParam = "name=$name"
) {
client.api.updatePlaylist("22", name = name).execute()
}
}
@ -46,8 +50,10 @@ class SubsonicApiUpdatePlaylistTest : SubsonicAPIClientTest() {
fun `Should pass comment param in request`() {
val comment = "some-unusual-comment"
mockWebServerRule.assertRequestParam(responseResourceName = "ping_ok.json",
expectedParam = "comment=$comment") {
mockWebServerRule.assertRequestParam(
responseResourceName = "ping_ok.json",
expectedParam = "comment=$comment"
) {
client.api.updatePlaylist("42", comment = comment).execute()
}
}
@ -56,8 +62,10 @@ class SubsonicApiUpdatePlaylistTest : SubsonicAPIClientTest() {
fun `Should pass public param in request`() {
val public = true
mockWebServerRule.assertRequestParam(responseResourceName = "ping_ok.json",
expectedParam = "public=$public") {
mockWebServerRule.assertRequestParam(
responseResourceName = "ping_ok.json",
expectedParam = "public=$public"
) {
client.api.updatePlaylist("53", public = public).execute()
}
}
@ -66,8 +74,10 @@ class SubsonicApiUpdatePlaylistTest : SubsonicAPIClientTest() {
fun `Should pass song ids to update in request`() {
val songIds = listOf("45", "81")
mockWebServerRule.assertRequestParam(responseResourceName = "ping_ok.json",
expectedParam = "songIdToAdd=${songIds[0]}&songIdToAdd=${songIds[1]}") {
mockWebServerRule.assertRequestParam(
responseResourceName = "ping_ok.json",
expectedParam = "songIdToAdd=${songIds[0]}&songIdToAdd=${songIds[1]}"
) {
client.api.updatePlaylist("25", songIdsToAdd = songIds).execute()
}
}
@ -76,9 +86,11 @@ class SubsonicApiUpdatePlaylistTest : SubsonicAPIClientTest() {
fun `Should pass song indexes to remove in request`() {
val songIndexesToRemove = listOf(129, 1)
mockWebServerRule.assertRequestParam(responseResourceName = "ping_ok.json",
expectedParam = "songIndexToRemove=${songIndexesToRemove[0]}&" +
"songIndexToRemove=${songIndexesToRemove[1]}") {
mockWebServerRule.assertRequestParam(
responseResourceName = "ping_ok.json",
expectedParam = "songIndexToRemove=${songIndexesToRemove[0]}&" +
"songIndexToRemove=${songIndexesToRemove[1]}"
) {
client.api.updatePlaylist("49", songIndexesToRemove = songIndexesToRemove).execute()
}
}

View File

@ -29,7 +29,7 @@ abstract class BaseInterceptorTest {
* params to the [Request].
*/
fun createRequest(additionalParams: (Request.Builder) -> Unit): Request = Request.Builder()
.url(mockWebServerRule.mockWebServer.url("/"))
.also { additionalParams(it) }
.build()
.url(mockWebServerRule.mockWebServer.url("/"))
.also { additionalParams(it) }
.build()
}

View File

@ -1,12 +1,12 @@
package org.moire.ultrasonic.api.subsonic.interceptors
import java.security.MessageDigest
import okhttp3.Interceptor
import okhttp3.mockwebserver.MockResponse
import org.amshove.kluent.`should contain`
import org.amshove.kluent.`should not contain`
import org.apache.commons.codec.binary.Hex
import org.junit.Test
import java.security.MessageDigest
/**
* Integration test for [PasswordMD5Interceptor].
@ -28,9 +28,14 @@ class PasswordMD5InterceptorTest : BaseInterceptorTest() {
requestLine `should not contain` "p=enc:"
val salt = requestLine.split('&').find { it.startsWith("s=") }
?.substringAfter('=')?.substringBefore(" ")
val expectedToken = String(Hex.encodeHex(MessageDigest.getInstance("MD5")
.digest("$password$salt".toByteArray()), true))
?.substringAfter('=')?.substringBefore(" ")
val expectedToken = String(
Hex.encodeHex(
MessageDigest.getInstance("MD5")
.digest("$password$salt".toByteArray()),
true
)
)
requestLine `should contain` "t=$expectedToken"
}
}

View File

@ -1,5 +1,6 @@
package org.moire.ultrasonic.api.subsonic.interceptors
import kotlin.LazyThreadSafetyMode.NONE
import okhttp3.Interceptor
import okhttp3.mockwebserver.MockResponse
import org.amshove.kluent.`should contain`
@ -7,7 +8,6 @@ import org.amshove.kluent.`should equal`
import org.junit.Test
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions
import org.moire.ultrasonic.api.subsonic.enqueueResponse
import kotlin.LazyThreadSafetyMode.NONE
/**
* Integration test for [VersionInterceptor].
@ -41,7 +41,7 @@ class VersionInterceptorTest : BaseInterceptorTest() {
client.newCall(createRequest {}).execute()
(interceptor as VersionInterceptor)
.protocolVersion `should equal` SubsonicAPIVersions.V1_13_0
.protocolVersion `should equal` SubsonicAPIVersions.V1_13_0
}
@Test
@ -51,7 +51,7 @@ class VersionInterceptorTest : BaseInterceptorTest() {
client.newCall(createRequest {}).execute()
(interceptor as VersionInterceptor)
.protocolVersion `should equal` SubsonicAPIVersions.V1_16_0
.protocolVersion `should equal` SubsonicAPIVersions.V1_16_0
}
@Test
@ -65,9 +65,11 @@ class VersionInterceptorTest : BaseInterceptorTest() {
@Test
fun `Should not update version on non-json response`() {
mockWebServerRule.mockWebServer.enqueue(MockResponse()
mockWebServerRule.mockWebServer.enqueue(
MockResponse()
.setBody("asdqwnekjnqwkjen")
.setHeader("Content-Type", "application/octet-stream"))
.setHeader("Content-Type", "application/octet-stream")
)
client.newCall(createRequest {}).execute()

View File

@ -82,8 +82,9 @@ internal class ApiVersionCheckWrapper(
): Call<SearchTwoResponse> {
checkVersion(V1_4_0)
checkParamVersion(musicFolderId, V1_12_0)
return api.search2(query, artistCount, artistOffset, albumCount, albumOffset, songCount,
musicFolderId)
return api.search2(
query, artistCount, artistOffset, albumCount, albumOffset, songCount, musicFolderId
)
}
override fun search3(
@ -97,8 +98,9 @@ internal class ApiVersionCheckWrapper(
): Call<SearchThreeResponse> {
checkVersion(V1_8_0)
checkParamVersion(musicFolderId, V1_12_0)
return api.search3(query, artistCount, artistOffset, albumCount, albumOffset,
songCount, musicFolderId)
return api.search3(
query, artistCount, artistOffset, albumCount, albumOffset, songCount, musicFolderId
)
}
override fun getPlaylists(username: String?): Call<GetPlaylistsResponse> {
@ -216,8 +218,9 @@ internal class ApiVersionCheckWrapper(
checkParamVersion(videoSize, V1_6_0)
checkParamVersion(estimateContentLength, V1_8_0)
checkParamVersion(converted, V1_14_0)
return api.stream(id, maxBitRate, format, timeOffset, videoSize,
estimateContentLength, converted)
return api.stream(
id, maxBitRate, format, timeOffset, videoSize, estimateContentLength, converted
)
}
override fun jukeboxControl(

View File

@ -4,6 +4,11 @@ import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.fasterxml.jackson.module.kotlin.readValue
import java.security.SecureRandom
import java.security.cert.X509Certificate
import java.util.concurrent.TimeUnit.MILLISECONDS
import javax.net.ssl.SSLContext
import javax.net.ssl.X509TrustManager
import okhttp3.OkHttpClient
import okhttp3.ResponseBody
import okhttp3.logging.HttpLoggingInterceptor
@ -17,11 +22,6 @@ import org.moire.ultrasonic.api.subsonic.response.SubsonicResponse
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.jackson.JacksonConverterFactory
import java.security.SecureRandom
import java.security.cert.X509Certificate
import java.util.concurrent.TimeUnit.MILLISECONDS
import javax.net.ssl.SSLContext
import javax.net.ssl.X509TrustManager
private const val READ_TIMEOUT = 60_000L
@ -44,10 +44,10 @@ class SubsonicAPIClient(
}
private val proxyPasswordInterceptor = ProxyPasswordInterceptor(
config.minimalProtocolVersion,
PasswordHexInterceptor(config.password),
PasswordMD5Interceptor(config.password),
config.enableLdapUserSupport
config.minimalProtocolVersion,
PasswordHexInterceptor(config.password),
PasswordMD5Interceptor(config.password),
config.enableLdapUserSupport
)
/**
@ -61,38 +61,38 @@ class SubsonicAPIClient(
}
private val okHttpClient = baseOkClient.newBuilder()
.readTimeout(READ_TIMEOUT, MILLISECONDS)
.apply { if (config.allowSelfSignedCertificate) allowSelfSignedCertificates() }
.addInterceptor { chain ->
// Adds default request params
val originalRequest = chain.request()
val newUrl = originalRequest.url().newBuilder()
.addQueryParameter("u", config.username)
.addQueryParameter("c", config.clientID)
.addQueryParameter("f", "json")
.build()
chain.proceed(originalRequest.newBuilder().url(newUrl).build())
}
.addInterceptor(versionInterceptor)
.addInterceptor(proxyPasswordInterceptor)
.addInterceptor(RangeHeaderInterceptor())
.apply { if (config.debug) addLogging() }
.build()
.readTimeout(READ_TIMEOUT, MILLISECONDS)
.apply { if (config.allowSelfSignedCertificate) allowSelfSignedCertificates() }
.addInterceptor { chain ->
// Adds default request params
val originalRequest = chain.request()
val newUrl = originalRequest.url().newBuilder()
.addQueryParameter("u", config.username)
.addQueryParameter("c", config.clientID)
.addQueryParameter("f", "json")
.build()
chain.proceed(originalRequest.newBuilder().url(newUrl).build())
}
.addInterceptor(versionInterceptor)
.addInterceptor(proxyPasswordInterceptor)
.addInterceptor(RangeHeaderInterceptor())
.apply { if (config.debug) addLogging() }
.build()
private val jacksonMapper = ObjectMapper()
.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.registerModule(KotlinModule())
.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.registerModule(KotlinModule())
private val retrofit = Retrofit.Builder()
.baseUrl("${config.baseUrl}/rest/")
.client(okHttpClient)
.addConverterFactory(JacksonConverterFactory.create(jacksonMapper))
.build()
.baseUrl("${config.baseUrl}/rest/")
.client(okHttpClient)
.addConverterFactory(JacksonConverterFactory.create(jacksonMapper))
.build()
private val wrappedApi = ApiVersionCheckWrapper(
retrofit.create(SubsonicAPIDefinition::class.java),
config.minimalProtocolVersion
retrofit.create(SubsonicAPIDefinition::class.java),
config.minimalProtocolVersion
)
val api: SubsonicAPIDefinition get() = wrappedApi
@ -118,9 +118,9 @@ class SubsonicAPIClient(
* Prefer this method over [SubsonicAPIDefinition.stream] as this handles error cases.
*/
fun stream(id: String, maxBitrate: Int? = null, offset: Long? = null): StreamResponse =
handleStreamResponse {
api.stream(id, maxBitrate, offset = offset).execute()
}
handleStreamResponse {
api.stream(id, maxBitrate, offset = offset).execute()
}
/**
* Convenient method to get user avatar using [username].
@ -138,14 +138,18 @@ class SubsonicAPIClient(
return if (response.isSuccessful) {
val responseBody = response.body()
val contentType = responseBody?.contentType()
if (contentType != null &&
contentType.type().equals("application", true) &&
contentType.subtype().equals("json", true)) {
if (
contentType != null &&
contentType.type().equals("application", true) &&
contentType.subtype().equals("json", true)
) {
val error = jacksonMapper.readValue<SubsonicResponse>(responseBody.byteStream())
StreamResponse(apiError = error.error, responseHttpCode = response.code())
} else {
StreamResponse(stream = responseBody?.byteStream(),
responseHttpCode = response.code())
StreamResponse(
stream = responseBody?.byteStream(),
responseHttpCode = response.code()
)
}
} else {
StreamResponse(responseHttpCode = response.code())

View File

@ -149,7 +149,7 @@ interface SubsonicAPIDefinition {
@Query("songIdToAdd") songIdsToAdd: List<String>? = null,
@Query("songIndexToRemove") songIndexesToRemove: List<Int>? = null
):
Call<SubsonicResponse>
Call<SubsonicResponse>
@GET("getPodcasts.view")
fun getPodcasts(

View File

@ -1,9 +1,9 @@
package org.moire.ultrasonic.api.subsonic.interceptors
import kotlin.LazyThreadSafetyMode.NONE
import okhttp3.Interceptor
import okhttp3.Interceptor.Chain
import okhttp3.Response
import kotlin.LazyThreadSafetyMode.NONE
/**
* Adds password param converted to hex string in request url.
@ -19,7 +19,7 @@ class PasswordHexInterceptor(private val password: String) : Interceptor {
override fun intercept(chain: Chain): Response {
val originalRequest = chain.request()
val updatedUrl = originalRequest.url().newBuilder()
.addEncodedQueryParameter("p", passwordHex).build()
.addEncodedQueryParameter("p", passwordHex).build()
return chain.proceed(originalRequest.newBuilder().url(updatedUrl).build())
}
}

View File

@ -1,12 +1,12 @@
package org.moire.ultrasonic.api.subsonic.interceptors
import okhttp3.Interceptor
import okhttp3.Interceptor.Chain
import okhttp3.Response
import java.math.BigInteger
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.security.SecureRandom
import okhttp3.Interceptor
import okhttp3.Interceptor.Chain
import okhttp3.Response
/**
* Adds password param as MD5 hash with random salt. Salt is also added as a param.
@ -32,9 +32,9 @@ class PasswordMD5Interceptor(private val password: String) : Interceptor {
override fun intercept(chain: Chain): Response {
val originalRequest = chain.request()
val updatedUrl = originalRequest.url().newBuilder()
.addQueryParameter("t", passwordMD5Hash)
.addQueryParameter("s", salt)
.build()
.addQueryParameter("t", passwordMD5Hash)
.addQueryParameter("s", salt)
.build()
return chain.proceed(originalRequest.newBuilder().url(updatedUrl).build())
}

View File

@ -20,10 +20,9 @@ internal class ProxyPasswordInterceptor(
var apiVersion: SubsonicAPIVersions = initialAPIVersions
override fun intercept(chain: Chain): Response =
if (apiVersion < SubsonicAPIVersions.V1_13_0 ||
forceHexPassword) {
hexInterceptor.intercept(chain)
} else {
mD5Interceptor.intercept(chain)
}
if (apiVersion < SubsonicAPIVersions.V1_13_0 || forceHexPassword) {
hexInterceptor.intercept(chain)
} else {
mD5Interceptor.intercept(chain)
}
}

View File

@ -1,9 +1,9 @@
package org.moire.ultrasonic.api.subsonic.interceptors
import java.util.concurrent.TimeUnit.MILLISECONDS
import okhttp3.Interceptor
import okhttp3.Interceptor.Chain
import okhttp3.Response
import java.util.concurrent.TimeUnit.MILLISECONDS
internal const val SOCKET_READ_TIMEOUT_DOWNLOAD = 30 * 1000
// Allow 20 seconds extra timeout pear MB offset.
@ -24,9 +24,11 @@ internal class RangeHeaderInterceptor : Interceptor {
val offsetValue = headers["Range"] ?: "0"
val offset = "bytes=$offsetValue-"
chain.withReadTimeout(getReadTimeout(offsetValue.toInt()), MILLISECONDS)
.proceed(originalRequest.newBuilder()
.removeHeader("Range").addHeader("Range", offset)
.build())
.proceed(
originalRequest.newBuilder()
.removeHeader("Range").addHeader("Range", offset)
.build()
)
} else {
chain.proceed(originalRequest)
}
@ -37,5 +39,5 @@ internal class RangeHeaderInterceptor : Interceptor {
// on the server. In that case, the server uses a long time before sending any data,
// causing the client to time out.
private fun getReadTimeout(offset: Int) =
(SOCKET_READ_TIMEOUT_DOWNLOAD + offset * TIMEOUT_MILLIS_PER_OFFSET_BYTE).toInt()
(SOCKET_READ_TIMEOUT_DOWNLOAD + offset * TIMEOUT_MILLIS_PER_OFFSET_BYTE).toInt()
}

View File

@ -3,11 +3,11 @@ package org.moire.ultrasonic.api.subsonic.interceptors
import com.fasterxml.jackson.core.JsonFactory
import com.fasterxml.jackson.core.JsonParseException
import com.fasterxml.jackson.core.JsonToken
import java.io.IOException
import okhttp3.Interceptor
import okhttp3.Interceptor.Chain
import okhttp3.Response
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions
import java.io.IOException
private const val DEFAULT_PEEK_BYTE_COUNT = 1000L
@ -29,12 +29,14 @@ internal class VersionInterceptor(
val originalRequest = chain.request()
val newRequest = originalRequest.newBuilder()
.url(originalRequest
.url()
.newBuilder()
.addQueryParameter("v", protocolVersion.restApiVersion)
.build())
.build()
.url(
originalRequest
.url()
.newBuilder()
.addQueryParameter("v", protocolVersion.restApiVersion)
.build()
)
.build()
val response = chain.proceed(newRequest)
if (response.isSuccessful) {
@ -54,8 +56,10 @@ internal class VersionInterceptor(
val jsonReader = jsonFactory.createParser(content)
jsonReader.nextToken()
if (jsonReader.currentToken == JsonToken.START_OBJECT) {
while (jsonReader.currentName != "version" &&
jsonReader.currentToken != null) {
while (
jsonReader.currentName != "version" &&
jsonReader.currentToken != null
) {
jsonReader.nextToken()
}
val versionStr = jsonReader.nextTextValue()

View File

@ -1,7 +1,7 @@
package org.moire.ultrasonic.api.subsonic.response
import org.moire.ultrasonic.api.subsonic.SubsonicError
import java.io.InputStream
import org.moire.ultrasonic.api.subsonic.SubsonicError
/**
* Special response that contains either [stream] of data from api, or [apiError],

View File

@ -24,14 +24,16 @@ open class SubsonicResponse(
companion object {
fun getStatusFromJson(jsonValue: String) = values()
.filter { it.jsonValue == jsonValue }.firstOrNull()
?: throw IllegalArgumentException("Unknown status value: $jsonValue")
.filter { it.jsonValue == jsonValue }.firstOrNull()
?: throw IllegalArgumentException("Unknown status value: $jsonValue")
class StatusJsonDeserializer : JsonDeserializer<Status>() {
override fun deserialize(p: JsonParser, ctxt: DeserializationContext?): Status {
if (p.currentName != "status") {
throw JsonParseException(p,
"Current token is not status. Current token name ${p.currentName}.")
throw JsonParseException(
p,
"Current token is not status. Current token name ${p.currentName}."
)
}
return getStatusFromJson(p.text)
}

View File

@ -16,8 +16,10 @@ class ProxyPasswordInterceptorTest {
private val mockPasswordMd5Interceptor = mock<PasswordMD5Interceptor>()
private val mockChain = mock<Chain>()
private val proxyInterceptor = ProxyPasswordInterceptor(V1_12_0,
mockPasswordHexInterceptor, mockPasswordMd5Interceptor, false)
private val proxyInterceptor = ProxyPasswordInterceptor(
V1_12_0,
mockPasswordHexInterceptor, mockPasswordMd5Interceptor, false
)
@Test
fun `Should use hex password on versions less then 1 13 0`() {
@ -37,8 +39,10 @@ class ProxyPasswordInterceptorTest {
@Test
fun `Should use hex password if forceHex is true`() {
val interceptor = ProxyPasswordInterceptor(V1_16_0, mockPasswordHexInterceptor,
mockPasswordMd5Interceptor, true)
val interceptor = ProxyPasswordInterceptor(
V1_16_0, mockPasswordHexInterceptor,
mockPasswordMd5Interceptor, true
)
interceptor.intercept(mockChain)

View File

@ -11,7 +11,7 @@ class StreamResponseTest {
@Test
fun `Should have error if subsonic error is not null`() {
StreamResponse(apiError = RequestedDataWasNotFound, responseHttpCode = 200)
.hasError() `should be equal to` true
.hasError() `should be equal to` true
}
@Test

View File

@ -1,18 +1,21 @@
ext.versions = [
minSdk : 14,
targetSdk : 22,
targetSdk : 23,
compileSdk : 28,
gradle : '4.6',
gradle : '6.5',
androidTools : "3.2.0",
ktlint : "0.28.0",
ktlintGradle : "5.1.0",
androidTools : "4.0.0",
ktlint : "0.37.1",
ktlintGradle : "9.2.1",
detekt : "1.0.0.RC6-4",
jacoco : "0.8.2",
jacoco : "0.8.5",
androidSupport : "22.2.1",
androidLegacySupport : "1.0.0",
androidSupportDesign : "1.0.0",
multidex : "2.0.1",
kotlin : "1.2.71",
kotlin : "1.3.72",
retrofit : "2.4.0",
jackson : "2.9.5",
@ -30,20 +33,22 @@ ext.versions = [
apacheCodecs : "1.10",
testRunner : "1.0.1",
robolectric : "3.8",
dexter : "6.1.2",
]
ext.gradlePlugins = [
androidTools : "com.android.tools.build:gradle:$versions.androidTools",
kotlin : "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin",
ktlintGradle : "gradle.plugin.org.jlleitschuh.gradle:ktlint-gradle:$versions.ktlintGradle",
ktlintGradle : "org.jlleitschuh.gradle:ktlint-gradle:$versions.ktlintGradle",
detekt : "gradle.plugin.io.gitlab.arturbosch.detekt:detekt-gradle-plugin:$versions.detekt",
jacoco : "org.jacoco:org.jacoco.core:$versions.jacoco"
]
ext.androidSupport = [
support : "com.android.support:support-v4:$versions.androidSupport",
design : "com.android.support:design:$versions.androidSupport",
annotations : "com.android.support:support-annotations:$versions.androidSupport"
support : "androidx.legacy:legacy-support-v4:$versions.androidLegacySupport",
design : "com.google.android.material:material:$versions.androidSupportDesign",
annotations: "com.android.support:support-annotations:$versions.androidSupport",
multidex : "androidx.multidex:multidex:$versions.multidex",
]
ext.other = [
@ -60,6 +65,7 @@ ext.other = [
koinJava : "org.koin:koin-java:$versions.koin",
koinAndroid : "org.koin:koin-android:$versions.koin",
picasso : "com.squareup.picasso:picasso:$versions.picasso",
dexter : "com.karumi:dexter:$versions.dexter",
]
ext.testing = [

View File

@ -8,3 +8,5 @@ kotlin.caching.enabled=true
kotlin.incremental.usePreciseJavaTracking=true
android.enableBuildCache=true
android.useAndroidX=true
android.enableJetifier=true

View File

@ -1,5 +1,6 @@
#Sat Jun 13 17:12:11 CEST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip

View File

@ -12,7 +12,7 @@ android {
compileSdkVersion versions.compileSdk
defaultConfig {
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
minSdkVersion versions.minSdk
targetSdkVersion versions.targetSdk
}

View File

@ -31,9 +31,9 @@ tasks.create(name: 'jacocoFullReport', type: JacocoReport, dependsOn: 'jacocoMer
group = "Reporting"
description = "Generate full Jacoco coverage report including all modules."
classDirectories = files()
sourceDirectories = files()
executionData = files()
getClassDirectories().setFrom(files())
getSourceDirectories().setFrom(files())
getExecutionData().setFrom(files())
reports {
xml.enabled = true

View File

@ -41,9 +41,9 @@ jacocoTestReport {
}
afterEvaluate {
classDirectories = files(classDirectories.files.collect {
getClassDirectories().setFrom(files(classDirectories.files.collect {
fileTree(dir: it, excludes: jacocoExclude)
})
}))
}
}

View File

@ -30,6 +30,7 @@ android {
}
debug {
minifyEnabled false
multiDexEnabled true
testCoverageEnabled true
applicationIdSuffix ".debug"
}
@ -64,6 +65,7 @@ dependencies {
implementation androidSupport.support
implementation androidSupport.design
implementation androidSupport.multidex
implementation other.kotlinStdlib
implementation other.koinAndroid
@ -75,6 +77,7 @@ dependencies {
testImplementation testing.kotlinJunit
testImplementation testing.mockitoKotlin
testImplementation testing.kluent
implementation other.dexter
}
jacoco {

View File

@ -1649,7 +1649,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
private void displaySongRating()
{
int rating = currentSong.getUserRating() == null ? 0 : currentSong.getUserRating();
int rating = currentSong == null || currentSong.getUserRating() == null ? 0 : currentSong.getUserRating();
fiveStar1ImageView.setImageDrawable(rating > 0 ? fullStar : hollowStar);
fiveStar2ImageView.setImageDrawable(rating > 1 ? fullStar : hollowStar);
fiveStar3ImageView.setImageDrawable(rating > 2 ? fullStar : hollowStar);

View File

@ -21,7 +21,7 @@ package org.moire.ultrasonic.activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import androidx.appcompat.app.ActionBar;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.View;

View File

@ -2,7 +2,7 @@ package org.moire.ultrasonic.activity;
import android.app.Activity;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatActivity;
import org.moire.ultrasonic.util.Constants;
import org.moire.ultrasonic.util.Util;

View File

@ -1,9 +1,9 @@
package org.moire.ultrasonic.activity;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import android.view.MenuItem;
import org.moire.ultrasonic.R;

View File

@ -19,7 +19,7 @@
package org.moire.ultrasonic.activity;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import androidx.appcompat.app.ActionBar;
import org.moire.ultrasonic.R;
import org.moire.ultrasonic.fragment.SettingsFragment;

View File

@ -30,7 +30,7 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.ActionBar;
import androidx.appcompat.app.ActionBar;
import android.util.Log;
import android.view.*;
import android.view.View.OnClickListener;

View File

@ -8,7 +8,7 @@ import android.preference.EditTextPreference;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import androidx.annotation.Nullable;
import android.util.Log;
import android.view.View;

View File

@ -6,7 +6,7 @@ import android.os.Build;
import android.os.Bundle;
import android.preference.*;
import android.provider.SearchRecentSuggestions;
import android.support.annotation.Nullable;
import androidx.annotation.Nullable;
import android.util.Log;
import android.view.View;
import org.koin.java.standalone.KoinJavaComponent;
@ -382,21 +382,18 @@ public class SettingsFragment extends PreferenceFragment
File dir = new File(path);
if (!FileUtil.ensureDirectoryExistsAndIsReadWritable(dir)) {
Util.toast(getActivity(), R.string.settings_cache_location_error, false);
// Reset it to the default.
String defaultPath = FileUtil.getDefaultMusicDirectory().getPath();
if (!defaultPath.equals(path)) {
Util.getPreferences(getActivity()).edit()
.putString(Constants.PREFERENCES_KEY_CACHE_LOCATION, defaultPath)
.apply();
cacheLocation.setSummary(defaultPath);
cacheLocation.setText(defaultPath);
}
// Clear download queue.
DownloadService downloadService = DownloadServiceImpl.getInstance();
downloadService.clear();
PermissionUtil.handlePermissionFailed(getActivity(), new PermissionUtil.PermissionRequestFinishedCallback() {
@Override
public void onPermissionRequestFinished() {
String currentPath = settings.getString(Constants.PREFERENCES_KEY_CACHE_LOCATION, FileUtil.getDefaultMusicDirectory().getPath());
cacheLocation.setSummary(currentPath);
cacheLocation.setText(currentPath);
}
});
}
// Clear download queue.
DownloadService downloadService = DownloadServiceImpl.getInstance();
downloadService.clear();
}
}

View File

@ -36,8 +36,8 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.PowerManager;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import android.util.Log;
import android.view.View;
import android.widget.RemoteViews;

View File

@ -339,7 +339,7 @@ public class OfflineMusicService extends RESTMusicService
{
try
{
Bitmap bitmap = FileUtil.getAvatarBitmap(username, size, highQuality);
Bitmap bitmap = FileUtil.getAvatarBitmap(context, username, size, highQuality);
return Util.scaleBitmap(bitmap, size);
}
catch (Exception e)

View File

@ -20,9 +20,9 @@ package org.moire.ultrasonic.service;
import android.content.Context;
import android.graphics.Bitmap;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import android.text.TextUtils;
import android.util.Log;
@ -1013,7 +1013,7 @@ public class RESTMusicService implements MusicService {
synchronized (username) {
// Use cached file, if existing.
Bitmap bitmap = FileUtil.getAvatarBitmap(username, size, highQuality);
Bitmap bitmap = FileUtil.getAvatarBitmap(context, username, size, highQuality);
if (bitmap == null) {
InputStream in = null;
@ -1031,7 +1031,7 @@ public class RESTMusicService implements MusicService {
OutputStream out = null;
try {
out = new FileOutputStream(FileUtil.getAvatarFile(username));
out = new FileOutputStream(FileUtil.getAvatarFile(context, username));
out.write(bytes);
} finally {
Util.close(out);

View File

@ -47,7 +47,7 @@ public class CacheCleaner
catch (Exception ex)
{
// If an exception is thrown, assume we execute correctly the next time
Log.w("Exception in CacheCleaner.clean", ex);
Log.w(TAG, "Exception in CacheCleaner.clean", ex);
}
}
@ -60,7 +60,7 @@ public class CacheCleaner
catch (Exception ex)
{
// If an exception is thrown, assume we execute correctly the next time
Log.w("Exception in CacheCleaner.cleanSpace", ex);
Log.w(TAG,"Exception in CacheCleaner.cleanSpace", ex);
}
}
@ -73,11 +73,11 @@ public class CacheCleaner
catch (Exception ex)
{
// If an exception is thrown, assume we execute correctly the next time
Log.w("Exception in CacheCleaner.cleanPlaylists", ex);
Log.w(TAG, "Exception in CacheCleaner.cleanPlaylists", ex);
}
}
private static void deleteEmptyDirs(Iterable<File> dirs, Collection<File> doNotDelete)
private void deleteEmptyDirs(Iterable<File> dirs, Collection<File> doNotDelete)
{
for (File dir : dirs)
{
@ -91,9 +91,9 @@ public class CacheCleaner
if (children != null)
{
// No songs left in the folder
if (children.length == 1 && children[0].getPath().equals(FileUtil.getAlbumArtFile(dir).getPath()))
if (children.length == 1 && children[0].getPath().equals(FileUtil.getAlbumArtFile(context, dir).getPath()))
{
Util.delete(FileUtil.getAlbumArtFile(dir));
Util.delete(FileUtil.getAlbumArtFile(context, dir));
children = dir.listFiles();
}

View File

@ -21,10 +21,12 @@ package org.moire.ultrasonic.util;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;
import org.moire.ultrasonic.activity.MainActivity;
import org.moire.ultrasonic.activity.SubsonicTabActivity;
import org.moire.ultrasonic.domain.MusicDirectory;
@ -106,12 +108,12 @@ public class FileUtil
public static File getAlbumArtFile(Context context, MusicDirectory.Entry entry)
{
File albumDir = getAlbumDirectory(context, entry);
return getAlbumArtFile(albumDir);
return getAlbumArtFile(context, albumDir);
}
public static File getAvatarFile(String username)
public static File getAvatarFile(Context context, String username)
{
File albumArtDir = getAlbumArtDirectory();
File albumArtDir = getAlbumArtDirectory(context);
if (albumArtDir == null || username == null)
{
@ -122,9 +124,9 @@ public class FileUtil
return new File(albumArtDir, String.format("%s.jpeg", md5Hex));
}
public static File getAlbumArtFile(File albumDir)
public static File getAlbumArtFile(Context context, File albumDir)
{
File albumArtDir = getAlbumArtDirectory();
File albumArtDir = getAlbumArtDirectory(context);
if (albumArtDir == null || albumDir == null)
{
@ -135,11 +137,11 @@ public class FileUtil
return new File(albumArtDir, String.format("%s.jpeg", md5Hex));
}
public static Bitmap getAvatarBitmap(String username, int size, boolean highQuality)
public static Bitmap getAvatarBitmap(Context context, String username, int size, boolean highQuality)
{
if (username == null) return null;
File avatarFile = getAvatarFile(username);
File avatarFile = getAvatarFile(context, username);
SubsonicTabActivity subsonicTabActivity = SubsonicTabActivity.getInstance();
Bitmap bitmap = null;
@ -299,7 +301,7 @@ public class FileUtil
return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, opt);
}
public static File getAlbumArtDirectory()
public static File getAlbumArtDirectory(Context context)
{
File albumArtDir = new File(getUltraSonicDirectory(), "artwork");
ensureDirectoryExistsAndIsReadWritable(albumArtDir);
@ -316,10 +318,13 @@ public class FileUtil
File dir;
if (!TextUtils.isEmpty(entry.getPath())) {
if (!TextUtils.isEmpty(entry.getPath()))
{
File f = new File(fileSystemSafeDir(entry.getPath()));
dir = new File(String.format("%s/%s", getMusicDirectory(context).getPath(), entry.isDirectory() ? f.getPath() : f.getParent()));
} else {
}
else
{
String artist = fileSystemSafe(entry.getArtist());
String album = fileSystemSafe(entry.getAlbum());
@ -360,7 +365,11 @@ public class FileUtil
public static File getUltraSonicDirectory()
{
return new File(Environment.getExternalStorageDirectory(), "Android/data/org.moire.ultrasonic");
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
return new File(Environment.getExternalStorageDirectory(), "Android/data/org.moire.ultrasonic");
// After Android M, the location of the files must be queried differently. GetExternalFilesDir will always return a directory which Ultrasonic can access without any extra privileges.
return MainActivity.getInstance().getExternalFilesDir(null);
}
public static File getDefaultMusicDirectory()
@ -372,7 +381,11 @@ public class FileUtil
{
String path = Util.getPreferences(context).getString(Constants.PREFERENCES_KEY_CACHE_LOCATION, DEFAULT_MUSIC_DIR.getPath());
File dir = new File(path);
return ensureDirectoryExistsAndIsReadWritable(dir) ? dir : DEFAULT_MUSIC_DIR;
boolean hasAccess = ensureDirectoryExistsAndIsReadWritable(dir);
if (hasAccess == false) PermissionUtil.handlePermissionFailed(context, null);
return hasAccess ? dir : DEFAULT_MUSIC_DIR;
}
public static boolean ensureDirectoryExistsAndIsReadWritable(File dir)
@ -406,13 +419,13 @@ public class FileUtil
if (!dir.canRead())
{
Log.w(TAG, String.format("No read permission for directory %s", dir));
return false;
return false;
}
if (!dir.canWrite())
{
Log.w(TAG, String.format("No write permission for directory %s", dir));
return false;
return false;
}
return true;

View File

@ -427,6 +427,11 @@ public class LegacyImageLoader implements Runnable, ImageLoader {
? musicService.getCoverArt(view.getContext(), entry, size, saveToFile, highQuality, null)
: musicService.getAvatar(view.getContext(), username, size, saveToFile, highQuality, null);
if (bitmap == null) {
Log.d(TAG, "Found empty album art.");
return;
}
if (isAvatar)
addImageToCache(bitmap, username, size);
else

View File

@ -0,0 +1,167 @@
package org.moire.ultrasonic.util;
import android.Manifest;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.util.Log;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.PermissionChecker;
import com.karumi.dexter.Dexter;
import com.karumi.dexter.MultiplePermissionsReport;
import com.karumi.dexter.PermissionToken;
import com.karumi.dexter.listener.DexterError;
import com.karumi.dexter.listener.PermissionRequest;
import com.karumi.dexter.listener.PermissionRequestErrorListener;
import com.karumi.dexter.listener.multi.MultiplePermissionsListener;
import org.moire.ultrasonic.R;
import org.moire.ultrasonic.activity.MainActivity;
import java.util.List;
import static androidx.core.content.PermissionChecker.PERMISSION_DENIED;
/**
* Contains static functions for Permission handling
*/
public class PermissionUtil {
private static final String TAG = FileUtil.class.getSimpleName();
public interface PermissionRequestFinishedCallback {
void onPermissionRequestFinished();
}
/**
* This function can be used to handle file access permission failures.
*
* It will check if the failure is because the necessary permissions aren't available,
* and it will request them, if necessary.
*
* @param context context for the operation
* @param callback callback function to execute after the permission request is finished
*/
public static void handlePermissionFailed(final Context context, final PermissionRequestFinishedCallback callback) {
String currentCachePath = Util.getPreferences(context).getString(Constants.PREFERENCES_KEY_CACHE_LOCATION, FileUtil.getDefaultMusicDirectory().getPath());
String defaultCachePath = FileUtil.getDefaultMusicDirectory().getPath();
// Ultrasonic can do nothing about this error when the Music Directory is already set to the default.
if (currentCachePath.compareTo(defaultCachePath) == 0) return;
// We must get the context of the Main Activity for the dialogs, as this function may be called from a background thread where displaying dialogs is not available
Context mainContext = MainActivity.getInstance();
if ((PermissionChecker.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PERMISSION_DENIED) ||
(PermissionChecker.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) == PERMISSION_DENIED)) {
// While we request permission, the Music Directory is temporarily reset to its default location
setCacheLocation(context, FileUtil.getDefaultMusicDirectory().getPath());
requestPermission(mainContext, currentCachePath, callback);
} else {
setCacheLocation(context, FileUtil.getDefaultMusicDirectory().getPath());
showWarning(mainContext, context.getString(R.string.permissions_message_box_title), context.getString(R.string.permissions_access_error), null);
callback.onPermissionRequestFinished();
}
}
private static void setCacheLocation(Context context, String cacheLocation) {
Util.getPreferences(context).edit()
.putString(Constants.PREFERENCES_KEY_CACHE_LOCATION, cacheLocation)
.apply();
}
private static void requestPermission(final Context context, final String cacheLocation, final PermissionRequestFinishedCallback callback) {
Dexter.withContext(context)
.withPermissions(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE)
.withListener(new MultiplePermissionsListener() {
@Override
public void onPermissionsChecked(MultiplePermissionsReport report) {
if (report.areAllPermissionsGranted()) {
Log.i(TAG, String.format("Permission granted to use cache directory %s", cacheLocation));
setCacheLocation(context, cacheLocation);
if (callback != null) callback.onPermissionRequestFinished();
return;
}
if (report.isAnyPermissionPermanentlyDenied()) {
Log.i(TAG, String.format("Found permanently denied permission to use cache directory %s, offering settings", cacheLocation));
showSettingsDialog(context);
if (callback != null) callback.onPermissionRequestFinished();
return;
}
Log.i(TAG, String.format("At least one permission is missing to use directory %s ", cacheLocation));
setCacheLocation(context, FileUtil.getDefaultMusicDirectory().getPath());
showWarning(context, context.getString(R.string.permissions_message_box_title),
context.getString(R.string.permissions_permission_missing), null);
if (callback != null) callback.onPermissionRequestFinished();
}
@Override
public void onPermissionRationaleShouldBeShown(List<PermissionRequest> permissions, PermissionToken token) {
showWarning(context, context.getString(R.string.permissions_rationale_title),
context.getString(R.string.permissions_rationale_description), token);
}
}).withErrorListener(new PermissionRequestErrorListener() {
@Override
public void onError(DexterError error) {
Log.e(TAG, String.format("An error has occurred during checking permissions with Dexter: %s", error.toString()));
}
})
.check();
}
private static void showSettingsDialog(final Context context) {
AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.Theme_AppCompat_Dialog);
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setTitle(context.getString(R.string.permissions_permanent_denial_title));
builder.setMessage(context.getString(R.string.permissions_permanent_denial_description));
builder.setPositiveButton(context.getString(R.string.permissions_open_settings), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
openSettings(context);
}
});
builder.setNegativeButton(context.getString(R.string.common_cancel), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
setCacheLocation(context, FileUtil.getDefaultMusicDirectory().getPath());
dialog.cancel();
}
});
builder.show();
}
private static void openSettings(Context context) {
Intent i = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
i.addCategory(Intent.CATEGORY_DEFAULT);
i.setData(Uri.parse("package:" + context.getPackageName()));
context.startActivity(i);
}
private static void showWarning(Context context, String title, String text, final PermissionToken token) {
AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.Theme_AppCompat_Dialog);
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setTitle(title);
builder.setMessage(text);
builder.setPositiveButton(context.getString(R.string.common_ok), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
if (token != null) token.continuePermissionRequest();
}
});
builder.show();
}
}

View File

@ -882,6 +882,7 @@ public class Util extends DownloadActivity
public static Bitmap scaleBitmap(Bitmap bitmap, int size)
{
if (bitmap == null) return null;
return Bitmap.createScaledBitmap(bitmap, size, getScaledHeight(bitmap, size), true);
}

View File

@ -19,8 +19,8 @@
package org.moire.ultrasonic.view;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

View File

@ -6,7 +6,9 @@ import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
public class AutoRepeatButton extends ImageView
import androidx.appcompat.widget.AppCompatImageView;
public class AutoRepeatButton extends AppCompatImageView
{
private long initialRepeatDelay = 1000;

View File

@ -19,8 +19,8 @@
package org.moire.ultrasonic.view;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

Some files were not shown because too many files have changed in this diff Show More