This commit is contained in:
Brage Skjønborg 2026-01-31 09:51:48 +01:00
parent 767372be54
commit 4e7fd5a9f7
11 changed files with 146 additions and 27 deletions

View File

@ -61,6 +61,9 @@ dependencies {
testImplementation("io.github.classgraph:classgraph:4.8.184")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2")
testImplementation("io.mockk:mockk:1.13.9")
testImplementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.17.0")
testImplementation("org.reflections:reflections:0.10.2")
}

View File

@ -6,7 +6,7 @@ import no.iktdev.eventi.models.store.TaskStatus
/**
* Base class, should not be serialized into
*/
abstract class TaskResultEvent(
open val status: TaskStatus,
open val error: String? = null
open class TaskResultEvent(
val status: TaskStatus,
val error: String? = null
) : Event()

View File

@ -3,10 +3,10 @@ package no.iktdev.mediaprocessing.shared.common.event_task_contract.events
import no.iktdev.eventi.models.store.TaskStatus
import no.iktdev.mediaprocessing.shared.common.event_task_contract.TaskResultEvent
data class ConvertTaskResultEvent(
class ConvertTaskResultEvent(
val data: ConvertedData?,
override val status: TaskStatus,
override val error: String? = null,
status: TaskStatus,
error: String? = null,
): TaskResultEvent(status, error) {
data class ConvertedData(
val language: String,

View File

@ -4,8 +4,8 @@ import com.google.gson.JsonObject
import no.iktdev.eventi.models.store.TaskStatus
import no.iktdev.mediaprocessing.shared.common.event_task_contract.TaskResultEvent
data class CoordinatorReadStreamsResultEvent(
class CoordinatorReadStreamsResultEvent(
val data: JsonObject? = null,
override val status: TaskStatus,
override val error: String? = null
status: TaskStatus,
error: String? = null
) : TaskResultEvent(status, error)

View File

@ -3,10 +3,10 @@ package no.iktdev.mediaprocessing.shared.common.event_task_contract.events
import no.iktdev.eventi.models.store.TaskStatus
import no.iktdev.mediaprocessing.shared.common.event_task_contract.TaskResultEvent
data class CoverDownloadResultEvent(
class CoverDownloadResultEvent(
val data: CoverDownloadedData? = null,
override val status: TaskStatus,
override val error: String? = null
status: TaskStatus,
error: String? = null
) : TaskResultEvent(status, error){
data class CoverDownloadedData(
val source: String,

View File

@ -6,11 +6,11 @@ import no.iktdev.mediaprocessing.shared.common.event_task_contract.TaskResultEve
import no.iktdev.mediaprocessing.shared.common.model.MediaType
import java.util.*
data class MetadataSearchResultEvent(
class MetadataSearchResultEvent(
val results: List<SearchResult> = emptyList(),
val recommended: SearchResult? = null,
override val status: TaskStatus,
override val error: String? = null
status: TaskStatus,
error: String? = null
) : TaskResultEvent(status, error) {
data class SearchResult(
val simpleScore: Int,

View File

@ -4,13 +4,13 @@ import no.iktdev.eventi.models.store.TaskStatus
import no.iktdev.mediaprocessing.shared.common.event_task_contract.TaskResultEvent
import no.iktdev.mediaprocessing.shared.common.model.MigrateStatus
data class MigrateContentToStoreTaskResultEvent(
class MigrateContentToStoreTaskResultEvent(
val collection: String,
val videoMigrate: FileMigration,
val subtitleMigrate: List<SubtitleMigration>,
val coverMigrate: List<FileMigration>,
override val status: TaskStatus,
override val error: String? = null
status: TaskStatus,
error: String? = null
) : TaskResultEvent(status, error) {
data class FileMigration(
val storedUri: String?,

View File

@ -3,10 +3,10 @@ package no.iktdev.mediaprocessing.shared.common.event_task_contract.events
import no.iktdev.eventi.models.store.TaskStatus
import no.iktdev.mediaprocessing.shared.common.event_task_contract.TaskResultEvent
data class ProcesserEncodeResultEvent(
class ProcesserEncodeResultEvent(
val data: EncodeResult? = null,
override val status: TaskStatus,
override val error: String? = null
status: TaskStatus,
error: String? = null
) : TaskResultEvent(status, error) {
data class EncodeResult(
val cachedOutputFile: String? = null

View File

@ -3,10 +3,10 @@ package no.iktdev.mediaprocessing.shared.common.event_task_contract.events
import no.iktdev.eventi.models.store.TaskStatus
import no.iktdev.mediaprocessing.shared.common.event_task_contract.TaskResultEvent
data class ProcesserExtractResultEvent(
class ProcesserExtractResultEvent(
val data: ExtractResult? = null,
override val status: TaskStatus,
override val error: String? = null
status: TaskStatus,
error: String? = null
) : TaskResultEvent(status, error) {
data class ExtractResult(
val language: String,

View File

@ -3,8 +3,8 @@ package no.iktdev.mediaprocessing.shared.common.event_task_contract.events
import no.iktdev.eventi.models.store.TaskStatus
import no.iktdev.mediaprocessing.shared.common.event_task_contract.TaskResultEvent
data class StoreContentAndMetadataTaskResultEvent(
override val status: TaskStatus,
override val error: String? = null
class StoreContentAndMetadataTaskResultEvent(
status: TaskStatus,
error: String? = null
) : TaskResultEvent(status, error){
}

View File

@ -0,0 +1,116 @@
package no.iktdev.mediaprocessing.shared.common.event_task_contract
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.module.kotlin.KotlinFeature
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.google.gson.GsonBuilder
import com.google.gson.JsonDeserializer
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSerializer
import no.iktdev.eventi.models.store.TaskStatus
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertDoesNotThrow
import org.reflections.Reflections
import java.time.Instant
import java.util.*
import kotlin.reflect.KClass
import kotlin.reflect.full.primaryConstructor
class TaskResultEventSerializationTest {
val gson = GsonBuilder()
.registerTypeAdapter(Instant::class.java, JsonSerializer<Instant> { src, _, _ ->
JsonPrimitive(src.toString())
})
.registerTypeAdapter(Instant::class.java, JsonDeserializer { json, _, _ ->
Instant.parse(json.asString)
})
.create()
val jackson = ObjectMapper()
.registerModule(JavaTimeModule())
.registerModule(
KotlinModule.Builder()
.configure(KotlinFeature.NullToEmptyCollection, false)
.configure(KotlinFeature.NullToEmptyMap, false)
.configure(KotlinFeature.NullIsSameAsDefault, false)
.configure(KotlinFeature.StrictNullChecks, true)
.configure(KotlinFeature.UseJavaDurationConversion, true)
.build()
)
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
@Test
fun `all TaskResultEvent subclasses must serialize with Gson and Jackson`() {
val reflections = Reflections("no.iktdev.mediaprocessing") // rotpakken din
val subclasses = reflections.getSubTypesOf(TaskResultEvent::class.java)
require(subclasses.isNotEmpty()) {
"Fant ingen subklasser av TaskResultEvent — er pakken riktig?"
}
subclasses.forEach { clazz ->
assertDoesNotThrow("Serialization failed for ${clazz.simpleName}") {
val instance = createDummyInstance(clazz)
val jsonGson = gson.toJson(instance)
gson.fromJson(jsonGson, clazz)
val jsonJackson = jackson.writeValueAsString(instance)
jackson.readValue(jsonJackson, clazz)
}
}
}
/**
* Lager en dummy-instans av en TaskResultEvent-subklasse.
* Forutsetter at alle event-klasser har en primary constructor.
*/
private fun createDummyInstance(clazz: Class<*>): Any {
val kClass = clazz.kotlin
val ctor = kClass.primaryConstructor
?: error("Klassen ${clazz.simpleName} mangler primary constructor")
val args = ctor.parameters.associateWith { param ->
val kType = param.type
val classifier = kType.classifier
// --- 1. Handle concrete known types first ---
if (classifier == String::class) return@associateWith "dummy"
if (classifier == Int::class) return@associateWith 1
if (classifier == Long::class) return@associateWith 1L
if (classifier == Boolean::class) return@associateWith false
if (classifier == Double::class) return@associateWith 1.0
if (classifier == UUID::class) return@associateWith UUID.randomUUID()
if (classifier == Instant::class) return@associateWith Instant.now()
if (classifier == TaskStatus::class) return@associateWith TaskStatus.Completed
if (classifier == Float::class) return@associateWith 1.0f
// --- 2. Generic enum support ---
if (classifier is KClass<*> && classifier.java.isEnum) {
return@associateWith classifier.java.enumConstants.first()
}
// --- 3. Lists and maps ---
if (classifier == List::class) return@associateWith emptyList<Any>()
if (classifier == Map::class) return@associateWith emptyMap<String, Any>()
// --- 4. Nested data classes ---
if (classifier is KClass<*> && classifier.isData) {
return@associateWith createDummyInstance(classifier.java)
}
// --- 5. Fallback ---
null
}
return ctor.callBy(args)
}
}