Version + more tests

This commit is contained in:
Brage Skjønborg 2026-01-03 08:55:45 +01:00
parent 6bc2ade681
commit 295349e78b
17 changed files with 806 additions and 47 deletions

View File

@ -39,7 +39,7 @@ dependencies {
implementation("no.iktdev:exfl:0.0.16-SNAPSHOT")
implementation("no.iktdev.library:subtitle:1.8.1-SNAPSHOT")
implementation("no.iktdev:eventi:1.0-rc15")
implementation("no.iktdev:eventi:1.0-rc17")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")
@ -53,6 +53,7 @@ dependencies {
testImplementation("io.mockk:mockk:1.12.0")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation(project(":shared:common", configuration = "testArtifacts"))
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2")
}

View File

@ -0,0 +1,10 @@
package no.iktdev.mediaprocessing.converter
import no.iktdev.library.subtitle.reader.BaseReader
import java.io.File
interface ConverterEnvironment {
fun canRead(file: File): Boolean
fun getReader(file: File): BaseReader?
fun createExporter(input: File, outputDir: File, name: String): Exporter
}

View File

@ -0,0 +1,14 @@
package no.iktdev.mediaprocessing.converter
import no.iktdev.library.subtitle.classes.Dialog
import no.iktdev.library.subtitle.export.Export
class ExportAdapter(
private val export: Export
) : Exporter {
override fun write(dialogs: List<Dialog>) = export.write(dialogs)
override fun writeSrt(dialogs: List<Dialog>) = export.writeSrt(dialogs)
override fun writeSmi(dialogs: List<Dialog>) = export.writeSmi(dialogs)
override fun writeVtt(dialogs: List<Dialog>) = export.writeVtt(dialogs)
}

View File

@ -0,0 +1,11 @@
package no.iktdev.mediaprocessing.converter
import no.iktdev.library.subtitle.classes.Dialog
import java.io.File
interface Exporter {
fun write(dialogs: List<Dialog>): MutableList<File>
fun writeSrt(dialogs: List<Dialog>): File
fun writeSmi(dialogs: List<Dialog>): File
fun writeVtt(dialogs: List<Dialog>): File
}

View File

@ -0,0 +1,18 @@
package no.iktdev.mediaprocessing.converter.convert
import no.iktdev.library.subtitle.reader.BaseReader
import no.iktdev.mediaprocessing.converter.ConverterEnvironment
import no.iktdev.mediaprocessing.shared.common.event_task_contract.tasks.ConvertTask
import java.io.File
abstract class Converter(val env: ConverterEnvironment, val listener: ConvertListener) {
var writtenUris: List<String>? = null
abstract fun getSubtitleReader(useFile: File): BaseReader?
abstract suspend fun convert(data: ConvertTask.Data)
class FileIsNullOrEmpty(override val message: String? = "File read is null or empty"): RuntimeException()
class FileUnavailableException(override val message: String): RuntimeException()
abstract fun getResult(): List<String>
}

View File

@ -4,51 +4,43 @@ import no.iktdev.library.subtitle.Configuration
import no.iktdev.library.subtitle.Syncro
import no.iktdev.library.subtitle.classes.Dialog
import no.iktdev.library.subtitle.classes.DialogType
import no.iktdev.library.subtitle.export.Export
import no.iktdev.library.subtitle.reader.BaseReader
import no.iktdev.library.subtitle.reader.Reader
import no.iktdev.mediaprocessing.converter.ConverterEnv
import no.iktdev.mediaprocessing.converter.ConverterEnvironment
import no.iktdev.mediaprocessing.shared.common.event_task_contract.tasks.ConvertTask
import no.iktdev.mediaprocessing.shared.common.model.SubtitleFormat
import java.io.File
class Converter2(val data: ConvertTask.Data,
private val listener: ConvertListener) {
class Converter2(
env: ConverterEnvironment,
listener: ConvertListener
): Converter(env = env, listener = listener) {
@Throws(FileUnavailableException::class)
private fun getReader(): BaseReader? {
val file = File(data.inputFile)
if (!file.canRead())
override fun getSubtitleReader(useFile: File): BaseReader? {
if (!env.canRead(useFile)) {
throw FileUnavailableException("Can't open file for reading..")
return Reader(file).getSubtitleReader()
}
return env.getReader(useFile)
}
private fun syncDialogs(input: List<Dialog>): List<Dialog> {
return if (ConverterEnv.syncDialogs) Syncro().sync(input) else input
}
fun canRead(): Boolean {
try {
val reader = getReader()
return reader != null
} catch (e: FileUnavailableException) {
return false
}
}
@Throws(FileUnavailableException::class, FileIsNullOrEmpty::class)
fun execute() {
override suspend fun convert(data: ConvertTask.Data) {
val file = File(data.inputFile)
listener.onStarted(file.absolutePath)
try {
Configuration.exportJson = true
val read = getReader()?.read() ?: throw FileIsNullOrEmpty()
val read = getSubtitleReader(file)?.read() ?: throw FileIsNullOrEmpty()
if (read.isEmpty())
throw FileIsNullOrEmpty()
val filtered = read.filter { !it.ignore && it.type !in listOf(DialogType.SIGN_SONG, DialogType.CAPTION) }
val syncOrNotSync = syncDialogs(filtered)
val exporter = Export(file, File(data.outputDirectory), data.outputFileName)
val exporter = env.createExporter(file, File(data.outputDirectory), data.outputFileName)
val outFiles = if (data.formats.isEmpty()) {
exporter.write(syncOrNotSync)
@ -65,22 +57,17 @@ class Converter2(val data: ConvertTask.Data,
}
exported
}
result = outFiles.map { it.absolutePath }
listener.onCompleted(file.absolutePath, result!!)
writtenUris = outFiles.map { it.absolutePath }
listener.onCompleted(file.absolutePath, writtenUris!!)
} catch (e: Exception) {
listener.onError(file.absolutePath, e.message ?: e.localizedMessage)
}
}
private var result: List<String>? = null
fun getResult(): List<String> {
if (result == null) {
override fun getResult(): List<String> {
if (writtenUris == null) {
throw IllegalStateException("Execute must be called before getting result")
}
return result!!
return writtenUris!!
}
class FileIsNullOrEmpty(override val message: String? = "File read is null or empty"): RuntimeException()
class FileUnavailableException(override val message: String): RuntimeException()
}

View File

@ -1,19 +1,29 @@
package no.iktdev.mediaprocessing.converter.listeners
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import no.iktdev.eventi.models.Event
import no.iktdev.eventi.models.Task
import no.iktdev.eventi.models.store.TaskStatus
import no.iktdev.eventi.tasks.TaskListener
import no.iktdev.eventi.tasks.TaskType
import no.iktdev.library.subtitle.export.Export
import no.iktdev.library.subtitle.reader.BaseReader
import no.iktdev.library.subtitle.reader.Reader
import no.iktdev.mediaprocessing.converter.ConverterEnvironment
import no.iktdev.mediaprocessing.converter.ExportAdapter
import no.iktdev.mediaprocessing.converter.Exporter
import no.iktdev.mediaprocessing.converter.convert.ConvertListener
import no.iktdev.mediaprocessing.converter.convert.Converter
import no.iktdev.mediaprocessing.converter.convert.Converter2
import no.iktdev.mediaprocessing.shared.common.event_task_contract.events.ConvertTaskResultEvent
import no.iktdev.mediaprocessing.shared.common.event_task_contract.tasks.ConvertTask
import org.springframework.stereotype.Component
import java.io.File
import java.util.*
@Component
class ConvertTaskListener: TaskListener(TaskType.CPU_INTENSIVE) {
open class ConvertTaskListener(): TaskListener(TaskType.CPU_INTENSIVE) {
override fun getWorkerId(): String {
return "${this::class.java.simpleName}-${TaskType.CPU_INTENSIVE}-${UUID.randomUUID()}"
@ -27,18 +37,15 @@ class ConvertTaskListener: TaskListener(TaskType.CPU_INTENSIVE) {
if (task !is ConvertTask) {
throw IllegalArgumentException("Invalid task type: ${task::class.java.name}")
}
val converter = Converter2(task.data, object: ConvertListener {
override fun onStarted(inputFile: String) {
}
override fun onCompleted(inputFile: String, outputFiles: List<String>) {
}
})
val converter = getConverter()
withHeartbeatRunner {
reporter?.updateLastSeen(task.taskId)
}
converter.execute()
withContext(Dispatchers.Unconfined) {
converter.convert(task.data)
}
return try {
val result = converter.getResult()
@ -59,9 +66,38 @@ class ConvertTaskListener: TaskListener(TaskType.CPU_INTENSIVE) {
).producedFrom(task)
newEvent
}
}
open fun getConverter(): Converter {
return Converter2(getConverterEnvironment(), getListener())
}
open fun getListener(): ConvertListener {
return DefaultConvertListener()
}
class DefaultConvertListener: ConvertListener {
override fun onStarted(inputFile: String) {
}
override fun onCompleted(inputFile: String, outputFiles: List<String>) {
}
}
open fun getConverterEnvironment(): ConverterEnvironment {
return DefaultConverterEnvironment()
}
class DefaultConverterEnvironment : ConverterEnvironment {
override fun canRead(file: File) = file.canRead()
override fun getReader(file: File): BaseReader? =
Reader(file).getSubtitleReader()
override fun createExporter(input: File, outputDir: File, name: String): Exporter {
return ExportAdapter(Export(input, outputDir, name))
}
}
}

View File

@ -0,0 +1,38 @@
package no.iktdev.mediaprocessing.converter
import kotlinx.coroutines.delay
import no.iktdev.eventi.models.store.TaskStatus
import no.iktdev.library.subtitle.reader.BaseReader
import no.iktdev.mediaprocessing.converter.convert.ConvertListener
import no.iktdev.mediaprocessing.converter.convert.Converter
import no.iktdev.mediaprocessing.shared.common.event_task_contract.tasks.ConvertTask
import java.io.File
class MockConverter(
val delayMillis: Long = 0,
private val simulatedResult: List<String>? = null,
private val taskResultStatus: TaskStatus = TaskStatus.Completed,
private val throwException: Boolean = false,
val mockEnv: ConverterEnvironment = MockConverterEnvironment(),
listener: ConvertListener
) : Converter(env = mockEnv, listener = listener) {
override fun getSubtitleReader(useFile: File): BaseReader? {
TODO("Not yet implemented")
}
override suspend fun convert(data: ConvertTask.Data) {
if (delayMillis > 0) delay(delayMillis)
if (throwException) throw RuntimeException("Simulated convert failure")
if (taskResultStatus == TaskStatus.Failed) {
listener.onError(data.inputFile, "Failed state desired")
} else {
listener.onCompleted(data.inputFile, simulatedResult!!)
}
}
override fun getResult(): List<String> {
return simulatedResult!!
}
}

View File

@ -0,0 +1,19 @@
package no.iktdev.mediaprocessing.converter
import no.iktdev.library.subtitle.reader.BaseReader
import java.io.File
class MockConverterEnvironment(
var canReadValue: Boolean = true,
var reader: BaseReader? = null,
var exporter: Exporter? = null
) : ConverterEnvironment {
override fun canRead(file: File): Boolean = canReadValue
override fun getReader(file: File): BaseReader? = reader
override fun createExporter(input: File, outputDir: File, name: String): Exporter {
return exporter ?: error("FakeEnv.exporter must be set before calling createExporter")
}
}

View File

@ -0,0 +1,249 @@
package no.iktdev.mediaprocessing.converter.convert
import kotlinx.coroutines.test.runTest
import no.iktdev.library.subtitle.classes.Dialog
import no.iktdev.library.subtitle.classes.DialogType
import no.iktdev.library.subtitle.classes.Time
import no.iktdev.library.subtitle.reader.BaseReader
import no.iktdev.mediaprocessing.converter.ConverterEnvironment
import no.iktdev.mediaprocessing.converter.Exporter
import no.iktdev.mediaprocessing.shared.common.event_task_contract.tasks.ConvertTask
import no.iktdev.mediaprocessing.shared.common.model.SubtitleFormat
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import java.io.File
class Converter2Test {
// -------------------------------------------------------------------------
// Fake implementations
// -------------------------------------------------------------------------
class FakeReader(
private val dialogs: List<Dialog>,
private val shouldThrow: Boolean = false
) : BaseReader() {
override fun read(): List<Dialog> {
if (shouldThrow) throw RuntimeException("reader failed")
return dialogs
}
}
class FakeExporter(
private val srtFile: File? = null,
private val smiFile: File? = null,
private val vttFile: File? = null,
private val filesForWrite: List<File> = emptyList(),
private val shouldThrow: Boolean = false
) : Exporter {
override fun write(dialogs: List<Dialog>): MutableList<File> {
if (shouldThrow) throw RuntimeException("export failed")
return filesForWrite.toMutableList()
}
override fun writeSrt(dialogs: List<Dialog>): File =
srtFile ?: error("srtFile not set in FakeExporter")
override fun writeSmi(dialogs: List<Dialog>): File =
smiFile ?: error("smiFile not set in FakeExporter")
override fun writeVtt(dialogs: List<Dialog>): File =
vttFile ?: error("vttFile not set in FakeExporter")
}
class FakeListener : ConvertListener {
var started = 0
var completed = 0
var errors = 0
var lastError: String? = null
override fun onStarted(inputFile: String) {
started++
}
override fun onCompleted(inputFile: String, outputFiles: List<String>) {
completed++
}
override fun onError(inputFile: String, message: String) {
errors++
lastError = message
}
}
class FakeEnv(
var canReadValue: Boolean = true,
var reader: BaseReader? = null,
var exporter: Exporter? = null
) : ConverterEnvironment {
override fun canRead(file: File): Boolean = canReadValue
override fun getReader(file: File): BaseReader? = reader
override fun createExporter(input: File, outputDir: File, name: String): Exporter {
return exporter ?: error("FakeEnv.exporter must be set before calling createExporter")
}
}
// -------------------------------------------------------------------------
// Helpers
// -------------------------------------------------------------------------
private fun makeTaskData(
input: String = "input.srt",
language: String = "no",
outDir: String = "out",
outName: String = "name",
formats: List<SubtitleFormat> = emptyList(),
allowOverwrite: Boolean = true
) = ConvertTask.Data(
inputFile = input,
language = language,
outputDirectory = outDir,
outputFileName = outName,
formats = formats,
allowOverwrite = allowOverwrite
)
// -------------------------------------------------------------------------
// Tests
// -------------------------------------------------------------------------
@Test
@DisplayName(
"""
Når execute() kjøres
Hvis reader returnerer tom liste
kalles onError
"""
)
fun execute_emptyFile() = runTest {
val env = FakeEnv(
canReadValue = true,
reader = FakeReader(emptyList())
)
val listener = FakeListener()
val converter = Converter2(
env = env,
listener = listener
)
converter.convert(makeTaskData())
assertEquals(1, listener.errors)
assertEquals(0, listener.completed)
}
@Test
@DisplayName(
"""
Når execute() kjøres
Hvis reader returnerer dialoger
Og exporter.write lykkes
kalles onCompleted
"""
)
fun execute_success() = runTest {
val dialogs = listOf(
Dialog("0", Time(0, 0, 0, 0), Time(0, 0, 1000, 0), "Hello", DialogType.DIALOG_NORMAL)
)
val env = FakeEnv(
canReadValue = true,
reader = FakeReader(dialogs),
exporter = FakeExporter(srtFile = File("/fake/out.srt"))
)
val listener = FakeListener()
val converter = Converter2(
env = env,
listener = listener
)
converter.convert(
makeTaskData(
formats = listOf(SubtitleFormat.SRT)
)
)
assertEquals(1, listener.completed)
assertEquals(listOf("/fake/out.srt"), converter.getResult())
}
@Test
@DisplayName(
"""
Når execute() kjøres
Hvis exporter.write kaster exception
kalles onError
"""
)
fun execute_exportFails() = runTest {
val dialogs = listOf(
Dialog("0", Time(0, 0, 0, 0), Time(0, 0, 1000, 0), "Hello", DialogType.DIALOG_NORMAL)
)
val env = FakeEnv(
canReadValue = true,
reader = FakeReader(dialogs),
exporter = FakeExporter(srtFile = File("/fake/out.srt"), shouldThrow = true)
)
val listener = FakeListener()
val converter = Converter2(
env = env,
listener = listener
)
converter.convert(makeTaskData())
assertEquals(1, listener.errors)
assertEquals(0, listener.completed)
}
@Test
@DisplayName(
"""
Når execute() kjøres
Hvis formats inneholder SRT og VTT
brukes writeSrt og writeVtt
"""
)
fun execute_multipleFormats() = runTest {
val dialogs = listOf(
Dialog("0", Time(0, 0, 0, 0), Time(0, 0, 1000, 0), "Hello", DialogType.DIALOG_NORMAL)
)
val env = FakeEnv(
canReadValue = true,
reader = FakeReader(dialogs),
exporter = FakeExporter(srtFile = File("/fake/out.srt"), vttFile = File("/fake/out.vtt"))
)
val listener = FakeListener()
val converter = Converter2(
env = env,
listener = listener
)
converter.convert(
makeTaskData(
formats = listOf(SubtitleFormat.SRT, SubtitleFormat.VTT)
)
)
assertEquals(1, listener.completed)
assertEquals(listOf("/fake/out.srt", "/fake/out.vtt"), converter.getResult())
}
}

View File

@ -1,7 +1,245 @@
package no.iktdev.mediaprocessing.converter.listeners
import kotlinx.coroutines.test.runTest
import no.iktdev.eventi.models.Event
import no.iktdev.eventi.models.Task
import no.iktdev.eventi.models.store.TaskStatus
import no.iktdev.eventi.tasks.TaskReporter
import no.iktdev.library.subtitle.classes.Dialog
import no.iktdev.library.subtitle.classes.DialogType
import no.iktdev.library.subtitle.classes.Time
import no.iktdev.library.subtitle.reader.BaseReader
import no.iktdev.mediaprocessing.converter.ConverterEnvironment
import no.iktdev.mediaprocessing.converter.Exporter
import no.iktdev.mediaprocessing.converter.MockConverter
import no.iktdev.mediaprocessing.converter.MockConverterEnvironment
import no.iktdev.mediaprocessing.converter.convert.ConvertListener
import no.iktdev.mediaprocessing.converter.convert.Converter
import no.iktdev.mediaprocessing.shared.common.event_task_contract.events.ConvertTaskResultEvent
import no.iktdev.mediaprocessing.shared.common.event_task_contract.tasks.ConvertTask
import no.iktdev.mediaprocessing.shared.common.model.SubtitleFormat
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import java.io.File
import java.util.*
import kotlin.system.measureTimeMillis
class ConvertTaskListenerTest {
class ConvertTaskListenerTestImplementation : ConvertTaskListener() {
fun getJob() = currentJob
var overrideEnv: ConverterEnvironment = DefaultConverterEnvironment()
override fun getConverterEnvironment(): ConverterEnvironment {
return overrideEnv
}
var overrideListener: ConvertListener? = null
override fun getListener(): ConvertListener {
if (overrideListener != null)
return overrideListener!!
else
return super.getListener()
}
var overrideConverter: Converter? = null
override fun getConverter(): Converter {
return if (overrideConverter != null)
overrideConverter!!
else
super.getConverter()
}
}
val listener = ConvertTaskListenerTestImplementation()
// ---------------------------------------------------------------------
// Fake environment + fake converter
// ---------------------------------------------------------------------
class FakeListener : ConvertListener {
var started = 0
var completed = 0
var errors = 0
override fun onStarted(inputFile: String) {
started++
}
override fun onCompleted(inputFile: String, outputFiles: List<String>) {
completed++
}
override fun onError(inputFile: String, message: String) {
errors++
}
}
class FakeExporter(
private val files: List<File>,
private val shouldThrow: Boolean = false
) : Exporter {
override fun write(dialogs: List<Dialog>): MutableList<File> {
if (shouldThrow) throw RuntimeException("export failed")
return files.toMutableList()
}
override fun writeSrt(dialogs: List<Dialog>) = files[0]
override fun writeSmi(dialogs: List<Dialog>) = files[0]
override fun writeVtt(dialogs: List<Dialog>) = files[0]
}
class FakeReader(
private val dialogs: List<Dialog>,
private val shouldThrow: Boolean = false
) : BaseReader() {
override fun read(): List<Dialog> {
if (shouldThrow) throw RuntimeException("reader failed")
return dialogs
}
}
val overrideReporter = object : TaskReporter {
override fun markClaimed(taskId: UUID, workerId: String) {}
override fun updateLastSeen(taskId: UUID) {}
override fun markConsumed(taskId: UUID) {}
override fun updateProgress(taskId: UUID, progress: Int) {}
override fun log(taskId: UUID, message: String) {}
override fun publishEvent(event: Event) {
}
}
// ---------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------
private fun makeTask(
formats: List<SubtitleFormat> = emptyList()
): ConvertTask {
return ConvertTask(
ConvertTask.Data(
inputFile = "input.srt",
language = "no",
outputDirectory = "out",
outputFileName = "name",
formats = formats,
allowOverwrite = true
)
).apply { newReferenceId() }
}
// ---------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------
@Test
@DisplayName("""
Når onTask kjøres og converterer
Hvis den bruker lengre tid
:
Skal koden vente til den er ferdig
""")
fun onTask_validate_delay() = runTest {
val delay = 1000L
val converter = MockConverter(
delay,
listOf("file:///potato.srt"),
listener = FakeListener()
)
listener.apply {
overrideConverter = converter
}
val task = makeTask()
val event = listener.onTask(task)
val time = measureTimeMillis {
val accepted = listener.accept(task, overrideReporter)
assertTrue(accepted, "Task listener did not accept the task.")
listener.getJob()?.join()
assertTrue(event is ConvertTaskResultEvent)
assertEquals(TaskStatus.Completed, (event as ConvertTaskResultEvent).status)
}
}
@Test
@DisplayName(
"""
Når onTask kjøres
Hvis converter lykkes
returneres Completed-event
"""
)
fun onTask_success() = runTest {
val dialogs = listOf(
Dialog("0", Time(0, 0, 0, 0), Time(0, 0, 1000, 0), "Hello", DialogType.DIALOG_NORMAL)
)
val env = MockConverterEnvironment(
canReadValue = true,
reader = FakeReader(dialogs),
exporter = FakeExporter(listOf(File("/fake/out.srt")))
)
listener.apply {
overrideListener = FakeListener()
overrideEnv = env
}
val task = makeTask()
val event = listener.onTask(task) as ConvertTaskResultEvent
assertEquals(TaskStatus.Completed, event.status)
assertEquals(listOf("/fake/out.srt"), event.data!!.outputFiles)
}
@Test
@DisplayName(
"""
Når onTask kjøres
Hvis converter feiler
returneres Failed-event
"""
)
fun onTask_failure() = runTest {
val env = MockConverterEnvironment(
canReadValue = true,
reader = FakeReader(emptyList()) // triggers FileIsNullOrEmpty
)
listener.apply {
overrideListener = FakeListener()
overrideEnv = env
}
val task = makeTask()
val event = listener.onTask(task) as ConvertTaskResultEvent
assertEquals(TaskStatus.Failed, event.status)
assertNull(event.data)
}
@Test
@DisplayName(
"""
Når supports() kalles
returnerer den true for ConvertTask og false for andre typer
"""
)
fun supports_test() {
val listener = ConvertTaskListener()
assertTrue(listener.supports(makeTask()))
assertFalse(listener.supports(DummyTask()))
}
class DummyTask : Task()
}

View File

@ -39,7 +39,7 @@ dependencies {
implementation("no.iktdev:exfl:0.0.16-SNAPSHOT")
implementation("no.iktdev.streamit.library:streamit-library-db:1.0.0-alpha14")
implementation("no.iktdev:eventi:1.0-rc16")
implementation("no.iktdev:eventi:1.0-rc17")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")

View File

@ -9,6 +9,7 @@ import no.iktdev.mediaprocessing.coordinator.util.FileSystemService
import no.iktdev.mediaprocessing.shared.common.event_task_contract.events.MigrateContentToStoreTaskResultEvent
import no.iktdev.mediaprocessing.shared.common.event_task_contract.tasks.MigrateToContentStoreTask
import no.iktdev.mediaprocessing.shared.common.model.MigrateStatus
import no.iktdev.mediaprocessing.shared.common.silentTry
import org.jetbrains.annotations.VisibleForTesting
import org.springframework.stereotype.Component
import java.io.File
@ -40,13 +41,13 @@ class MigrateContentToStoreTaskListener: TaskListener(TaskType.IO_INTENSIVE) {
coverStatus.none { it.status == MigrateStatus.Failed })
{
pickedTask.data.videoContent?.cachedUri?.let { File(it) }?.let {
fs.delete(it)
silentTry { fs.delete(it) }
}
pickedTask.data.subtitleContent?.map { File(it.cachedUri) }?.forEach {
fs.delete(it)
silentTry { fs.delete(it) }
}
pickedTask.data.coverContent?.map { File(it.cachedUri) }?.forEach {
fs.delete(it)
silentTry { fs.delete(it) }
}
} else {
status = TaskStatus.Failed

View File

@ -5,6 +5,8 @@ import java.io.File
class MockFileSystemService : FileSystemService {
var copyShouldFail = false
var deleteShouldFail = false
var identical = true
val copied = mutableListOf<Pair<File, File>>()
val deleted = mutableListOf<File>()
@ -19,6 +21,7 @@ class MockFileSystemService : FileSystemService {
}
override fun delete(file: File) {
if (deleteShouldFail) throw RuntimeException("delete failed")
deleted += file
}
}

View File

@ -102,6 +102,25 @@ class MigrateContentToStoreTaskListenerTest {
assertEquals(MigrateStatus.Failed, result.status)
}
@Test
@DisplayName(
"""
Når migrateVideo kjøres
Hvis content er null
:
returneres NotPresent
"""
)
fun migrateVideo_null() {
val fs = MockFileSystemService().also {
listener.fs = it
}
val result = listener.migrateVideo(fs, null)
assertEquals(MigrateStatus.NotPresent, result.status)
}
// -------------------------------------------------------------------------
// migrateSubtitle
// -------------------------------------------------------------------------
@ -151,6 +170,36 @@ class MigrateContentToStoreTaskListenerTest {
assertEquals(MigrateStatus.Completed, result.first().status)
}
@Test
@DisplayName(
"""
Når migrateSubtitle kjøres
Hvis én lykkes og én feiler
:
returneres både Completed og Failed
"""
)
fun migrateSubtitle_mixed() {
val fs = MockFileSystemService().apply {
// first OK, second fails
copyShouldFail = false
}.also { listener.fs = it }
val subs = listOf(
MigrateToContentStoreTask.Data.SingleSubtitle("en", "/tmp/a", "/tmp/b"),
MigrateToContentStoreTask.Data.SingleSubtitle("no", "/tmp/c", "/tmp/d")
)
// simulate second failing
fs.copyShouldFail = false
val result1 = listener.migrateSubtitle(fs, listOf(subs[0]))
fs.copyShouldFail = true
val result2 = listener.migrateSubtitle(fs, listOf(subs[1]))
assertEquals(MigrateStatus.Completed, result1.first().status)
assertEquals(MigrateStatus.Failed, result2.first().status)
}
// -------------------------------------------------------------------------
// migrateCover
// -------------------------------------------------------------------------
@ -179,6 +228,34 @@ class MigrateContentToStoreTaskListenerTest {
assertEquals(MigrateStatus.Completed, result.first().status)
}
@Test
@DisplayName(
"""
Når migrateCover kjøres
Hvis flere covers og én feiler
:
returneres både Completed og Failed
"""
)
fun migrateCover_mixed() {
val fs = MockFileSystemService().also { listener.fs = it }
val covers = listOf(
MigrateToContentStoreTask.Data.SingleContent("/tmp/a", "/tmp/b"),
MigrateToContentStoreTask.Data.SingleContent("/tmp/c", "/tmp/d")
)
// first OK, second mismatch
fs.identical = true
val ok = listener.migrateCover(fs, listOf(covers[0]))
fs.identical = false
val fail = listener.migrateCover(fs, listOf(covers[1]))
assertEquals(MigrateStatus.Completed, ok.first().status)
assertEquals(MigrateStatus.Failed, fail.first().status)
}
// -------------------------------------------------------------------------
// onTask
// -------------------------------------------------------------------------
@ -244,4 +321,61 @@ class MigrateContentToStoreTaskListenerTest {
assertEquals(TaskStatus.Failed, event.status)
assertEquals(0, fs.deleted.size)
}
@Test
@DisplayName(
"""
Når onTask kjøres
Hvis video feiler
:
returneres Failed og ingenting slettes
"""
)
fun onTask_videoFails() = runTest {
val fs = MockFileSystemService().apply { copyShouldFail = true }
.also { listener.fs = it }
val task = MigrateToContentStoreTask(
MigrateToContentStoreTask.Data(
collection = "col",
videoContent = MigrateToContentStoreTask.Data.SingleContent("/tmp/v", "/tmp/v2"),
subtitleContent = emptyList(),
coverContent = emptyList()
)
).newReferenceId()
val event = listener.onTask(task) as MigrateContentToStoreTaskResultEvent
assertEquals(TaskStatus.Failed, event.status)
assertEquals(0, fs.deleted.size)
}
@Test
@DisplayName(
"""
Når onTask kjøres
Hvis sletting feiler
:
returneres fortsatt Completed
"""
)
fun onTask_deleteFails() = runTest {
val fs = MockFileSystemService().apply { deleteShouldFail = true }
.also { listener.fs = it }
val task = MigrateToContentStoreTask(
MigrateToContentStoreTask.Data(
collection = "col",
videoContent = MigrateToContentStoreTask.Data.SingleContent("/tmp/v", "/tmp/v2"),
subtitleContent = emptyList(),
coverContent = emptyList()
)
).newReferenceId()
val event = listener.onTask(task) as MigrateContentToStoreTaskResultEvent
assertEquals(TaskStatus.Completed, event.status)
}
}

View File

@ -35,7 +35,7 @@ dependencies {
implementation("org.json:json:20210307")
implementation("no.iktdev:exfl:0.0.16-SNAPSHOT")
implementation("no.iktdev:eventi:1.0-rc15")
implementation("no.iktdev:eventi:1.0-rc17")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")

View File

@ -61,7 +61,7 @@ dependencies {
implementation("com.zaxxer:HikariCP:7.0.2")
implementation(project(":shared:ffmpeg"))
implementation("no.iktdev:eventi:1.0-rc16")
implementation("no.iktdev:eventi:1.0-rc17")
testImplementation(kotlin("test"))
testImplementation(platform("org.junit:junit-bom:5.10.0"))