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:exfl:0.0.16-SNAPSHOT")
implementation("no.iktdev.library:subtitle:1.8.1-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") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")
@ -53,6 +53,7 @@ dependencies {
testImplementation("io.mockk:mockk:1.12.0") testImplementation("io.mockk:mockk:1.12.0")
testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation(project(":shared:common", configuration = "testArtifacts")) 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.Syncro
import no.iktdev.library.subtitle.classes.Dialog import no.iktdev.library.subtitle.classes.Dialog
import no.iktdev.library.subtitle.classes.DialogType 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.BaseReader
import no.iktdev.library.subtitle.reader.Reader
import no.iktdev.mediaprocessing.converter.ConverterEnv 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.event_task_contract.tasks.ConvertTask
import no.iktdev.mediaprocessing.shared.common.model.SubtitleFormat import no.iktdev.mediaprocessing.shared.common.model.SubtitleFormat
import java.io.File import java.io.File
class Converter2(val data: ConvertTask.Data, class Converter2(
private val listener: ConvertListener) { env: ConverterEnvironment,
listener: ConvertListener
): Converter(env = env, listener = listener) {
@Throws(FileUnavailableException::class) @Throws(FileUnavailableException::class)
private fun getReader(): BaseReader? { override fun getSubtitleReader(useFile: File): BaseReader? {
val file = File(data.inputFile) if (!env.canRead(useFile)) {
if (!file.canRead())
throw FileUnavailableException("Can't open file for reading..") throw FileUnavailableException("Can't open file for reading..")
return Reader(file).getSubtitleReader() }
return env.getReader(useFile)
} }
private fun syncDialogs(input: List<Dialog>): List<Dialog> { private fun syncDialogs(input: List<Dialog>): List<Dialog> {
return if (ConverterEnv.syncDialogs) Syncro().sync(input) else input 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) @Throws(FileUnavailableException::class, FileIsNullOrEmpty::class)
fun execute() { override suspend fun convert(data: ConvertTask.Data) {
val file = File(data.inputFile) val file = File(data.inputFile)
listener.onStarted(file.absolutePath) listener.onStarted(file.absolutePath)
try { try {
Configuration.exportJson = true Configuration.exportJson = true
val read = getReader()?.read() ?: throw FileIsNullOrEmpty() val read = getSubtitleReader(file)?.read() ?: throw FileIsNullOrEmpty()
if (read.isEmpty()) if (read.isEmpty())
throw FileIsNullOrEmpty() throw FileIsNullOrEmpty()
val filtered = read.filter { !it.ignore && it.type !in listOf(DialogType.SIGN_SONG, DialogType.CAPTION) } val filtered = read.filter { !it.ignore && it.type !in listOf(DialogType.SIGN_SONG, DialogType.CAPTION) }
val syncOrNotSync = syncDialogs(filtered) 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()) { val outFiles = if (data.formats.isEmpty()) {
exporter.write(syncOrNotSync) exporter.write(syncOrNotSync)
@ -65,22 +57,17 @@ class Converter2(val data: ConvertTask.Data,
} }
exported exported
} }
result = outFiles.map { it.absolutePath } writtenUris = outFiles.map { it.absolutePath }
listener.onCompleted(file.absolutePath, result!!) listener.onCompleted(file.absolutePath, writtenUris!!)
} catch (e: Exception) { } catch (e: Exception) {
listener.onError(file.absolutePath, e.message ?: e.localizedMessage) listener.onError(file.absolutePath, e.message ?: e.localizedMessage)
} }
} }
private var result: List<String>? = null override fun getResult(): List<String> {
fun getResult(): List<String> { if (writtenUris == null) {
if (result == null) {
throw IllegalStateException("Execute must be called before getting result") 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 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.Event
import no.iktdev.eventi.models.Task import no.iktdev.eventi.models.Task
import no.iktdev.eventi.models.store.TaskStatus import no.iktdev.eventi.models.store.TaskStatus
import no.iktdev.eventi.tasks.TaskListener import no.iktdev.eventi.tasks.TaskListener
import no.iktdev.eventi.tasks.TaskType 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.ConvertListener
import no.iktdev.mediaprocessing.converter.convert.Converter
import no.iktdev.mediaprocessing.converter.convert.Converter2 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.events.ConvertTaskResultEvent
import no.iktdev.mediaprocessing.shared.common.event_task_contract.tasks.ConvertTask import no.iktdev.mediaprocessing.shared.common.event_task_contract.tasks.ConvertTask
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
import java.io.File
import java.util.* import java.util.*
@Component @Component
class ConvertTaskListener: TaskListener(TaskType.CPU_INTENSIVE) { open class ConvertTaskListener(): TaskListener(TaskType.CPU_INTENSIVE) {
override fun getWorkerId(): String { override fun getWorkerId(): String {
return "${this::class.java.simpleName}-${TaskType.CPU_INTENSIVE}-${UUID.randomUUID()}" return "${this::class.java.simpleName}-${TaskType.CPU_INTENSIVE}-${UUID.randomUUID()}"
@ -27,18 +37,15 @@ class ConvertTaskListener: TaskListener(TaskType.CPU_INTENSIVE) {
if (task !is ConvertTask) { if (task !is ConvertTask) {
throw IllegalArgumentException("Invalid task type: ${task::class.java.name}") throw IllegalArgumentException("Invalid task type: ${task::class.java.name}")
} }
val converter = Converter2(task.data, object: ConvertListener { val converter = getConverter()
override fun onStarted(inputFile: String) {
}
override fun onCompleted(inputFile: String, outputFiles: List<String>) {
}
})
withHeartbeatRunner { withHeartbeatRunner {
reporter?.updateLastSeen(task.taskId) reporter?.updateLastSeen(task.taskId)
} }
converter.execute() withContext(Dispatchers.Unconfined) {
converter.convert(task.data)
}
return try { return try {
val result = converter.getResult() val result = converter.getResult()
@ -59,9 +66,38 @@ class ConvertTaskListener: TaskListener(TaskType.CPU_INTENSIVE) {
).producedFrom(task) ).producedFrom(task)
newEvent 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 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 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:exfl:0.0.16-SNAPSHOT")
implementation("no.iktdev.streamit.library:streamit-library-db:1.0.0-alpha14") 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") 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.events.MigrateContentToStoreTaskResultEvent
import no.iktdev.mediaprocessing.shared.common.event_task_contract.tasks.MigrateToContentStoreTask 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.model.MigrateStatus
import no.iktdev.mediaprocessing.shared.common.silentTry
import org.jetbrains.annotations.VisibleForTesting import org.jetbrains.annotations.VisibleForTesting
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
import java.io.File import java.io.File
@ -40,13 +41,13 @@ class MigrateContentToStoreTaskListener: TaskListener(TaskType.IO_INTENSIVE) {
coverStatus.none { it.status == MigrateStatus.Failed }) coverStatus.none { it.status == MigrateStatus.Failed })
{ {
pickedTask.data.videoContent?.cachedUri?.let { File(it) }?.let { pickedTask.data.videoContent?.cachedUri?.let { File(it) }?.let {
fs.delete(it) silentTry { fs.delete(it) }
} }
pickedTask.data.subtitleContent?.map { File(it.cachedUri) }?.forEach { pickedTask.data.subtitleContent?.map { File(it.cachedUri) }?.forEach {
fs.delete(it) silentTry { fs.delete(it) }
} }
pickedTask.data.coverContent?.map { File(it.cachedUri) }?.forEach { pickedTask.data.coverContent?.map { File(it.cachedUri) }?.forEach {
fs.delete(it) silentTry { fs.delete(it) }
} }
} else { } else {
status = TaskStatus.Failed status = TaskStatus.Failed

View File

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

View File

@ -102,6 +102,25 @@ class MigrateContentToStoreTaskListenerTest {
assertEquals(MigrateStatus.Failed, result.status) 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 // migrateSubtitle
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
@ -151,6 +170,36 @@ class MigrateContentToStoreTaskListenerTest {
assertEquals(MigrateStatus.Completed, result.first().status) 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 // migrateCover
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
@ -179,6 +228,34 @@ class MigrateContentToStoreTaskListenerTest {
assertEquals(MigrateStatus.Completed, result.first().status) 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 // onTask
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
@ -244,4 +321,61 @@ class MigrateContentToStoreTaskListenerTest {
assertEquals(TaskStatus.Failed, event.status) assertEquals(TaskStatus.Failed, event.status)
assertEquals(0, fs.deleted.size) 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("org.json:json:20210307")
implementation("no.iktdev:exfl:0.0.16-SNAPSHOT") 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") 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("com.zaxxer:HikariCP:7.0.2")
implementation(project(":shared:ffmpeg")) implementation(project(":shared:ffmpeg"))
implementation("no.iktdev:eventi:1.0-rc16") implementation("no.iktdev:eventi:1.0-rc17")
testImplementation(kotlin("test")) testImplementation(kotlin("test"))
testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation(platform("org.junit:junit-bom:5.10.0"))