Merge pull request #18
* MP-19 Refactoring: Einführung der "Registry" & "Masterdata" Trennung … * MP-19 Refactoring: Frontend Tabula Rasa * MP-19 Refactoring: Frontend Tabula Rasa * refactoring: * MP-20 fix(docker/clients): include `:domains` module in web/desktop b… * MP-20 fix(web-app build): resolve JS compile error and add dev/prod b… * MP-20 fix(web-app): remove vendor.js reference and harden JS bootstra… * MP-20 fixing: clients * MP-20 fixing: clients
This commit is contained in:
@@ -1,71 +1,73 @@
|
||||
plugins {
|
||||
alias(libs.plugins.kotlinMultiplatform)
|
||||
alias(libs.plugins.kotlinSerialization)
|
||||
alias(libs.plugins.kotlinMultiplatform)
|
||||
alias(libs.plugins.kotlinSerialization)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(21)
|
||||
jvmToolchain(21)
|
||||
|
||||
jvm {
|
||||
compilerOptions {
|
||||
freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime")
|
||||
}
|
||||
jvm {
|
||||
compilerOptions {
|
||||
freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime")
|
||||
}
|
||||
}
|
||||
|
||||
js(IR) {
|
||||
browser {
|
||||
testTask {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
// Opt-in to experimental Kotlin UUID API across all source sets
|
||||
all {
|
||||
languageSettings.optIn("kotlin.uuid.ExperimentalUuidApi")
|
||||
// Opt-in für kotlin.time.ExperimentalTime projektweit, solange Teile noch experimentell sind
|
||||
languageSettings.optIn("kotlin.time.ExperimentalTime")
|
||||
}
|
||||
|
||||
js(IR) {
|
||||
browser {
|
||||
testTask {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
commonMain.dependencies {
|
||||
// Core dependencies (that aren't included in platform-dependencies)
|
||||
// Note: core-domain should NOT depend on core-utils to avoid circular dependencies
|
||||
// core-utils depends on core-domain, not the other way around
|
||||
|
||||
sourceSets {
|
||||
// Opt-in to experimental Kotlin UUID API across all source sets
|
||||
all {
|
||||
languageSettings.optIn("kotlin.uuid.ExperimentalUuidApi")
|
||||
}
|
||||
|
||||
commonMain.dependencies {
|
||||
// Core dependencies (that aren't included in platform-dependencies)
|
||||
// Note: core-domain should NOT depend on core-utils to avoid circular dependencies
|
||||
// core-utils depends on core-domain, not the other way around
|
||||
|
||||
// Serialization and date-time for commonMain
|
||||
api(libs.kotlinx.serialization.json)
|
||||
api(libs.kotlinx.datetime)
|
||||
|
||||
}
|
||||
|
||||
commonTest.dependencies {
|
||||
implementation(libs.kotlin.test)
|
||||
}
|
||||
|
||||
jsMain.dependencies {
|
||||
api(libs.kotlinx.coroutines.core)
|
||||
}
|
||||
|
||||
jsTest.dependencies {
|
||||
implementation(libs.kotlin.test)
|
||||
}
|
||||
|
||||
jvmMain.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.
|
||||
}
|
||||
|
||||
jvmTest.dependencies {
|
||||
// implementation(kotlin("test-junit5"))
|
||||
implementation(libs.junit.jupiter.api)
|
||||
implementation(libs.mockk)
|
||||
implementation(projects.platform.platformTesting)
|
||||
implementation(libs.bundles.testing.jvm)
|
||||
}
|
||||
// Serialization and date-time for commonMain
|
||||
api(libs.kotlinx.serialization.json)
|
||||
api(libs.kotlinx.datetime)
|
||||
|
||||
}
|
||||
|
||||
commonTest.dependencies {
|
||||
implementation(libs.kotlin.test)
|
||||
}
|
||||
|
||||
jsMain.dependencies {
|
||||
api(libs.kotlinx.coroutines.core)
|
||||
}
|
||||
|
||||
jsTest.dependencies {
|
||||
implementation(libs.kotlin.test)
|
||||
}
|
||||
|
||||
jvmMain.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.
|
||||
}
|
||||
|
||||
jvmTest.dependencies {
|
||||
// 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()
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
|
||||
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.KotlinxInstantSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.ExperimentalTime
|
||||
import kotlin.time.Clock as KtClock
|
||||
import kotlin.time.Instant
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
@@ -13,73 +13,71 @@ import kotlin.uuid.Uuid
|
||||
* 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
|
||||
val eventType: EventType
|
||||
val timestamp: Instant
|
||||
val version: EventVersion
|
||||
val correlationId: CorrelationId?
|
||||
val causationId: CausationId?
|
||||
val eventId: EventId
|
||||
val aggregateId: AggregateId
|
||||
val eventType: EventType
|
||||
val timestamp: Instant
|
||||
val version: EventVersion
|
||||
val correlationId: CorrelationId?
|
||||
val causationId: CausationId?
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstrakte Basisklasse für Domain-Events, um Boilerplate zu reduzieren.
|
||||
*/
|
||||
@Serializable
|
||||
@OptIn(ExperimentalTime::class)
|
||||
abstract class BaseDomainEvent(
|
||||
override val aggregateId: AggregateId,
|
||||
override val eventType: EventType,
|
||||
override val version: EventVersion,
|
||||
override val eventId: EventId = EventId(Uuid.random()),
|
||||
@Serializable(with = KotlinInstantSerializer::class)
|
||||
override val timestamp: Instant,
|
||||
override val correlationId: CorrelationId? = null,
|
||||
override val causationId: CausationId? = null
|
||||
override val aggregateId: AggregateId,
|
||||
override val eventType: EventType,
|
||||
override val version: EventVersion,
|
||||
override val eventId: EventId = EventId(Uuid.random()),
|
||||
|
||||
@Serializable(with = KotlinxInstantSerializer::class)
|
||||
override val timestamp: Instant,
|
||||
override val correlationId: CorrelationId? = null,
|
||||
override val causationId: CausationId? = null
|
||||
) : DomainEvent {
|
||||
|
||||
constructor(
|
||||
aggregateId: AggregateId,
|
||||
eventType: EventType,
|
||||
version: EventVersion,
|
||||
eventId: EventId = EventId(Uuid.random()),
|
||||
correlationId: CorrelationId? = null,
|
||||
causationId: CausationId? = null
|
||||
) : this(
|
||||
aggregateId = aggregateId,
|
||||
eventType = eventType,
|
||||
version = version,
|
||||
eventId = eventId,
|
||||
timestamp = createTimestamp(),
|
||||
correlationId = correlationId,
|
||||
causationId = causationId
|
||||
)
|
||||
constructor(
|
||||
aggregateId: AggregateId,
|
||||
eventType: EventType,
|
||||
version: EventVersion,
|
||||
eventId: EventId = EventId(Uuid.random()),
|
||||
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()
|
||||
}
|
||||
companion object {
|
||||
private fun createTimestamp(): Instant = Instant.parse(KtClock.System.now().toString())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schnittstelle für einen Publisher, der Domain-Events veröffentlichen kann.
|
||||
*/
|
||||
interface DomainEventPublisher {
|
||||
suspend fun publish(event: DomainEvent)
|
||||
suspend fun publishAll(events: List<DomainEvent>)
|
||||
suspend fun publish(event: DomainEvent)
|
||||
suspend fun publishAll(events: List<DomainEvent>)
|
||||
}
|
||||
|
||||
/**
|
||||
* Schnittstelle für einen Handler, der auf bestimmte Domain-Events reagieren kann.
|
||||
*/
|
||||
interface DomainEventHandler<T : DomainEvent> {
|
||||
suspend fun handle(event: T)
|
||||
fun canHandle(eventType: EventType): Boolean
|
||||
suspend fun handle(event: T)
|
||||
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))
|
||||
/**
|
||||
* Rückwärtskompatible Methode für String-basierte Prüfung des Event-Typs.
|
||||
*/
|
||||
fun canHandle(eventType: String): Boolean = canHandle(EventType(eventType))
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package at.mocode.core.domain.model
|
||||
|
||||
import at.mocode.core.domain.serialization.KotlinInstantSerializer
|
||||
import at.mocode.core.domain.serialization.KotlinxInstantSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.ExperimentalTime
|
||||
@@ -15,15 +15,14 @@ interface BaseDto
|
||||
* Basis-DTO für Domänen-Entitäten mit eindeutiger ID und Audit-Zeitstempeln.
|
||||
*/
|
||||
@Serializable
|
||||
@OptIn(ExperimentalTime::class)
|
||||
abstract class EntityDto : BaseDto {
|
||||
abstract val id: EntityId
|
||||
abstract val id: EntityId
|
||||
|
||||
@Serializable(with = KotlinInstantSerializer::class)
|
||||
abstract val createdAt: Instant
|
||||
@Serializable(with = KotlinxInstantSerializer::class)
|
||||
abstract val createdAt: Instant
|
||||
|
||||
@Serializable(with = KotlinInstantSerializer::class)
|
||||
abstract val updatedAt: Instant
|
||||
@Serializable(with = KotlinxInstantSerializer::class)
|
||||
abstract val updatedAt: Instant
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -31,57 +30,63 @@ abstract class EntityDto : BaseDto {
|
||||
*/
|
||||
@Serializable
|
||||
data class ErrorDto(
|
||||
val code: ErrorCode,
|
||||
val message: String,
|
||||
val field: String? = null
|
||||
val code: ErrorCode,
|
||||
val message: String,
|
||||
val field: String? = null
|
||||
) : BaseDto
|
||||
|
||||
/**
|
||||
* Standardisierte Hülle für API-Antworten mit einheitlicher Struktur.
|
||||
*/
|
||||
@Serializable
|
||||
@OptIn(ExperimentalTime::class)
|
||||
data class ApiResponse<T>(
|
||||
val data: T?,
|
||||
val success: Boolean,
|
||||
val errors: List<ErrorDto> = emptyList(),
|
||||
@Serializable(with = KotlinInstantSerializer::class)
|
||||
val timestamp: Instant
|
||||
val data: T?,
|
||||
val success: Boolean,
|
||||
val errors: List<ErrorDto> = emptyList(),
|
||||
@Serializable(with = KotlinxInstantSerializer::class)
|
||||
val timestamp: Instant
|
||||
) {
|
||||
companion object {
|
||||
@OptIn(ExperimentalTime::class)
|
||||
fun <T> success(data: T): ApiResponse<T> {
|
||||
return ApiResponse(data = data, success = true, timestamp = Clock.System.now())
|
||||
}
|
||||
companion object {
|
||||
@OptIn(ExperimentalTime::class)
|
||||
fun <T> success(data: T): ApiResponse<T> =
|
||||
ApiResponse(
|
||||
data = data,
|
||||
success = true,
|
||||
timestamp = Instant.parse(Clock.System.now().toString())
|
||||
)
|
||||
|
||||
@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)
|
||||
fun <T> error(
|
||||
code: String,
|
||||
message: String,
|
||||
field: String? = null
|
||||
): ApiResponse<T> {
|
||||
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, 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 = Instant.parse(Clock.System.now().toString())
|
||||
)
|
||||
}
|
||||
|
||||
fun <T> error(
|
||||
code: String,
|
||||
message: String,
|
||||
field: String? = null
|
||||
): ApiResponse<T> {
|
||||
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,
|
||||
timestamp = Instant.parse(Clock.System.now().toString())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,37 +94,37 @@ data class ApiResponse<T>(
|
||||
*/
|
||||
@Serializable
|
||||
data class PagedResponse<T>(
|
||||
val content: List<T>,
|
||||
val page: PageNumber,
|
||||
val size: PageSize,
|
||||
val totalElements: Long,
|
||||
val totalPages: Int,
|
||||
val hasNext: Boolean,
|
||||
val hasPrevious: Boolean
|
||||
val content: List<T>,
|
||||
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
|
||||
)
|
||||
}
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,10 +12,10 @@ import kotlinx.serialization.Serializable
|
||||
*/
|
||||
@Serializable
|
||||
enum class DatenQuelleE {
|
||||
MANUELL,
|
||||
IMPORT_ZNS,
|
||||
SYSTEM_GENERATED,
|
||||
IMPORT_API
|
||||
MANUELL,
|
||||
IMPORT_ZNS,
|
||||
SYSTEM_GENERATED,
|
||||
IMPORT_API
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -23,11 +23,11 @@ enum class DatenQuelleE {
|
||||
*/
|
||||
@Serializable
|
||||
enum class StatusE {
|
||||
AKTIV,
|
||||
INAKTIV,
|
||||
ENTWURF,
|
||||
ARCHIVIERT,
|
||||
GELOESCHT
|
||||
AKTIV,
|
||||
INAKTIV,
|
||||
ENTWURF,
|
||||
ARCHIVIERT,
|
||||
GELOESCHT
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,10 +35,10 @@ enum class StatusE {
|
||||
*/
|
||||
@Serializable
|
||||
enum class PrioritaetE {
|
||||
NIEDRIG,
|
||||
NORMAL,
|
||||
HOCH,
|
||||
KRITISCH
|
||||
NIEDRIG,
|
||||
NORMAL,
|
||||
HOCH,
|
||||
KRITISCH
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -46,11 +46,11 @@ enum class PrioritaetE {
|
||||
*/
|
||||
@Serializable
|
||||
enum class BenutzerRolleE {
|
||||
ADMIN,
|
||||
BENUTZER,
|
||||
MODERATOR,
|
||||
GAST,
|
||||
SYSTEM
|
||||
ADMIN,
|
||||
BENUTZER,
|
||||
MODERATOR,
|
||||
GAST,
|
||||
SYSTEM
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,11 +58,11 @@ enum class BenutzerRolleE {
|
||||
*/
|
||||
@Serializable
|
||||
enum class VerifikationsStatusE {
|
||||
NICHT_VERIFIZIERT,
|
||||
IN_PRUEFUNG,
|
||||
VERIFIZIERT,
|
||||
ABGELEHNT,
|
||||
KORREKTUR_ERFORDERLICH
|
||||
NICHT_VERIFIZIERT,
|
||||
IN_PRUEFUNG,
|
||||
VERIFIZIERT,
|
||||
ABGELEHNT,
|
||||
KORREKTUR_ERFORDERLICH
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,10 +70,10 @@ enum class VerifikationsStatusE {
|
||||
*/
|
||||
@Serializable
|
||||
enum class BearbeitungsStatusE {
|
||||
OFFEN,
|
||||
IN_BEARBEITUNG,
|
||||
WARTEND,
|
||||
ABGESCHLOSSEN,
|
||||
ABGEBROCHEN,
|
||||
FEHLER
|
||||
OFFEN,
|
||||
IN_BEARBEITUNG,
|
||||
WARTEND,
|
||||
ABGESCHLOSSEN,
|
||||
ABGEBROCHEN,
|
||||
FEHLER
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package at.mocode.core.domain.model
|
||||
|
||||
/**
|
||||
* Zentrale Sammlung der standardisierten Fehlercodes der Anwendung.
|
||||
* Dient als Single-Source-of-Truth, um Inkonsistenzen zu vermeiden.
|
||||
*/
|
||||
object ErrorCodes {
|
||||
val DUPLICATE_ENTRY = ErrorCode("DUPLICATE_ENTRY")
|
||||
val CONSTRAINT_VIOLATION = ErrorCode("CONSTRAINT_VIOLATION")
|
||||
val FOREIGN_KEY_VIOLATION = ErrorCode("FOREIGN_KEY_VIOLATION")
|
||||
val CHECK_VIOLATION = ErrorCode("CHECK_VIOLATION")
|
||||
val DATABASE_TIMEOUT = ErrorCode("DATABASE_TIMEOUT")
|
||||
val DATABASE_ERROR = ErrorCode("DATABASE_ERROR")
|
||||
val TRANSACTION_ERROR = ErrorCode("TRANSACTION_ERROR")
|
||||
val VALIDATION_ERROR = ErrorCode("VALIDATION_ERROR")
|
||||
}
|
||||
+31
-31
@@ -8,38 +8,38 @@ import kotlinx.serialization.Serializable
|
||||
*/
|
||||
@Serializable
|
||||
data class ValidationError(
|
||||
val field: String,
|
||||
val message: String,
|
||||
val code: String
|
||||
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")
|
||||
}
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
|
||||
package at.mocode.core.domain.model
|
||||
|
||||
import at.mocode.core.domain.serialization.UuidSerializer
|
||||
@@ -19,7 +20,7 @@ import kotlin.uuid.Uuid
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class EntityId(@Serializable(with = UuidSerializer::class) val value: Uuid) {
|
||||
companion object
|
||||
companion object
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -28,7 +29,7 @@ value class EntityId(@Serializable(with = UuidSerializer::class) val value: Uuid
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class EventId(@Serializable(with = UuidSerializer::class) val value: Uuid) {
|
||||
companion object
|
||||
companion object
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,7 +38,7 @@ value class EventId(@Serializable(with = UuidSerializer::class) val value: Uuid)
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class AggregateId(@Serializable(with = UuidSerializer::class) val value: Uuid) {
|
||||
companion object
|
||||
companion object
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -46,7 +47,7 @@ value class AggregateId(@Serializable(with = UuidSerializer::class) val value: U
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class CorrelationId(@Serializable(with = UuidSerializer::class) val value: Uuid) {
|
||||
companion object
|
||||
companion object
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,7 +56,7 @@ value class CorrelationId(@Serializable(with = UuidSerializer::class) val value:
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class CausationId(@Serializable(with = UuidSerializer::class) val value: Uuid) {
|
||||
companion object
|
||||
companion object
|
||||
}
|
||||
|
||||
// === Domain Value Classes ===
|
||||
@@ -66,14 +67,14 @@ value class CausationId(@Serializable(with = UuidSerializer::class) val value: U
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class EventType(val value: String) {
|
||||
init {
|
||||
require(value.isNotBlank()) { "Event type cannot be blank" }
|
||||
require(value.matches(Regex("^[A-Za-z][A-Za-z0-9]*$"))) {
|
||||
"Event type must start with a letter and contain only alphanumeric characters"
|
||||
}
|
||||
init {
|
||||
require(value.isNotBlank()) { "Event type cannot be blank" }
|
||||
require(value.matches(Regex("^[A-Za-z][A-Za-z0-9]*$"))) {
|
||||
"Event type must start with a letter and contain only alphanumeric characters"
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String = value
|
||||
override fun toString(): String = value
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,13 +83,13 @@ value class EventType(val value: String) {
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class EventVersion(val value: Long) : Comparable<EventVersion> {
|
||||
init {
|
||||
require(value >= 0) { "Event version must be non-negative" }
|
||||
}
|
||||
init {
|
||||
require(value >= 0) { "Event version must be non-negative" }
|
||||
}
|
||||
|
||||
override fun toString(): String = value.toString()
|
||||
override fun toString(): String = value.toString()
|
||||
|
||||
override fun compareTo(other: EventVersion): Int = value.compareTo(other.value)
|
||||
override fun compareTo(other: EventVersion): Int = value.compareTo(other.value)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,14 +98,14 @@ value class EventVersion(val value: Long) : Comparable<EventVersion> {
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class ErrorCode(val value: String) {
|
||||
init {
|
||||
require(value.isNotBlank()) { "Error code cannot be blank" }
|
||||
require(value.matches(Regex("^[A-Z][A-Z0-9_]*$"))) {
|
||||
"Error code must be uppercase and contain only letters, numbers, and underscores"
|
||||
}
|
||||
init {
|
||||
require(value.isNotBlank()) { "Error code cannot be blank" }
|
||||
require(value.matches(Regex("^[A-Z][A-Z0-9_]*$"))) {
|
||||
"Error code must be uppercase and contain only letters, numbers, and underscores"
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String = value
|
||||
override fun toString(): String = value
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,11 +114,11 @@ value class ErrorCode(val value: String) {
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class PageNumber(val value: Int) {
|
||||
init {
|
||||
require(value >= 0) { "Page number must be non-negative" }
|
||||
}
|
||||
init {
|
||||
require(value >= 0) { "Page number must be non-negative" }
|
||||
}
|
||||
|
||||
override fun toString(): String = value.toString()
|
||||
override fun toString(): String = value.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,10 +127,10 @@ value class PageNumber(val value: Int) {
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class PageSize(val value: Int) {
|
||||
init {
|
||||
require(value > 0) { "Page size must be positive" }
|
||||
require(value <= 1000) { "Page size cannot exceed 1000" }
|
||||
}
|
||||
init {
|
||||
require(value > 0) { "Page size must be positive" }
|
||||
require(value <= 1000) { "Page size cannot exceed 1000" }
|
||||
}
|
||||
|
||||
override fun toString(): String = value.toString()
|
||||
override fun toString(): String = value.toString()
|
||||
}
|
||||
|
||||
+12
-12
@@ -14,19 +14,19 @@ import kotlinx.serialization.encoding.Encoder
|
||||
* Serializes as ISO-8601 date string (yyyy-MM-dd).
|
||||
*/
|
||||
object KotlinLocalDateSerializer : KSerializer<LocalDate> {
|
||||
override val descriptor: SerialDescriptor =
|
||||
PrimitiveSerialDescriptor("KotlinLocalDate", PrimitiveKind.STRING)
|
||||
override val descriptor: SerialDescriptor =
|
||||
PrimitiveSerialDescriptor("KotlinLocalDate", PrimitiveKind.STRING)
|
||||
|
||||
override fun serialize(encoder: Encoder, value: LocalDate) {
|
||||
encoder.encodeString(value.toString())
|
||||
}
|
||||
override fun serialize(encoder: Encoder, value: LocalDate) {
|
||||
encoder.encodeString(value.toString())
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): LocalDate {
|
||||
val text = decoder.decodeString()
|
||||
return try {
|
||||
LocalDate.parse(text)
|
||||
} catch (e: Exception) {
|
||||
throw SerializationException("Invalid LocalDate format: '$text'", e)
|
||||
}
|
||||
override fun deserialize(decoder: Decoder): LocalDate {
|
||||
val text = decoder.decodeString()
|
||||
return try {
|
||||
LocalDate.parse(text)
|
||||
} catch (e: Exception) {
|
||||
throw SerializationException("Invalid LocalDate format: '$text'", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-10
@@ -1,7 +1,6 @@
|
||||
@file:OptIn(kotlin.time.ExperimentalTime::class)
|
||||
package at.mocode.core.domain.serialization
|
||||
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlin.time.Instant
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
@@ -10,17 +9,17 @@ import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
|
||||
/**
|
||||
* Serializer for kotlinx.datetime.Instant.
|
||||
* Serializer for kotlin.time.Instant.
|
||||
* Uses ISO-8601 string representation.
|
||||
*/
|
||||
object KotlinxInstantSerializer : KSerializer<Instant> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("KotlinxInstant", PrimitiveKind.STRING)
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("KotlinxInstant", PrimitiveKind.STRING)
|
||||
|
||||
override fun serialize(encoder: Encoder, value: Instant) {
|
||||
encoder.encodeString(value.toString())
|
||||
}
|
||||
override fun serialize(encoder: Encoder, value: Instant) {
|
||||
encoder.encodeString(value.toString())
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): Instant {
|
||||
return Instant.parse(decoder.decodeString())
|
||||
}
|
||||
override fun deserialize(decoder: Decoder): Instant {
|
||||
return Instant.parse(decoder.decodeString())
|
||||
}
|
||||
}
|
||||
|
||||
+35
-35
@@ -20,15 +20,15 @@ import kotlin.uuid.Uuid
|
||||
*/
|
||||
@OptIn(ExperimentalTime::class)
|
||||
object KotlinInstantSerializer : KSerializer<Instant> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Instant", PrimitiveKind.STRING)
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Instant", PrimitiveKind.STRING)
|
||||
|
||||
override fun serialize(encoder: Encoder, value: Instant) {
|
||||
encoder.encodeString(value.toString())
|
||||
}
|
||||
override fun serialize(encoder: Encoder, value: Instant) {
|
||||
encoder.encodeString(value.toString())
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): Instant {
|
||||
return Instant.parse(decoder.decodeString())
|
||||
}
|
||||
override fun deserialize(decoder: Decoder): Instant {
|
||||
return Instant.parse(decoder.decodeString())
|
||||
}
|
||||
}
|
||||
|
||||
// Note: Serializer for kotlinx.datetime.Instant is defined in a separate file
|
||||
@@ -39,15 +39,15 @@ object KotlinInstantSerializer : KSerializer<Instant> {
|
||||
*/
|
||||
@OptIn(ExperimentalUuidApi::class)
|
||||
object UuidSerializer : KSerializer<Uuid> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING)
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING)
|
||||
|
||||
override fun serialize(encoder: Encoder, value: Uuid) {
|
||||
encoder.encodeString(value.toString())
|
||||
}
|
||||
override fun serialize(encoder: Encoder, value: Uuid) {
|
||||
encoder.encodeString(value.toString())
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): Uuid {
|
||||
return Uuid.parse(decoder.decodeString())
|
||||
}
|
||||
override fun deserialize(decoder: Decoder): Uuid {
|
||||
return Uuid.parse(decoder.decodeString())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,15 +55,15 @@ object UuidSerializer : KSerializer<Uuid> {
|
||||
* Konvertiert LocalDate zu/von ISO-8601 String-Repräsentation.
|
||||
*/
|
||||
object LocalDateSerializer : KSerializer<LocalDate> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.STRING)
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.STRING)
|
||||
|
||||
override fun serialize(encoder: Encoder, value: LocalDate) {
|
||||
encoder.encodeString(value.toString())
|
||||
}
|
||||
override fun serialize(encoder: Encoder, value: LocalDate) {
|
||||
encoder.encodeString(value.toString())
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): LocalDate {
|
||||
return LocalDate.parse(decoder.decodeString())
|
||||
}
|
||||
override fun deserialize(decoder: Decoder): LocalDate {
|
||||
return LocalDate.parse(decoder.decodeString())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,15 +71,15 @@ object LocalDateSerializer : KSerializer<LocalDate> {
|
||||
* Konvertiert LocalDateTime zu/von ISO-8601 String-Repräsentation.
|
||||
*/
|
||||
object LocalDateTimeSerializer : KSerializer<LocalDateTime> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LocalDateTime", PrimitiveKind.STRING)
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LocalDateTime", PrimitiveKind.STRING)
|
||||
|
||||
override fun serialize(encoder: Encoder, value: LocalDateTime) {
|
||||
encoder.encodeString(value.toString())
|
||||
}
|
||||
override fun serialize(encoder: Encoder, value: LocalDateTime) {
|
||||
encoder.encodeString(value.toString())
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): LocalDateTime {
|
||||
return LocalDateTime.parse(decoder.decodeString())
|
||||
}
|
||||
override fun deserialize(decoder: Decoder): LocalDateTime {
|
||||
return LocalDateTime.parse(decoder.decodeString())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,13 +87,13 @@ object LocalDateTimeSerializer : KSerializer<LocalDateTime> {
|
||||
* Konvertiert LocalTime zu/von ISO-8601 String-Repräsentation.
|
||||
*/
|
||||
object LocalTimeSerializer : KSerializer<LocalTime> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LocalTime", PrimitiveKind.STRING)
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LocalTime", PrimitiveKind.STRING)
|
||||
|
||||
override fun serialize(encoder: Encoder, value: LocalTime) {
|
||||
encoder.encodeString(value.toString())
|
||||
}
|
||||
override fun serialize(encoder: Encoder, value: LocalTime) {
|
||||
encoder.encodeString(value.toString())
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): LocalTime {
|
||||
return LocalTime.parse(decoder.decodeString())
|
||||
}
|
||||
override fun deserialize(decoder: Decoder): LocalTime {
|
||||
return LocalTime.parse(decoder.decodeString())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,42 +12,42 @@ 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 `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 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 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)
|
||||
}
|
||||
@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)
|
||||
}
|
||||
}
|
||||
|
||||
+46
-46
@@ -10,54 +10,54 @@ import kotlin.uuid.Uuid
|
||||
@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
|
||||
)
|
||||
@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(Uuid.random())
|
||||
val ev = object : BaseDomainEvent(
|
||||
aggregateId = aggId,
|
||||
eventType = EventType("TestEvent"),
|
||||
version = EventVersion(1)
|
||||
) {}
|
||||
@Test
|
||||
fun `secondary constructor generates id and timestamp`() {
|
||||
val aggId = AggregateId(Uuid.random())
|
||||
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)
|
||||
}
|
||||
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(Uuid.random())
|
||||
val eid = EventId(Uuid.random())
|
||||
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(Uuid.random()),
|
||||
causationId = CausationId(Uuid.random())
|
||||
) {}
|
||||
@Test
|
||||
fun `primary constructor uses provided id and timestamp`() {
|
||||
val aggId = AggregateId(Uuid.random())
|
||||
val eid = EventId(Uuid.random())
|
||||
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(Uuid.random()),
|
||||
causationId = CausationId(Uuid.random())
|
||||
) {}
|
||||
|
||||
assertEquals(eid, base.eventId)
|
||||
assertEquals(ts, base.timestamp)
|
||||
assertEquals(EventVersion(2), base.version)
|
||||
}
|
||||
assertEquals(eid, base.eventId)
|
||||
assertEquals(ts, base.timestamp)
|
||||
assertEquals(EventVersion(2), base.version)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,43 +12,43 @@ import kotlin.uuid.Uuid
|
||||
@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 `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 = Uuid.random()
|
||||
val json = Json.encodeToString(UuidSerializer, uuid)
|
||||
val decoded = Json.decodeFromString(UuidSerializer, json)
|
||||
assertEquals(uuid, decoded)
|
||||
}
|
||||
@Test
|
||||
fun `UUID roundtrip`() {
|
||||
val uuid = Uuid.random()
|
||||
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 `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 `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)
|
||||
}
|
||||
@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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,39 +8,39 @@ 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 `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 `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 `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 `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())
|
||||
}
|
||||
@Test
|
||||
fun `PageSize range is enforced`() {
|
||||
assertFailsWith<IllegalArgumentException> { PageSize(0) }
|
||||
assertFailsWith<IllegalArgumentException> { PageSize(1001) }
|
||||
assertEquals("1000", PageSize(1000).toString())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user