From 1d9ad7f2eefec3c007fb0bc29be8351a3cf4e1dd Mon Sep 17 00:00:00 2001 From: Jorge Antonio Diaz-Benito Soriano Date: Fri, 28 Jun 2024 12:51:02 +0200 Subject: [PATCH 1/2] Add PlayLog test 20 --- .../playlog/TwoMediaProductsPlayLogTest.kt | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/player/src/androidTest/kotlin/com/tidal/sdk/player/playlog/TwoMediaProductsPlayLogTest.kt b/player/src/androidTest/kotlin/com/tidal/sdk/player/playlog/TwoMediaProductsPlayLogTest.kt index 21437373..7636da5e 100644 --- a/player/src/androidTest/kotlin/com/tidal/sdk/player/playlog/TwoMediaProductsPlayLogTest.kt +++ b/player/src/androidTest/kotlin/com/tidal/sdk/player/playlog/TwoMediaProductsPlayLogTest.kt @@ -465,4 +465,65 @@ internal class TwoMediaProductsPlayLogTest { eq(emptyMap()), ) } + + @Suppress("CyclomaticComplexMethod", "LongMethod") + @Test + fun skipToNextWithRepeatOne() = runTest { + val gson = Gson() + + player.playbackEngine.load(mediaProduct1) + player.playbackEngine.setRepeatOne(true) + player.playbackEngine.play() + player.playbackEngine.setNext(mediaProduct2) + withContext(Dispatchers.Default.limitedParallelism(1)) { + withTimeout(8.seconds) { + player.playbackEngine.events.filter { it is Event.MediaProductTransition }.first() + } + delay(1.seconds) + while (player.playbackEngine.assetPosition < 1) { + delay(10.milliseconds) + } + player.playbackEngine.skipToNext() + withTimeout(8.seconds) { + player.playbackEngine.events.filter { it is Event.MediaProductTransition }.first() + } + delay(1.seconds) + while (player.playbackEngine.assetPosition < 1) { + delay(10.milliseconds) + } + player.playbackEngine.reset() + } + + eventReporterCoroutineScope.advanceUntilIdle() + verify(eventSender).sendEvent( + eq("playback_session"), + eq(ConsentCategory.NECESSARY), + argThat { + with(gson.fromJson(this, JsonObject::class.java)["payload"].asJsonObject) { + get("startAssetPosition").asDouble.isAssetPositionEqualTo(0.0) && + get("endAssetPosition").asDouble.isAssetPositionEqualTo(1.0) && + get("actualProductId")?.asString.contentEquals(mediaProduct1.productId) && + get("sourceType")?.asString.contentEquals(mediaProduct1.sourceType) && + get("sourceId")?.asString.contentEquals(mediaProduct1.sourceId) && + get("actions").asJsonArray.isEmpty + } + }, + eq(emptyMap()), + ) + verify(eventSender).sendEvent( + eq("playback_session"), + eq(ConsentCategory.NECESSARY), + argThat { + with(gson.fromJson(this, JsonObject::class.java)["payload"].asJsonObject) { + get("startAssetPosition").asDouble.isAssetPositionEqualTo(0.0) && + get("endAssetPosition").asDouble.isAssetPositionEqualTo(1.0) && + get("actualProductId")?.asString.contentEquals(mediaProduct2.productId) && + get("sourceType")?.asString.contentEquals(mediaProduct2.sourceType) && + get("sourceId")?.asString.contentEquals(mediaProduct2.sourceId) && + get("actions").asJsonArray.isEmpty + } + }, + eq(emptyMap()), + ) + } } From 5cf2923c2d990c157e7b95d3231dedcfa4a27944 Mon Sep 17 00:00:00 2001 From: Jorge Antonio Diaz-Benito Soriano Date: Mon, 1 Jul 2024 13:54:49 +0200 Subject: [PATCH 2/2] MF-171: Replace argThat with eager failures argThat is not descriptive enough for composite conditions. --- .../tidal/sdk/player/playlog/Extensions.kt | 42 ++- .../playlog/SingleMediaProductPlayLogTest.kt | 77 +++-- .../playlog/TwoMediaProductsPlayLogTest.kt | 273 +++++++++--------- 3 files changed, 208 insertions(+), 184 deletions(-) diff --git a/player/src/androidTest/kotlin/com/tidal/sdk/player/playlog/Extensions.kt b/player/src/androidTest/kotlin/com/tidal/sdk/player/playlog/Extensions.kt index cdb07e3c..22feff01 100644 --- a/player/src/androidTest/kotlin/com/tidal/sdk/player/playlog/Extensions.kt +++ b/player/src/androidTest/kotlin/com/tidal/sdk/player/playlog/Extensions.kt @@ -2,10 +2,46 @@ package com.tidal.sdk.player.playlog import assertk.Assert import assertk.assertions.isCloseTo -import kotlin.math.absoluteValue +import junit.framework.AssertionFailedError internal fun Assert.isAssetPositionEqualTo(targetPosition: Double) = isCloseTo(targetPosition, 0.5) -internal fun Double.isAssetPositionEqualTo(targetPosition: Double) = - (this - targetPosition).absoluteValue < 0.5 +@Suppress("ThrowsCount") +internal fun Iterable.combinedPassAllOf( + vararg checks: Pair Unit>, +) { + val passesLeft = checks.map { it.first }.toMutableList() + val throwableMessageMap = mutableMapOf>() + forEachIndexed { subjectIndex, subject -> + var checkIndex = -1 + while (checkIndex < checks.size) { + try { + val check = checks[++checkIndex].second + subject.check() + if (passesLeft[checkIndex] <= 0) { + throw AssertionFailedError( + "Check at index $checkIndex passed more than desired amount of times (${checks[checkIndex].first})" // ktlint-disable max-line-length + ) + } + passesLeft[checkIndex]-- + throwableMessageMap.remove(subjectIndex) + checkIndex = checks.size + } catch (throwable: Throwable) { + if (!throwableMessageMap.containsKey(subjectIndex)) { + throwableMessageMap[subjectIndex] = mutableListOf() + } + throwableMessageMap.getValue(subjectIndex).add(throwable.message!!) + } + } + } + if (throwableMessageMap.isNotEmpty()) { + throw AssertionFailedError("Failed check(s): $throwableMessageMap") + } + val index = passesLeft.indexOfFirst { it != 0 } + if (index != -1) { + throw AssertionFailedError( + "Missed check: index $index, wanted ${checks[index].first}, missed ${passesLeft[index]}" + ) + } +} diff --git a/player/src/androidTest/kotlin/com/tidal/sdk/player/playlog/SingleMediaProductPlayLogTest.kt b/player/src/androidTest/kotlin/com/tidal/sdk/player/playlog/SingleMediaProductPlayLogTest.kt index e4102ea8..b40df0d1 100644 --- a/player/src/androidTest/kotlin/com/tidal/sdk/player/playlog/SingleMediaProductPlayLogTest.kt +++ b/player/src/androidTest/kotlin/com/tidal/sdk/player/playlog/SingleMediaProductPlayLogTest.kt @@ -60,6 +60,7 @@ import org.mockito.Mockito.times import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.argThat +import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.eq import org.mockito.kotlin.verify @@ -830,6 +831,7 @@ internal class SingleMediaProductPlayLogTest { @Test fun seekBeyondBoundsWithRepeatOne() = runTest { val gson = Gson() + val payloadCaptor = argumentCaptor() player.playbackEngine.load(mediaProduct) player.playbackEngine.setRepeatOne(true) @@ -848,51 +850,48 @@ internal class SingleMediaProductPlayLogTest { } eventReporterCoroutineScope.advanceUntilIdle() - verify(eventSender).sendEvent( + verify(eventSender, times(2)).sendEvent( eq("playback_session"), eq(ConsentCategory.NECESSARY), - argThat { - with(gson.fromJson(this, JsonObject::class.java)["payload"].asJsonObject) { - get("startAssetPosition").asDouble.isAssetPositionEqualTo(0.0) && - get("endAssetPosition").asDouble - .isAssetPositionEqualTo(MEDIA_PRODUCT_DURATION_SECONDS) && - get("actualProductId")?.asString.contentEquals(mediaProduct.productId) && - get("sourceType")?.asString.contentEquals(mediaProduct.sourceType) && - get("sourceId")?.asString.contentEquals(mediaProduct.sourceId) && - get("actions").asJsonArray.run { - val stopAction = - gson.fromJson(this[0], PlaybackSession.Payload.Action::class.java) - val startAction = - gson.fromJson(this[1], PlaybackSession.Payload.Action::class.java) - val perfectResumeTimestamp = stopAction.timestamp - stopAction.actionType == - PlaybackSession.Payload.Action.Type.PLAYBACK_STOP && - stopAction.assetPositionSeconds.isAssetPositionEqualTo(2.0) && - startAction.actionType == - PlaybackSession.Payload.Action.Type.PLAYBACK_START && - startAction.assetPositionSeconds - .isAssetPositionEqualTo(MEDIA_PRODUCT_DURATION_SECONDS) && - startAction.timestamp in - (perfectResumeTimestamp - 500)..(perfectResumeTimestamp + 500) - } - } - }, + payloadCaptor.capture(), eq(emptyMap()), ) - verify(eventSender).sendEvent( - eq("playback_session"), - eq(ConsentCategory.NECESSARY), - argThat { - with(gson.fromJson(this, JsonObject::class.java)["payload"].asJsonObject) { - get("startAssetPosition").asDouble.isAssetPositionEqualTo(0.0) && - get("endAssetPosition").asDouble.isAssetPositionEqualTo(1.0) && - get("actualProductId")?.asString.contentEquals(mediaProduct.productId) && - get("sourceType")?.asString.contentEquals(mediaProduct.sourceType) && - get("sourceId")?.asString.contentEquals(mediaProduct.sourceId) && - get("actions").asJsonArray.isEmpty + payloadCaptor.allValues.map { + gson.fromJson(it, JsonObject::class.java)["payload"].asJsonObject + }.combinedPassAllOf( + 1 to { + assertThat(get("startAssetPosition").asDouble).isAssetPositionEqualTo(0.0) + assertThat(get("endAssetPosition").asDouble) + .isAssetPositionEqualTo(MEDIA_PRODUCT_DURATION_SECONDS) + assertThat(get("actualProductId")?.asString) + .isEqualTo(mediaProduct.productId) + assertThat(get("sourceType")?.asString).isEqualTo(mediaProduct.sourceType) + assertThat(get("sourceId")?.asString).isEqualTo(mediaProduct.sourceId) + with(get("actions").asJsonArray) { + val stopAction = + gson.fromJson(this[0], PlaybackSession.Payload.Action::class.java) + val startAction = + gson.fromJson(this[1], PlaybackSession.Payload.Action::class.java) + val perfectResumeTimestamp = stopAction.timestamp + assertThat(stopAction.actionType) + .isEqualTo(PlaybackSession.Payload.Action.Type.PLAYBACK_STOP) + assertThat(stopAction.assetPositionSeconds).isAssetPositionEqualTo(2.0) + assertThat(startAction.actionType) + .isEqualTo(PlaybackSession.Payload.Action.Type.PLAYBACK_START) + assertThat(startAction.assetPositionSeconds) + .isAssetPositionEqualTo(MEDIA_PRODUCT_DURATION_SECONDS) + assertThat(startAction.timestamp) + .isBetween(perfectResumeTimestamp - 500, perfectResumeTimestamp + 500) } }, - eq(emptyMap()), + 1 to { + assertThat(get("startAssetPosition").asDouble).isAssetPositionEqualTo(0.0) + assertThat(get("endAssetPosition").asDouble).isAssetPositionEqualTo(1.0) + assertThat(get("actualProductId")?.asString).isEqualTo(mediaProduct.productId) + assertThat(get("sourceType")?.asString).isEqualTo(mediaProduct.sourceType) + assertThat(get("sourceId")?.asString).isEqualTo(mediaProduct.sourceId) + assertThat(get("actions").asJsonArray).isEmpty() + }, ) } } diff --git a/player/src/androidTest/kotlin/com/tidal/sdk/player/playlog/TwoMediaProductsPlayLogTest.kt b/player/src/androidTest/kotlin/com/tidal/sdk/player/playlog/TwoMediaProductsPlayLogTest.kt index 7636da5e..ed433a5c 100644 --- a/player/src/androidTest/kotlin/com/tidal/sdk/player/playlog/TwoMediaProductsPlayLogTest.kt +++ b/player/src/androidTest/kotlin/com/tidal/sdk/player/playlog/TwoMediaProductsPlayLogTest.kt @@ -3,6 +3,7 @@ package com.tidal.sdk.player.playlog import android.app.Application import androidx.test.platform.app.InstrumentationRegistry import assertk.assertThat +import assertk.assertions.isBetween import assertk.assertions.isEmpty import assertk.assertions.isEqualTo import com.google.gson.Gson @@ -60,6 +61,7 @@ import org.mockito.Mockito.times import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.argThat +import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.eq import org.mockito.kotlin.verify @@ -195,6 +197,7 @@ internal class TwoMediaProductsPlayLogTest { @Test fun playSequentially() = runTest(timeout = 3.minutes) { val gson = Gson() + val payloadCaptor = argumentCaptor() player.playbackEngine.load(mediaProduct1) player.playbackEngine.setNext(mediaProduct2) @@ -206,47 +209,44 @@ internal class TwoMediaProductsPlayLogTest { } eventReporterCoroutineScope.advanceUntilIdle() - verify(eventSender).sendEvent( + verify(eventSender, times(2)).sendEvent( eq("playback_session"), eq(ConsentCategory.NECESSARY), - argThat { - with(gson.fromJson(this, JsonObject::class.java)["payload"].asJsonObject) { - // https://github.com/androidx/media/issues/1252 - get("startAssetPosition").asDouble.isAssetPositionEqualTo(0.0) && - // https://github.com/androidx/media/issues/1253 - get("endAssetPosition").asDouble - .isAssetPositionEqualTo(MEDIA_PRODUCT_1_DURATION_SECONDS) && - get("actualProductId")?.asString.contentEquals(mediaProduct1.productId) && - get("sourceType")?.asString.contentEquals(mediaProduct1.sourceType) && - get("sourceId")?.asString.contentEquals(mediaProduct1.sourceId) && - get("actions").asJsonArray.isEmpty - } - }, + payloadCaptor.capture(), eq(emptyMap()), ) - verify(eventSender).sendEvent( - eq("playback_session"), - eq(ConsentCategory.NECESSARY), - argThat { - with(gson.fromJson(this, JsonObject::class.java)["payload"].asJsonObject) { - // https://github.com/androidx/media/issues/1252 - get("startAssetPosition").asDouble.isAssetPositionEqualTo(0.0) && - // https://github.com/androidx/media/issues/1253 - get("endAssetPosition").asDouble - .isAssetPositionEqualTo(MEDIA_PRODUCT_2_DURATION_SECONDS) && - get("actualProductId")?.asString.contentEquals(mediaProduct2.productId) && - get("sourceType")?.asString.contentEquals(mediaProduct2.sourceType) && - get("sourceId")?.asString.contentEquals(mediaProduct2.sourceId) && - get("actions").asJsonArray.isEmpty - } + payloadCaptor.allValues.map { + gson.fromJson(it, JsonObject::class.java)["payload"].asJsonObject + }.combinedPassAllOf( + 1 to { + // https://github.com/androidx/media/issues/1252 + assertThat(get("startAssetPosition").asDouble).isAssetPositionEqualTo(0.0) + // https://github.com/androidx/media/issues/1253 + assertThat(get("endAssetPosition").asDouble) + .isAssetPositionEqualTo(MEDIA_PRODUCT_1_DURATION_SECONDS) + assertThat(get("actualProductId")?.asString).isEqualTo(mediaProduct1.productId) + assertThat(get("sourceType")?.asString).isEqualTo(mediaProduct1.sourceType) + assertThat(get("sourceId")?.asString).isEqualTo(mediaProduct1.sourceId) + assertThat(get("actions").asJsonArray).isEmpty() + }, + 1 to { + // https://github.com/androidx/media/issues/1252 + assertThat(get("startAssetPosition").asDouble).isAssetPositionEqualTo(0.0) + // https://github.com/androidx/media/issues/1253 + assertThat(get("endAssetPosition").asDouble) + .isAssetPositionEqualTo(MEDIA_PRODUCT_2_DURATION_SECONDS) + assertThat(get("actualProductId")?.asString).isEqualTo(mediaProduct2.productId) + assertThat(get("sourceType")?.asString).isEqualTo(mediaProduct2.sourceType) + assertThat(get("sourceId")?.asString).isEqualTo(mediaProduct2.sourceId) + assertThat(get("actions").asJsonArray).isEmpty() }, - eq(emptyMap()), ) } @Test fun repeatOneWithNext() = runTest { val gson = Gson() + val payloadCaptor = argumentCaptor() player.playbackEngine.load(mediaProduct1) player.playbackEngine.setNext(mediaProduct2) @@ -270,36 +270,32 @@ internal class TwoMediaProductsPlayLogTest { } eventReporterCoroutineScope.advanceUntilIdle() - verify(eventSender, times(2)).sendEvent( + verify(eventSender, times(3)).sendEvent( eq("playback_session"), eq(ConsentCategory.NECESSARY), - argThat { - with(gson.fromJson(this, JsonObject::class.java)["payload"].asJsonObject) { - get("startAssetPosition").asDouble.isAssetPositionEqualTo(0.0) && - get("endAssetPosition").asDouble - .isAssetPositionEqualTo(MEDIA_PRODUCT_1_DURATION_SECONDS) && - get("actualProductId")?.asString.contentEquals(mediaProduct1.productId) && - get("sourceType")?.asString.contentEquals(mediaProduct1.sourceType) && - get("sourceId")?.asString.contentEquals(mediaProduct1.sourceId) && - get("actions").asJsonArray.isEmpty - } - }, + payloadCaptor.capture(), eq(emptyMap()), ) - verify(eventSender).sendEvent( - eq("playback_session"), - eq(ConsentCategory.NECESSARY), - argThat { - with(gson.fromJson(this, JsonObject::class.java)["payload"].asJsonObject) { - get("startAssetPosition").asDouble.isAssetPositionEqualTo(0.0) && - get("endAssetPosition").asDouble.isAssetPositionEqualTo(1.0) && - get("actualProductId")?.asString.contentEquals(mediaProduct2.productId) && - get("sourceType")?.asString.contentEquals(mediaProduct2.sourceType) && - get("sourceId")?.asString.contentEquals(mediaProduct2.sourceId) && - get("actions").asJsonArray.isEmpty - } + payloadCaptor.allValues.map { + gson.fromJson(it, JsonObject::class.java)["payload"].asJsonObject + }.combinedPassAllOf( + 2 to { + assertThat(get("startAssetPosition").asDouble).isAssetPositionEqualTo(0.0) + assertThat(get("endAssetPosition").asDouble) + .isAssetPositionEqualTo(MEDIA_PRODUCT_1_DURATION_SECONDS) + assertThat(get("actualProductId")?.asString).isEqualTo(mediaProduct1.productId) + assertThat(get("sourceType")?.asString).isEqualTo(mediaProduct1.sourceType) + assertThat(get("sourceId")?.asString).isEqualTo(mediaProduct1.sourceId) + assertThat(get("actions").asJsonArray).isEmpty() + }, + 1 to { + assertThat(get("startAssetPosition").asDouble).isAssetPositionEqualTo(0.0) + assertThat(get("endAssetPosition").asDouble).isAssetPositionEqualTo(1.0) + assertThat(get("actualProductId")?.asString).isEqualTo(mediaProduct2.productId) + assertThat(get("sourceType")?.asString).isEqualTo(mediaProduct2.sourceType) + assertThat(get("sourceId")?.asString).isEqualTo(mediaProduct2.sourceId) + assertThat(get("actions").asJsonArray).isEmpty() }, - eq(emptyMap()), ) } @@ -307,6 +303,7 @@ internal class TwoMediaProductsPlayLogTest { @Test fun seekBeyondBoundsWithNext() = runTest { val gson = Gson() + val payloadCaptor = argumentCaptor() player.playbackEngine.load(mediaProduct1) player.playbackEngine.setNext(mediaProduct2) @@ -325,51 +322,49 @@ internal class TwoMediaProductsPlayLogTest { } eventReporterCoroutineScope.advanceUntilIdle() - verify(eventSender).sendEvent( + verify(eventSender, times(2)).sendEvent( eq("playback_session"), eq(ConsentCategory.NECESSARY), - argThat { - with(gson.fromJson(this, JsonObject::class.java)["payload"].asJsonObject) { - get("startAssetPosition").asDouble.isAssetPositionEqualTo(0.0) && - get("endAssetPosition").asDouble - .isAssetPositionEqualTo(MEDIA_PRODUCT_1_DURATION_SECONDS) && - get("actualProductId")?.asString.contentEquals(mediaProduct1.productId) && - get("sourceType")?.asString.contentEquals(mediaProduct1.sourceType) && - get("sourceId")?.asString.contentEquals(mediaProduct1.sourceId) && - get("actions").asJsonArray.run { - val stopAction = - gson.fromJson(this[0], PlaybackSession.Payload.Action::class.java) - val startAction = - gson.fromJson(this[1], PlaybackSession.Payload.Action::class.java) - val perfectResumeTimestamp = stopAction.timestamp - stopAction.actionType == - PlaybackSession.Payload.Action.Type.PLAYBACK_STOP && - stopAction.assetPositionSeconds.isAssetPositionEqualTo(2.0) && - startAction.actionType == - PlaybackSession.Payload.Action.Type.PLAYBACK_START && - startAction.assetPositionSeconds - .isAssetPositionEqualTo(MEDIA_PRODUCT_1_DURATION_SECONDS) && - startAction.timestamp in - (perfectResumeTimestamp - 500)..(perfectResumeTimestamp + 500) - } - } - }, + payloadCaptor.capture(), eq(emptyMap()), ) - verify(eventSender).sendEvent( - eq("playback_session"), - eq(ConsentCategory.NECESSARY), - argThat { - with(gson.fromJson(this, JsonObject::class.java)["payload"].asJsonObject) { - get("startAssetPosition").asDouble.isAssetPositionEqualTo(0.0) && - get("endAssetPosition").asDouble.isAssetPositionEqualTo(1.0) && - get("actualProductId")?.asString.contentEquals(mediaProduct2.productId) && - get("sourceType")?.asString.contentEquals(mediaProduct2.sourceType) && - get("sourceId")?.asString.contentEquals(mediaProduct2.sourceId) && - get("actions").asJsonArray.isEmpty + payloadCaptor.allValues.map { + gson.fromJson(it, JsonObject::class.java)["payload"].asJsonObject + }.combinedPassAllOf( + 1 to { + assertThat(get("startAssetPosition").asDouble).isAssetPositionEqualTo(0.0) + assertThat(get("endAssetPosition").asDouble) + .isAssetPositionEqualTo(MEDIA_PRODUCT_1_DURATION_SECONDS) + assertThat(get("actualProductId")?.asString).isEqualTo(mediaProduct1.productId) + assertThat(get("sourceType")?.asString).isEqualTo(mediaProduct1.sourceType) + assertThat(get("sourceId")?.asString).isEqualTo(mediaProduct1.sourceId) + with(get("actions").asJsonArray) { + val stopAction = + gson.fromJson(this[0], PlaybackSession.Payload.Action::class.java) + val startAction = + gson.fromJson(this[1], PlaybackSession.Payload.Action::class.java) + val perfectResumeTimestamp = stopAction.timestamp + assertThat(stopAction.actionType) + .isEqualTo(PlaybackSession.Payload.Action.Type.PLAYBACK_STOP) + assertThat(stopAction.assetPositionSeconds) + .isAssetPositionEqualTo(2.0) + assertThat(startAction.actionType) + .isEqualTo(PlaybackSession.Payload.Action.Type.PLAYBACK_START) + assertThat(startAction.assetPositionSeconds) + .isAssetPositionEqualTo(MEDIA_PRODUCT_1_DURATION_SECONDS) + assertThat(startAction.timestamp) + .isBetween(perfectResumeTimestamp - 500, perfectResumeTimestamp + 500) } }, - eq(emptyMap()), + 1 to { + assertThat(get("startAssetPosition").asDouble) + .isAssetPositionEqualTo(0.0) + assertThat(get("endAssetPosition").asDouble).isAssetPositionEqualTo(1.0) + assertThat(get("actualProductId")?.asString).isEqualTo(mediaProduct2.productId) + assertThat(get("sourceType")?.asString).isEqualTo(mediaProduct2.sourceType) + assertThat(get("sourceId")?.asString).isEqualTo(mediaProduct2.sourceId) + assertThat(get("actions").asJsonArray).isEmpty() + }, ) } @@ -410,6 +405,7 @@ internal class TwoMediaProductsPlayLogTest { @Test fun skipToNext() = runTest { val gson = Gson() + val payloadCaptor = argumentCaptor() player.playbackEngine.load(mediaProduct1) player.playbackEngine.play() @@ -434,35 +430,31 @@ internal class TwoMediaProductsPlayLogTest { } eventReporterCoroutineScope.advanceUntilIdle() - verify(eventSender).sendEvent( + verify(eventSender, times(2)).sendEvent( eq("playback_session"), eq(ConsentCategory.NECESSARY), - argThat { - with(gson.fromJson(this, JsonObject::class.java)["payload"].asJsonObject) { - get("startAssetPosition").asDouble.isAssetPositionEqualTo(0.0) && - get("endAssetPosition").asDouble.isAssetPositionEqualTo(1.0) && - get("actualProductId")?.asString.contentEquals(mediaProduct1.productId) && - get("sourceType")?.asString.contentEquals(mediaProduct1.sourceType) && - get("sourceId")?.asString.contentEquals(mediaProduct1.sourceId) && - get("actions").asJsonArray.isEmpty - } - }, + payloadCaptor.capture(), eq(emptyMap()), ) - verify(eventSender).sendEvent( - eq("playback_session"), - eq(ConsentCategory.NECESSARY), - argThat { - with(gson.fromJson(this, JsonObject::class.java)["payload"].asJsonObject) { - get("startAssetPosition").asDouble.isAssetPositionEqualTo(0.0) && - get("endAssetPosition").asDouble.isAssetPositionEqualTo(1.0) && - get("actualProductId")?.asString.contentEquals(mediaProduct2.productId) && - get("sourceType")?.asString.contentEquals(mediaProduct2.sourceType) && - get("sourceId")?.asString.contentEquals(mediaProduct2.sourceId) && - get("actions").asJsonArray.isEmpty - } + payloadCaptor.allValues.map { + gson.fromJson(it, JsonObject::class.java)["payload"].asJsonObject + }.combinedPassAllOf( + 1 to { + assertThat(get("startAssetPosition").asDouble).isAssetPositionEqualTo(0.0) + assertThat(get("endAssetPosition").asDouble).isAssetPositionEqualTo(1.0) + assertThat(get("actualProductId")?.asString).isEqualTo(mediaProduct1.productId) + assertThat(get("sourceType")?.asString).isEqualTo(mediaProduct1.sourceType) + assertThat(get("sourceId")?.asString).isEqualTo(mediaProduct1.sourceId) + assertThat(get("actions").asJsonArray).isEmpty() + }, + 1 to { + assertThat(get("startAssetPosition").asDouble).isAssetPositionEqualTo(0.0) + assertThat(get("endAssetPosition").asDouble).isAssetPositionEqualTo(1.0) + assertThat(get("actualProductId")?.asString).isEqualTo(mediaProduct2.productId) + assertThat(get("sourceType")?.asString).isEqualTo(mediaProduct2.sourceType) + assertThat(get("sourceId")?.asString).isEqualTo(mediaProduct2.sourceId) + assertThat(get("actions").asJsonArray).isEmpty() }, - eq(emptyMap()), ) } @@ -470,6 +462,7 @@ internal class TwoMediaProductsPlayLogTest { @Test fun skipToNextWithRepeatOne() = runTest { val gson = Gson() + val payloadCaptor = argumentCaptor() player.playbackEngine.load(mediaProduct1) player.playbackEngine.setRepeatOne(true) @@ -495,35 +488,31 @@ internal class TwoMediaProductsPlayLogTest { } eventReporterCoroutineScope.advanceUntilIdle() - verify(eventSender).sendEvent( + verify(eventSender, times(2)).sendEvent( eq("playback_session"), eq(ConsentCategory.NECESSARY), - argThat { - with(gson.fromJson(this, JsonObject::class.java)["payload"].asJsonObject) { - get("startAssetPosition").asDouble.isAssetPositionEqualTo(0.0) && - get("endAssetPosition").asDouble.isAssetPositionEqualTo(1.0) && - get("actualProductId")?.asString.contentEquals(mediaProduct1.productId) && - get("sourceType")?.asString.contentEquals(mediaProduct1.sourceType) && - get("sourceId")?.asString.contentEquals(mediaProduct1.sourceId) && - get("actions").asJsonArray.isEmpty - } - }, + payloadCaptor.capture(), eq(emptyMap()), ) - verify(eventSender).sendEvent( - eq("playback_session"), - eq(ConsentCategory.NECESSARY), - argThat { - with(gson.fromJson(this, JsonObject::class.java)["payload"].asJsonObject) { - get("startAssetPosition").asDouble.isAssetPositionEqualTo(0.0) && - get("endAssetPosition").asDouble.isAssetPositionEqualTo(1.0) && - get("actualProductId")?.asString.contentEquals(mediaProduct2.productId) && - get("sourceType")?.asString.contentEquals(mediaProduct2.sourceType) && - get("sourceId")?.asString.contentEquals(mediaProduct2.sourceId) && - get("actions").asJsonArray.isEmpty - } + payloadCaptor.allValues.map { + gson.fromJson(it, JsonObject::class.java)["payload"].asJsonObject + }.combinedPassAllOf( + 1 to { + assertThat(get("startAssetPosition").asDouble).isAssetPositionEqualTo(0.0) + assertThat(get("endAssetPosition").asDouble).isAssetPositionEqualTo(1.0) + assertThat(get("actualProductId")?.asString).isEqualTo(mediaProduct1.productId) + assertThat(get("sourceType")?.asString).isEqualTo(mediaProduct1.sourceType) + assertThat(get("sourceId")?.asString).isEqualTo(mediaProduct1.sourceId) + assertThat(get("actions").asJsonArray).isEmpty() + }, + 1 to { + assertThat(get("startAssetPosition").asDouble).isAssetPositionEqualTo(0.0) + assertThat(get("endAssetPosition").asDouble).isAssetPositionEqualTo(1.0) + assertThat(get("actualProductId")?.asString).isEqualTo(mediaProduct2.productId) + assertThat(get("sourceType")?.asString).isEqualTo(mediaProduct2.sourceType) + assertThat(get("sourceId")?.asString).isEqualTo(mediaProduct2.sourceId) + assertThat(get("actions").asJsonArray).isEmpty() }, - eq(emptyMap()), ) } }