fixing(gradle)
This commit is contained in:
@@ -1,17 +1,16 @@
|
||||
// Dieses Modul definiert die Kern-Domänenobjekte des Shared kernels.
|
||||
// Es enthält keine Implementierungsdetails, nur reine Datenklassen und Enums.
|
||||
// Core domain objects of the Shared kernel
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.multiplatform)
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
// Target platforms
|
||||
jvm {
|
||||
compilerOptions {
|
||||
freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime")
|
||||
}
|
||||
}
|
||||
|
||||
js(IR) {
|
||||
browser()
|
||||
}
|
||||
@@ -19,33 +18,52 @@ kotlin {
|
||||
sourceSets {
|
||||
val commonMain by getting {
|
||||
dependencies {
|
||||
// Kern-Abhängigkeiten für das Domänen-Modul (common for all platforms)
|
||||
// Core dependencies (that aren't included in platform-dependencies)
|
||||
api(libs.uuid)
|
||||
// Serialization and date-time for commonMain
|
||||
api(libs.kotlinx.serialization.json)
|
||||
api(libs.kotlinx.datetime)
|
||||
}
|
||||
}
|
||||
|
||||
val jvmMain by getting {
|
||||
dependencies {
|
||||
// Stellt sicher, dass dieses Modul Zugriff auf die im zentralen Katalog
|
||||
// definierten Bibliotheken hat (JVM-specific)
|
||||
api(projects.platform.platformDependencies)
|
||||
}
|
||||
}
|
||||
|
||||
val commonTest by getting {
|
||||
dependencies {
|
||||
implementation(libs.kotlin.test)
|
||||
}
|
||||
}
|
||||
|
||||
val jsMain by getting {
|
||||
dependencies {
|
||||
api(libs.kotlinx.coroutines.core)
|
||||
}
|
||||
}
|
||||
|
||||
val jsTest by getting {
|
||||
dependencies {
|
||||
implementation(libs.kotlin.test)
|
||||
}
|
||||
}
|
||||
|
||||
val jvmMain by getting {
|
||||
dependencies {
|
||||
// Fachliches Domain-Modul: keine technischen Abhängigkeiten hier hinterlegen.
|
||||
// Falls in Zukunft JVM-spezifische, fachlich neutrale Ergänzungen nötig sind,
|
||||
// bitte bewusst und minimal hinzufügen.
|
||||
}
|
||||
}
|
||||
|
||||
val jvmTest by getting {
|
||||
dependencies {
|
||||
// Stellt die Test-Bibliotheken bereit (JVM-specific)
|
||||
// implementation(kotlin("test-junit5"))
|
||||
implementation(libs.junit.jupiter.api)
|
||||
implementation(libs.mockk)
|
||||
implementation(projects.platform.platformTesting)
|
||||
implementation(libs.bundles.testing.jvm)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named<Test>("jvmTest") {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
@@ -2,20 +2,17 @@ package at.mocode.core.domain.event
|
||||
|
||||
import at.mocode.core.domain.model.*
|
||||
import at.mocode.core.domain.serialization.KotlinInstantSerializer
|
||||
import at.mocode.core.domain.serialization.UuidSerializer
|
||||
import com.benasher44.uuid.Uuid
|
||||
import com.benasher44.uuid.uuid4
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.ExperimentalTime
|
||||
import kotlin.time.Instant
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.time.ExperimentalTime
|
||||
|
||||
@OptIn(ExperimentalTime::class)
|
||||
|
||||
/**
|
||||
* Basis-Interface für alle Domänen-Events im System.
|
||||
* Ein Domänen-Event repräsentiert etwas fachlich Bedeutsames, das passiert ist.
|
||||
* Basis-Interface für alle Domain-Events im System.
|
||||
* Ein Domain-Event beschreibt ein fachlich relevantes Ereignis, das stattgefunden hat.
|
||||
*/
|
||||
@OptIn(ExperimentalTime::class)
|
||||
interface DomainEvent {
|
||||
val eventId: EventId
|
||||
val aggregateId: AggregateId
|
||||
@@ -27,7 +24,7 @@ interface DomainEvent {
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstrakte Basisklasse für Domänen-Events, um Boilerplate-Code zu reduzieren.
|
||||
* Abstrakte Basisklasse für Domain-Events, um Boilerplate zu reduzieren.
|
||||
*/
|
||||
@Serializable
|
||||
@OptIn(ExperimentalTime::class)
|
||||
@@ -37,13 +34,36 @@ abstract class BaseDomainEvent(
|
||||
override val version: EventVersion,
|
||||
override val eventId: EventId = EventId(uuid4()),
|
||||
@Serializable(with = KotlinInstantSerializer::class)
|
||||
override val timestamp: Instant = Clock.System.now(),
|
||||
override val timestamp: Instant,
|
||||
override val correlationId: CorrelationId? = null,
|
||||
override val causationId: CausationId? = null
|
||||
) : DomainEvent
|
||||
) : DomainEvent {
|
||||
|
||||
constructor(
|
||||
aggregateId: AggregateId,
|
||||
eventType: EventType,
|
||||
version: EventVersion,
|
||||
eventId: EventId = EventId(uuid4()),
|
||||
correlationId: CorrelationId? = null,
|
||||
causationId: CausationId? = null
|
||||
) : this(
|
||||
aggregateId = aggregateId,
|
||||
eventType = eventType,
|
||||
version = version,
|
||||
eventId = eventId,
|
||||
timestamp = createTimestamp(),
|
||||
correlationId = correlationId,
|
||||
causationId = causationId
|
||||
)
|
||||
|
||||
companion object {
|
||||
@OptIn(ExperimentalTime::class)
|
||||
private fun createTimestamp(): Instant = Clock.System.now()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface für einen Publisher, der Domänen-Events veröffentlichen kann.
|
||||
* Schnittstelle für einen Publisher, der Domain-Events veröffentlichen kann.
|
||||
*/
|
||||
interface DomainEventPublisher {
|
||||
suspend fun publish(event: DomainEvent)
|
||||
@@ -51,9 +71,14 @@ interface DomainEventPublisher {
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface für einen Handler, der auf bestimmte Domänen-Events reagieren kann.
|
||||
* Schnittstelle für einen Handler, der auf bestimmte Domain-Events reagieren kann.
|
||||
*/
|
||||
interface DomainEventHandler<T : DomainEvent> {
|
||||
suspend fun handle(event: T)
|
||||
fun canHandle(eventType: String): Boolean
|
||||
fun canHandle(eventType: EventType): Boolean
|
||||
|
||||
/**
|
||||
* Rückwärtskompatible Methode für String-basierte Prüfung des Event-Typs.
|
||||
*/
|
||||
fun canHandle(eventType: String): Boolean = canHandle(EventType(eventType))
|
||||
}
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
package at.mocode.core.domain.model
|
||||
|
||||
import at.mocode.core.domain.serialization.KotlinInstantSerializer
|
||||
import at.mocode.core.domain.serialization.UuidSerializer
|
||||
import com.benasher44.uuid.Uuid
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Instant
|
||||
import kotlin.time.ExperimentalTime
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.ExperimentalTime
|
||||
import kotlin.time.Instant
|
||||
|
||||
/**
|
||||
* A marker interface for all Data Transfer Objects.
|
||||
* Marker-Interface für alle Data-Transfer-Objekte (DTO).
|
||||
*/
|
||||
interface BaseDto
|
||||
|
||||
/**
|
||||
* Base DTO for domain entities that have unique ID and audit timestamps.
|
||||
* Basis-DTO für Domänen-Entitäten mit eindeutiger ID und Audit-Zeitstempeln.
|
||||
*/
|
||||
@Serializable
|
||||
@OptIn(ExperimentalTime::class)
|
||||
@@ -29,17 +27,17 @@ abstract class EntityDto : BaseDto {
|
||||
}
|
||||
|
||||
/**
|
||||
* A structured representation of a single error.
|
||||
* Strukturierte Darstellung eines einzelnen Fehlers (Code, Nachricht, optionales Feld).
|
||||
*/
|
||||
@Serializable
|
||||
data class ErrorDto(
|
||||
val code: String,
|
||||
val code: ErrorCode,
|
||||
val message: String,
|
||||
val field: String? = null
|
||||
) : BaseDto
|
||||
|
||||
/**
|
||||
* A standardized and consistent wrapper for all API responses.
|
||||
* Standardisierte Hülle für API-Antworten mit einheitlicher Struktur.
|
||||
*/
|
||||
@Serializable
|
||||
@OptIn(ExperimentalTime::class)
|
||||
@@ -48,12 +46,26 @@ data class ApiResponse<T>(
|
||||
val success: Boolean,
|
||||
val errors: List<ErrorDto> = emptyList(),
|
||||
@Serializable(with = KotlinInstantSerializer::class)
|
||||
val timestamp: Instant = Clock.System.now()
|
||||
val timestamp: Instant
|
||||
) {
|
||||
companion object {
|
||||
@OptIn(ExperimentalTime::class)
|
||||
fun <T> success(data: T): ApiResponse<T> {
|
||||
return ApiResponse(data = data, success = true)
|
||||
return ApiResponse(data = data, success = true, timestamp = Clock.System.now())
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalTime::class)
|
||||
fun <T> error(
|
||||
code: ErrorCode,
|
||||
message: String,
|
||||
field: String? = null
|
||||
): ApiResponse<T> {
|
||||
return ApiResponse(
|
||||
data = null,
|
||||
success = false,
|
||||
errors = listOf(ErrorDto(code = code, message = message, field = field)),
|
||||
timestamp = Clock.System.now()
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalTime::class)
|
||||
@@ -62,30 +74,52 @@ data class ApiResponse<T>(
|
||||
message: String,
|
||||
field: String? = null
|
||||
): ApiResponse<T> {
|
||||
return ApiResponse(
|
||||
data = null,
|
||||
success = false,
|
||||
errors = listOf(ErrorDto(code = code, message = message, field = field))
|
||||
)
|
||||
return error(ErrorCode(code), message, field)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalTime::class)
|
||||
fun <T> error(errors: List<ErrorDto>): ApiResponse<T> {
|
||||
return ApiResponse(data = null, success = false, errors = errors)
|
||||
return ApiResponse(data = null, success = false, errors = errors, timestamp = Clock.System.now())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A standardized wrapper for paginated API responses.
|
||||
* Standardisierte Hülle für paginierte API-Antworten.
|
||||
*/
|
||||
@Serializable
|
||||
data class PagedResponse<T>(
|
||||
val content: List<T>,
|
||||
val page: Int,
|
||||
val size: Int,
|
||||
val page: PageNumber,
|
||||
val size: PageSize,
|
||||
val totalElements: Long,
|
||||
val totalPages: Int,
|
||||
val hasNext: Boolean,
|
||||
val hasPrevious: Boolean
|
||||
)
|
||||
) {
|
||||
companion object {
|
||||
/**
|
||||
* Erzeugt eine PagedResponse mit Rückwärtskompatibilität für einfache Int-Werte.
|
||||
* Nützlich, wenn Aufrufer noch keine PageNumber/PageSize verwenden.
|
||||
*/
|
||||
fun <T> create(
|
||||
content: List<T>,
|
||||
page: Int,
|
||||
size: Int,
|
||||
totalElements: Long,
|
||||
totalPages: Int,
|
||||
hasNext: Boolean,
|
||||
hasPrevious: Boolean
|
||||
): PagedResponse<T> {
|
||||
return PagedResponse(
|
||||
content = content,
|
||||
page = PageNumber(page),
|
||||
size = PageSize(size),
|
||||
totalElements = totalElements,
|
||||
totalPages = totalPages,
|
||||
hasNext = hasNext,
|
||||
hasPrevious = hasPrevious
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
package at.mocode.core.domain.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Gemeinsame Enums, die domänenweit verwendet werden.
|
||||
* Teil des Shared Kernel zur Sicherung einer konsistenten Fachsprache.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Quelle eines Datensatzes. Querschnittsthema und daher Teil des Shared Kernel.
|
||||
*/
|
||||
@Serializable
|
||||
enum class DatenQuelleE {
|
||||
MANUELL,
|
||||
IMPORT_ZNS,
|
||||
SYSTEM_GENERATED,
|
||||
IMPORT_API
|
||||
}
|
||||
|
||||
/**
|
||||
* Allgemeiner Status von Entitäten in der Domäne.
|
||||
*/
|
||||
@Serializable
|
||||
enum class StatusE {
|
||||
AKTIV,
|
||||
INAKTIV,
|
||||
ENTWURF,
|
||||
ARCHIVIERT,
|
||||
GELOESCHT
|
||||
}
|
||||
|
||||
/**
|
||||
* Prioritätsstufen für unterschiedliche Domänen-Objekte.
|
||||
*/
|
||||
@Serializable
|
||||
enum class PrioritaetE {
|
||||
NIEDRIG,
|
||||
NORMAL,
|
||||
HOCH,
|
||||
KRITISCH
|
||||
}
|
||||
|
||||
/**
|
||||
* Häufige Benutzerrollen im System.
|
||||
*/
|
||||
@Serializable
|
||||
enum class BenutzerRolleE {
|
||||
ADMIN,
|
||||
BENUTZER,
|
||||
MODERATOR,
|
||||
GAST,
|
||||
SYSTEM
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifikationsstatus für Datensätze.
|
||||
*/
|
||||
@Serializable
|
||||
enum class VerifikationsStatusE {
|
||||
NICHT_VERIFIZIERT,
|
||||
IN_PRUEFUNG,
|
||||
VERIFIZIERT,
|
||||
ABGELEHNT,
|
||||
KORREKTUR_ERFORDERLICH
|
||||
}
|
||||
|
||||
/**
|
||||
* Processing states for workflows and tasks.
|
||||
*/
|
||||
@Serializable
|
||||
enum class BearbeitungsStatusE {
|
||||
OFFEN,
|
||||
IN_BEARBEITUNG,
|
||||
WARTEND,
|
||||
ABGESCHLOSSEN,
|
||||
ABGEBROCHEN,
|
||||
FEHLER
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package at.mocode.core.domain.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Repräsentiert einen Validierungsfehler mit Feldname, Nachricht und Fehlercode.
|
||||
* Wird von Validierungs-Hilfsfunktionen im gesamten System verwendet.
|
||||
*/
|
||||
@Serializable
|
||||
data class ValidationError(
|
||||
val field: String,
|
||||
val message: String,
|
||||
val code: String
|
||||
) : BaseDto {
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Erzeugt einen Validierungsfehler für Pflichtfeld-Prüfungen.
|
||||
*/
|
||||
fun required(field: String): ValidationError {
|
||||
return ValidationError(field, "$field ist erforderlich", "REQUIRED")
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt einen Validierungsfehler für ungültiges Format.
|
||||
*/
|
||||
fun invalidFormat(field: String, message: String = "Ungültiges Format"): ValidationError {
|
||||
return ValidationError(field, message, "INVALID_FORMAT")
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt einen Validierungsfehler für Längenprüfungen.
|
||||
*/
|
||||
fun invalidLength(field: String, message: String): ValidationError {
|
||||
return ValidationError(field, message, "INVALID_LENGTH")
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt einen Validierungsfehler für Bereichsprüfungen.
|
||||
*/
|
||||
fun invalidRange(field: String, message: String): ValidationError {
|
||||
return ValidationError(field, message, "INVALID_RANGE")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,23 +2,23 @@ package at.mocode.core.domain.model
|
||||
|
||||
import at.mocode.core.domain.serialization.UuidSerializer
|
||||
import com.benasher44.uuid.Uuid
|
||||
import kotlin.jvm.JvmInline
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.jvm.JvmInline
|
||||
|
||||
/**
|
||||
* Value classes for strongly typed IDs and domain values.
|
||||
* These provide compile-time type safety without runtime overhead.
|
||||
* Value-Classes für stark typisierte IDs und Fachwerte.
|
||||
* Bieten Typsicherheit zur Compile-Zeit ohne Laufzeit-Overhead.
|
||||
*/
|
||||
|
||||
// === ID Value Classes ===
|
||||
|
||||
/**
|
||||
* A strongly typed wrapper for entity IDs.
|
||||
* Stark typisierte Hülle für Entitäts-IDs.
|
||||
*/
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class EntityId(@Serializable(with = UuidSerializer::class) val value: Uuid) {
|
||||
override fun toString(): String = value.toString()
|
||||
companion object
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -27,7 +27,7 @@ value class EntityId(@Serializable(with = UuidSerializer::class) val value: Uuid
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class EventId(@Serializable(with = UuidSerializer::class) val value: Uuid) {
|
||||
override fun toString(): String = value.toString()
|
||||
companion object
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -36,7 +36,7 @@ value class EventId(@Serializable(with = UuidSerializer::class) val value: Uuid)
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class AggregateId(@Serializable(with = UuidSerializer::class) val value: Uuid) {
|
||||
override fun toString(): String = value.toString()
|
||||
companion object
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -45,7 +45,7 @@ value class AggregateId(@Serializable(with = UuidSerializer::class) val value: U
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class CorrelationId(@Serializable(with = UuidSerializer::class) val value: Uuid) {
|
||||
override fun toString(): String = value.toString()
|
||||
companion object
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,7 +54,7 @@ value class CorrelationId(@Serializable(with = UuidSerializer::class) val value:
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class CausationId(@Serializable(with = UuidSerializer::class) val value: Uuid) {
|
||||
override fun toString(): String = value.toString()
|
||||
companion object
|
||||
}
|
||||
|
||||
// === Domain Value Classes ===
|
||||
|
||||
+69
-19
@@ -2,8 +2,6 @@ package at.mocode.core.domain.serialization
|
||||
|
||||
import com.benasher44.uuid.Uuid
|
||||
import com.benasher44.uuid.uuidFrom
|
||||
import kotlin.time.Instant // KORRIGIERT: Finaler Wechsel zu kotlin.time
|
||||
import kotlin.time.ExperimentalTime
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import kotlinx.datetime.LocalTime
|
||||
@@ -13,34 +11,86 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlin.time.ExperimentalTime
|
||||
import kotlin.time.Instant
|
||||
|
||||
object UuidSerializer : KSerializer<Uuid> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING)
|
||||
override fun serialize(encoder: Encoder, value: Uuid) = encoder.encodeString(value.toString())
|
||||
override fun deserialize(decoder: Decoder): Uuid = uuidFrom(decoder.decodeString())
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializer für kotlin.time. Instant Objekte.
|
||||
* Konvertiert Instant zu/von ISO-8601 String-Repräsentation.
|
||||
*/
|
||||
@OptIn(ExperimentalTime::class)
|
||||
object KotlinInstantSerializer : KSerializer<Instant> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Instant", PrimitiveKind.STRING)
|
||||
override fun serialize(encoder: Encoder, value: Instant) = encoder.encodeString(value.toString())
|
||||
override fun deserialize(decoder: Decoder): Instant = Instant.parse(decoder.decodeString())
|
||||
|
||||
override fun serialize(encoder: Encoder, value: Instant) {
|
||||
encoder.encodeString(value.toString())
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): Instant {
|
||||
return Instant.parse(decoder.decodeString())
|
||||
}
|
||||
}
|
||||
|
||||
object KotlinLocalDateSerializer : KSerializer<LocalDate> {
|
||||
/**
|
||||
* Serializer für UUID Objekte.
|
||||
* Konvertiert UUID zu/von String-Repräsentation.
|
||||
*/
|
||||
object UuidSerializer : KSerializer<Uuid> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING)
|
||||
|
||||
override fun serialize(encoder: Encoder, value: Uuid) {
|
||||
encoder.encodeString(value.toString())
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): Uuid {
|
||||
return uuidFrom(decoder.decodeString())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializer für kotlinx.datetime.LocalDate Objekte.
|
||||
* Konvertiert LocalDate zu/von ISO-8601 String-Repräsentation.
|
||||
*/
|
||||
object LocalDateSerializer : KSerializer<LocalDate> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.STRING)
|
||||
override fun serialize(encoder: Encoder, value: LocalDate) = encoder.encodeString(value.toString())
|
||||
override fun deserialize(decoder: Decoder): LocalDate = LocalDate.parse(decoder.decodeString())
|
||||
|
||||
override fun serialize(encoder: Encoder, value: LocalDate) {
|
||||
encoder.encodeString(value.toString())
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): LocalDate {
|
||||
return LocalDate.parse(decoder.decodeString())
|
||||
}
|
||||
}
|
||||
|
||||
object KotlinLocalDateTimeSerializer : KSerializer<LocalDateTime> {
|
||||
/**
|
||||
* Serializer für kotlinx.datetime. LocalDateTime Objekte.
|
||||
* Konvertiert LocalDateTime zu/von ISO-8601 String-Repräsentation.
|
||||
*/
|
||||
object LocalDateTimeSerializer : KSerializer<LocalDateTime> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LocalDateTime", PrimitiveKind.STRING)
|
||||
override fun serialize(encoder: Encoder, value: LocalDateTime) = encoder.encodeString(value.toString())
|
||||
override fun deserialize(decoder: Decoder): LocalDateTime = LocalDateTime.parse(decoder.decodeString())
|
||||
|
||||
override fun serialize(encoder: Encoder, value: LocalDateTime) {
|
||||
encoder.encodeString(value.toString())
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): LocalDateTime {
|
||||
return LocalDateTime.parse(decoder.decodeString())
|
||||
}
|
||||
}
|
||||
|
||||
object KotlinLocalTimeSerializer : KSerializer<LocalTime> {
|
||||
/**
|
||||
* Serializer für kotlinx.datetime.LocalTime Objekte.
|
||||
* Konvertiert LocalTime zu/von ISO-8601 String-Repräsentation.
|
||||
*/
|
||||
object LocalTimeSerializer : KSerializer<LocalTime> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LocalTime", PrimitiveKind.STRING)
|
||||
override fun serialize(encoder: Encoder, value: LocalTime) = encoder.encodeString(value.toString())
|
||||
override fun deserialize(decoder: Decoder): LocalTime = LocalTime.parse(decoder.decodeString())
|
||||
|
||||
override fun serialize(encoder: Encoder, value: LocalTime) {
|
||||
encoder.encodeString(value.toString())
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): LocalTime {
|
||||
return LocalTime.parse(decoder.decodeString())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package at.mocode.core.domain
|
||||
|
||||
import at.mocode.core.domain.model.ApiResponse
|
||||
import at.mocode.core.domain.model.ErrorCode
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
@OptIn(kotlin.time.ExperimentalTime::class)
|
||||
class ApiResponseTest {
|
||||
|
||||
@Test
|
||||
fun `success factory sets flags and timestamp`() {
|
||||
val res = ApiResponse.success(data = 42)
|
||||
assertTrue(res.success)
|
||||
assertEquals(42, res.data)
|
||||
assertTrue(res.errors.isEmpty())
|
||||
assertNotNull(res.timestamp)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `error factory with code object`() {
|
||||
val res = ApiResponse.error<Int>(ErrorCode("INVALID_INPUT"), "Fehlerhafte Eingabe", field = "name")
|
||||
assertFalse(res.success)
|
||||
assertNull(res.data)
|
||||
assertEquals(1, res.errors.size)
|
||||
assertEquals("INVALID_INPUT", res.errors.first().code.value)
|
||||
assertEquals("Fehlerhafte Eingabe", res.errors.first().message)
|
||||
assertEquals("name", res.errors.first().field)
|
||||
assertNotNull(res.timestamp)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `error factory with code string`() {
|
||||
val res = ApiResponse.error<Int>("NOT_FOUND", "Nicht gefunden")
|
||||
assertFalse(res.success)
|
||||
assertNull(res.data)
|
||||
assertEquals(1, res.errors.size)
|
||||
assertEquals("NOT_FOUND", res.errors.first().code.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `error factory with list`() {
|
||||
val res = ApiResponse.error<Int>(listOf())
|
||||
assertFalse(res.success)
|
||||
assertNull(res.data)
|
||||
assertTrue(res.errors.isEmpty())
|
||||
assertNotNull(res.timestamp)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package at.mocode.core.domain
|
||||
|
||||
import at.mocode.core.domain.event.BaseDomainEvent
|
||||
import at.mocode.core.domain.model.*
|
||||
import com.benasher44.uuid.uuid4
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
|
||||
@OptIn(kotlin.time.ExperimentalTime::class)
|
||||
class BaseDomainEventTest {
|
||||
|
||||
@kotlinx.serialization.Serializable
|
||||
data class TestEvent(
|
||||
val name: String,
|
||||
// Delegiert an BaseDomainEvent
|
||||
private val base: BaseDomainEvent
|
||||
) : BaseDomainEvent(
|
||||
aggregateId = base.aggregateId,
|
||||
eventType = base.eventType,
|
||||
version = base.version,
|
||||
eventId = base.eventId,
|
||||
timestamp = base.timestamp,
|
||||
correlationId = base.correlationId,
|
||||
causationId = base.causationId
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `secondary constructor generates id and timestamp`() {
|
||||
val aggId = AggregateId(uuid4())
|
||||
val ev = object : BaseDomainEvent(
|
||||
aggregateId = aggId,
|
||||
eventType = EventType("TestEvent"),
|
||||
version = EventVersion(1)
|
||||
) {}
|
||||
|
||||
assertNotNull(ev.eventId)
|
||||
assertNotNull(ev.timestamp)
|
||||
assertEquals(aggId, ev.aggregateId)
|
||||
assertEquals(EventType("TestEvent"), ev.eventType)
|
||||
assertEquals(EventVersion(1), ev.version)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `primary constructor uses provided id and timestamp`() {
|
||||
val aggId = AggregateId(uuid4())
|
||||
val eid = EventId(uuid4())
|
||||
val ts = kotlin.time.Instant.parse("2025-01-01T00:00:00Z")
|
||||
val base = object : BaseDomainEvent(
|
||||
aggregateId = aggId,
|
||||
eventType = EventType("TestEvent"),
|
||||
version = EventVersion(2),
|
||||
eventId = eid,
|
||||
timestamp = ts,
|
||||
correlationId = CorrelationId(uuid4()),
|
||||
causationId = CausationId(uuid4())
|
||||
) {}
|
||||
|
||||
assertEquals(eid, base.eventId)
|
||||
assertEquals(ts, base.timestamp)
|
||||
assertEquals(EventVersion(2), base.version)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package at.mocode.core.domain
|
||||
|
||||
import at.mocode.core.domain.serialization.*
|
||||
import com.benasher44.uuid.uuid4
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import kotlinx.datetime.LocalTime
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
@OptIn(kotlin.time.ExperimentalTime::class)
|
||||
class SerializersTest {
|
||||
|
||||
@Test
|
||||
fun `Instant roundtrip`() {
|
||||
val instant = kotlin.time.Instant.parse("2024-01-01T00:00:00Z")
|
||||
val json = Json.encodeToString(KotlinInstantSerializer, instant)
|
||||
val decoded = Json.decodeFromString(KotlinInstantSerializer, json)
|
||||
assertEquals(instant, decoded)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `UUID roundtrip`() {
|
||||
val uuid = uuid4()
|
||||
val json = Json.encodeToString(UuidSerializer, uuid)
|
||||
val decoded = Json.decodeFromString(UuidSerializer, json)
|
||||
assertEquals(uuid, decoded)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `LocalDate roundtrip`() {
|
||||
val ld = LocalDate.parse("2024-06-15")
|
||||
val json = Json.encodeToString(LocalDateSerializer, ld)
|
||||
val decoded = Json.decodeFromString(LocalDateSerializer, json)
|
||||
assertEquals(ld, decoded)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `LocalDateTime roundtrip`() {
|
||||
val ldt = LocalDateTime.parse("2024-06-15T12:34:56")
|
||||
val json = Json.encodeToString(LocalDateTimeSerializer, ldt)
|
||||
val decoded = Json.decodeFromString(LocalDateTimeSerializer, json)
|
||||
assertEquals(ldt, decoded)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `LocalTime roundtrip`() {
|
||||
val lt = LocalTime.parse("12:34:56")
|
||||
val json = Json.encodeToString(LocalTimeSerializer, lt)
|
||||
val decoded = Json.decodeFromString(LocalTimeSerializer, json)
|
||||
assertEquals(lt, decoded)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package at.mocode.core.domain
|
||||
|
||||
import at.mocode.core.domain.model.*
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class ValueTypesTest {
|
||||
|
||||
@Test
|
||||
fun `EventType validation works`() {
|
||||
assertFailsWith<IllegalArgumentException> { EventType("") }
|
||||
assertFailsWith<IllegalArgumentException> { EventType("1Bad") }
|
||||
assertFailsWith<IllegalArgumentException> { EventType("bad-char!") }
|
||||
assertEquals("OrderCreated", EventType("OrderCreated").toString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `EventVersion must be non-negative and comparable`() {
|
||||
assertFailsWith<IllegalArgumentException> { EventVersion(-1) }
|
||||
assertEquals(0, EventVersion(0).compareTo(EventVersion(0)))
|
||||
assertTrue(EventVersion(2) > EventVersion(1))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ErrorCode must be uppercase with allowed characters`() {
|
||||
assertFailsWith<IllegalArgumentException> { ErrorCode("") }
|
||||
assertFailsWith<IllegalArgumentException> { ErrorCode("abc") }
|
||||
assertFailsWith<IllegalArgumentException> { ErrorCode("Bad_Code") }
|
||||
assertEquals("VALID_CODE1", ErrorCode("VALID_CODE1").toString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `PageNumber must be non-negative`() {
|
||||
assertFailsWith<IllegalArgumentException> { PageNumber(-1) }
|
||||
assertEquals("0", PageNumber(0).toString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `PageSize range is enforced`() {
|
||||
assertFailsWith<IllegalArgumentException> { PageSize(0) }
|
||||
assertFailsWith<IllegalArgumentException> { PageSize(1001) }
|
||||
assertEquals("1000", PageSize(1000).toString())
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package at.mocode.core.domain.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Defines the source of a data record. This is a cross-cutting concern
|
||||
* and therefore part of the Shared Kernel.
|
||||
*/
|
||||
@Serializable
|
||||
enum class DatenQuelleE {
|
||||
MANUELL,
|
||||
IMPORT_ZNS,
|
||||
SYSTEM_GENERATED,
|
||||
IMPORT_API
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package at.mocode.core.utils.error
|
||||
|
||||
/**
|
||||
* A functional approach to error handling, avoiding exceptions for predictable errors.
|
||||
* Represents a value that can either be a Success (containing the result) or a Failure (containing an error).
|
||||
*
|
||||
* @param T The type of the success value.
|
||||
* @param E The type of the error value.
|
||||
*/
|
||||
sealed class Result<out T, out E> {
|
||||
data class Success<out T>(val value: T) : Result<T, Nothing>()
|
||||
data class Failure<out E>(val error: E) : Result<Nothing, E>()
|
||||
|
||||
val isSuccess: Boolean get() = this is Success
|
||||
val isFailure: Boolean get() = this is Failure
|
||||
|
||||
fun getOrNull(): T? = when (this) {
|
||||
is Success -> value
|
||||
is Failure -> null
|
||||
}
|
||||
|
||||
fun getOrElse(defaultValue: @UnsafeVariance T): T = when (this) {
|
||||
is Success -> value
|
||||
is Failure -> defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
// Extension functions for convenient usage
|
||||
inline fun <T, E> Result<T, E>.onSuccess(action: (T) -> Unit): Result<T, E> {
|
||||
if (this is Result.Success) action(value)
|
||||
return this
|
||||
}
|
||||
|
||||
inline fun <T, E> Result<T, E>.onFailure(action: (E) -> Unit): Result<T, E> {
|
||||
if (this is Result.Failure) action(error)
|
||||
return this
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package at.mocode.core.utils.validation
|
||||
|
||||
/**
|
||||
* Represents a single validation error.
|
||||
* @param field The name of the field that failed validation.
|
||||
* @param message A user-friendly error message.
|
||||
* @param code A machine-readable error code for the client.
|
||||
*/
|
||||
data class ValidationError(
|
||||
val field: String,
|
||||
val message: String,
|
||||
val code: String? = null
|
||||
)
|
||||
|
||||
/**
|
||||
* Represents the result of a validation process as a sealed class.
|
||||
* This ensures that a result is either Valid or Invalid, but never both.
|
||||
*/
|
||||
sealed class ValidationResult {
|
||||
/**
|
||||
* Represents a successful validation.
|
||||
*/
|
||||
object Valid : ValidationResult()
|
||||
|
||||
/**
|
||||
* Represents a failed validation with a list of specific errors.
|
||||
*/
|
||||
data class Invalid(val errors: List<ValidationError>) : ValidationResult()
|
||||
|
||||
fun isValid(): Boolean = this is Valid
|
||||
fun isInvalid(): Boolean = this is Invalid
|
||||
|
||||
companion object {
|
||||
fun invalid(field: String, message: String, code: String? = null): Invalid {
|
||||
return Invalid(listOf(ValidationError(field, message, code)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An exception that can be thrown to represent validation failure,
|
||||
* allowing it to be caught by centralized error handling (like Ktor StatusPages).
|
||||
*/
|
||||
class ValidationException(
|
||||
val validationResult: ValidationResult.Invalid
|
||||
) : IllegalArgumentException(
|
||||
"Validation failed: ${validationResult.errors.joinToString { "${it.field}: ${it.message}" }}"
|
||||
)
|
||||
@@ -1,67 +0,0 @@
|
||||
package at.mocode.core.domain
|
||||
|
||||
import at.mocode.core.domain.model.ApiResponse
|
||||
import at.mocode.core.domain.model.ErrorDto
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertFalse
|
||||
import org.junit.jupiter.api.Assertions.assertNotNull
|
||||
import org.junit.jupiter.api.Assertions.assertNull
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class ApiResponseTest {
|
||||
|
||||
@Test
|
||||
fun `ApiResponse success should create a successful response with data`() {
|
||||
// Arrange
|
||||
val testData = "This is a test"
|
||||
|
||||
// Act
|
||||
val response = ApiResponse.success(testData)
|
||||
|
||||
// Assert
|
||||
assertTrue(response.success, "Response should be successful")
|
||||
assertEquals(testData, response.data, "Response data should match the input data")
|
||||
assertTrue(response.errors.isEmpty(), "Errors list should be empty for a successful response")
|
||||
assertNotNull(response.timestamp, "Timestamp should be generated")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ApiResponse error with single message should create a failed response with one error`() {
|
||||
// Arrange
|
||||
val errorCode = "NOT_FOUND"
|
||||
val errorMessage = "The requested resource was not found."
|
||||
val errorField = "resourceId"
|
||||
|
||||
// Act
|
||||
val response = ApiResponse.error<Unit>(errorCode, errorMessage, errorField)
|
||||
|
||||
// Assert
|
||||
assertFalse(response.success, "Response should not be successful")
|
||||
assertNull(response.data, "Data should be null for a failed response")
|
||||
assertEquals(1, response.errors.size, "Should contain exactly one error")
|
||||
|
||||
val error = response.errors.first()
|
||||
assertEquals(errorCode, error.code, "Error code should match")
|
||||
assertEquals(errorMessage, error.message, "Error message should match")
|
||||
assertEquals(errorField, error.field, "Error field should match")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ApiResponse error with list should create a failed response with multiple errors`() {
|
||||
// Arrange
|
||||
val errors = listOf(
|
||||
ErrorDto("INVALID_INPUT", "Username cannot be empty.", "username"),
|
||||
ErrorDto("INVALID_INPUT", "Password is too short.", "password")
|
||||
)
|
||||
|
||||
// Act
|
||||
val response = ApiResponse.error<Unit>(errors)
|
||||
|
||||
// Assert
|
||||
assertFalse(response.success, "Response should not be successful")
|
||||
assertNull(response.data, "Data should be null for a failed response")
|
||||
assertEquals(2, response.errors.size, "Should contain two errors")
|
||||
assertEquals(errors, response.errors, "The error list should match the input list")
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package at.mocode.core.domain
|
||||
|
||||
import at.mocode.core.domain.event.BaseDomainEvent
|
||||
import at.mocode.core.domain.model.*
|
||||
import com.benasher44.uuid.Uuid
|
||||
import com.benasher44.uuid.uuid4
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNotNull
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class DomainEventTest {
|
||||
|
||||
/**
|
||||
* Eine konkrete Implementierung eines Domänen-Events zu Testzwecken.
|
||||
* Repräsentiert das Ereignis, dass eine Test-Entität erstellt wurde.
|
||||
*
|
||||
* @param aggregateId Die ID der Entität, auf die sich das Event bezieht.
|
||||
* @param version Die Versionsnummer des Events für dieses Aggregat.
|
||||
* @param testPayload Ein zusätzliches Datenfeld, das für den Test relevant ist.
|
||||
*/
|
||||
@Serializable
|
||||
data class TestEvent(
|
||||
@Transient
|
||||
override val aggregateId: AggregateId = AggregateId(uuid4()),
|
||||
@Transient
|
||||
override val version: EventVersion = EventVersion(1L),
|
||||
val testPayload: String = "Test"
|
||||
) : BaseDomainEvent(
|
||||
aggregateId = aggregateId,
|
||||
eventType = EventType("TestEventOccurred"), // Ein klar definierter Event-Typ
|
||||
version = version
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `BaseDomainEvent should auto-generate eventId and timestamp upon creation`() {
|
||||
// Arrange
|
||||
val aggregateId = AggregateId(uuid4())
|
||||
val version = EventVersion(1L)
|
||||
|
||||
// Act
|
||||
val event = TestEvent(aggregateId, version)
|
||||
|
||||
// Assert
|
||||
assertNotNull(event.eventId, "eventId should be automatically generated and not null")
|
||||
assertNotNull(event.timestamp, "timestamp should be automatically generated and not null")
|
||||
assertEquals(aggregateId, event.aggregateId, "aggregateId should be set correctly")
|
||||
assertEquals(version, event.version, "version should be set correctly")
|
||||
assertEquals(EventType("TestEventOccurred"), event.eventType, "eventType should be set correctly")
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
package at.mocode.core.domain
|
||||
|
||||
import at.mocode.core.domain.serialization.KotlinInstantSerializer
|
||||
import at.mocode.core.domain.serialization.KotlinLocalDateSerializer
|
||||
import at.mocode.core.domain.serialization.KotlinLocalDateTimeSerializer
|
||||
import at.mocode.core.domain.serialization.KotlinLocalTimeSerializer
|
||||
import at.mocode.core.domain.serialization.UuidSerializer
|
||||
import com.benasher44.uuid.Uuid
|
||||
import com.benasher44.uuid.uuid4
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import kotlinx.datetime.LocalTime
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Instant
|
||||
|
||||
class SerializersTest {
|
||||
|
||||
private val json = Json // Standard-Json-Konfiguration für die Tests
|
||||
|
||||
// Hilfsklasse, um die Serializer im Kontext von kotlinx.serialization zu testen
|
||||
@Serializable
|
||||
data class TestContainer(
|
||||
@Serializable(with = UuidSerializer::class) val uuid: Uuid,
|
||||
@Serializable(with = KotlinInstantSerializer::class) val instant: Instant,
|
||||
@Serializable(with = KotlinLocalDateSerializer::class) val localDate: LocalDate,
|
||||
@Serializable(with = KotlinLocalDateTimeSerializer::class) val localDateTime: LocalDateTime,
|
||||
@Serializable(with = KotlinLocalTimeSerializer::class) val localTime: LocalTime
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `all custom serializers should correctly serialize and deserialize`() {
|
||||
// Arrange
|
||||
val originalObject = TestContainer(
|
||||
uuid = uuid4(),
|
||||
instant = Clock.System.now(),
|
||||
localDate = LocalDate(2025, 8, 5),
|
||||
localDateTime = LocalDateTime(2025, 8, 5, 12, 30, 0),
|
||||
localTime = LocalTime(12, 30, 0)
|
||||
)
|
||||
|
||||
// Act: Serialize
|
||||
val jsonString = json.encodeToString(TestContainer.serializer(), originalObject)
|
||||
|
||||
// Assert: Serialization
|
||||
// Wir prüfen, ob die serialisierten Werte einfache Strings sind, wie erwartet.
|
||||
assertTrue(
|
||||
jsonString.contains("\"uuid\":\"${originalObject.uuid}\""),
|
||||
"Serialized JSON should contain the UUID as a string"
|
||||
)
|
||||
assertTrue(
|
||||
jsonString.contains("\"instant\":\"${originalObject.instant}\""),
|
||||
"Serialized JSON should contain the Instant as a string"
|
||||
)
|
||||
assertTrue(
|
||||
jsonString.contains("\"localDate\":\"2025-08-05\""),
|
||||
"Serialized JSON should contain the LocalDate as a string"
|
||||
)
|
||||
assertTrue(
|
||||
jsonString.contains("\"localDateTime\":\"2025-08-05T12:30\""),
|
||||
"Serialized JSON should contain the LocalDateTime as a string"
|
||||
)
|
||||
assertTrue(
|
||||
jsonString.contains("\"localTime\":\"12:30\""),
|
||||
"Serialized JSON should contain the LocalTime as a string"
|
||||
)
|
||||
|
||||
// Act: Deserialize
|
||||
val deserializedObject = json.decodeFromString(TestContainer.serializer(), jsonString)
|
||||
|
||||
// Assert: Deserialization
|
||||
assertEquals(originalObject, deserializedObject, "Deserialized object should be equal to the original object")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user