From af82cb6e57535937873b36ad7b1b9a8c5db3bfe2 Mon Sep 17 00:00:00 2001 From: Jurjen Vorhauer Date: Tue, 27 Feb 2024 11:06:15 +0100 Subject: [PATCH] use properties iso functions wherever possible; also added first part of Tag maintenance. --- build.gradle.kts | 3 +- deploy/deployment.yaml | 6 +-- src/main/kotlin/blog/Main.kt | 2 + src/main/kotlin/blog/model/Note.kt | 36 ++++++++------- src/main/kotlin/blog/model/State.kt | 15 ++++++- src/main/kotlin/blog/model/Tag.kt | 54 ++++++++++++++++++++--- src/main/kotlin/blog/model/Task.kt | 23 +++++----- src/main/kotlin/blog/model/User.kt | 14 +++--- src/main/kotlin/blog/model/model.kt | 10 ++--- src/main/kotlin/blog/module/Validation.kt | 4 +- src/main/kotlin/blog/read/Counts.kt | 3 +- src/main/kotlin/blog/read/Info.kt | 4 +- src/main/kotlin/blog/read/Reader.kt | 40 ++++++++++------- src/main/kotlin/blog/write/Processor.kt | 38 ++++++++++------ src/test/kotlin/blog/model/ModelTests.kt | 11 +---- src/test/kotlin/blog/model/NoteTests.kt | 16 +++---- src/test/kotlin/blog/model/StateTests.kt | 18 ++++---- src/test/kotlin/blog/model/TagTests.kt | 46 +++++++++++++++++++ src/test/kotlin/blog/model/TaskTests.kt | 18 ++++---- src/test/kotlin/blog/model/UserTests.kt | 14 +++--- src/test/kotlin/blog/read/ReaderTests.kt | 12 ++--- 21 files changed, 253 insertions(+), 134 deletions(-) create mode 100644 src/test/kotlin/blog/model/TagTests.kt diff --git a/build.gradle.kts b/build.gradle.kts index f47232b..4a17229 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -78,7 +78,6 @@ dependencies { implementation("io.sentry:sentry-logback:7.2.0") testImplementation("com.typesafe.akka:akka-persistence-testkit_2.13:2.9.1") - testImplementation("org.jetbrains.kotlin:kotlin-test-junit:1.9.22") testImplementation("org.assertj:assertj-core:3.25.2") testImplementation(platform("org.junit:junit-bom:5.10.2")) testImplementation("org.junit.jupiter:junit-jupiter") @@ -101,7 +100,7 @@ tasks.withType { } jacoco { - toolVersion = "0.8.9" + toolVersion = "0.8.11" } tasks.jacocoTestReport { diff --git a/deploy/deployment.yaml b/deploy/deployment.yaml index f76ad79..47f4d3a 100644 --- a/deploy/deployment.yaml +++ b/deploy/deployment.yaml @@ -43,11 +43,11 @@ spec: value: /var/local/scb_e93d3b60-4128-4094-8824-a37327f973c4_europe-west1.zip resources: limits: - cpu: "2" - memory: "2048Mi" + cpu: "4" + memory: "3072Mi" requests: cpu: 500m - memory: "512Mi" + memory: "1024Mi" livenessProbe: failureThreshold: 5 httpGet: diff --git a/src/main/kotlin/blog/Main.kt b/src/main/kotlin/blog/Main.kt index b6c83d1..10d67f8 100644 --- a/src/main/kotlin/blog/Main.kt +++ b/src/main/kotlin/blog/Main.kt @@ -13,6 +13,7 @@ import io.sentry.Sentry import blog.config.Konfig import blog.model.loginRoute import blog.model.notesRoute +import blog.model.tagRoute import blog.model.tasksRoute import blog.model.usersRoute import blog.module.authentication @@ -51,6 +52,7 @@ object Main { tasksRoute(processor, reader, scheduler, kfg) notesRoute(processor, reader, scheduler, kfg) info(reader) + tagRoute(processor, reader, scheduler, kfg) } }.start(wait = true) Behaviors.same() diff --git a/src/main/kotlin/blog/model/Note.kt b/src/main/kotlin/blog/model/Note.kt index 0701d5d..0f59b21 100644 --- a/src/main/kotlin/blog/model/Note.kt +++ b/src/main/kotlin/blog/model/Note.kt @@ -4,7 +4,7 @@ import java.time.ZonedDateTime import akka.Done import akka.actor.typed.ActorRef import akka.actor.typed.Scheduler -import akka.actor.typed.javadsl.AskPattern +import akka.actor.typed.javadsl.AskPattern.ask import akka.pattern.StatusReply import io.hypersistence.tsid.TSID import io.ktor.http.* @@ -30,28 +30,32 @@ data class CreateNote( val title: String, val body: String, val replyTo: ActorRef>, - val id: String = nextId() + val id: String = nextId ) : Command { - fun toEvent() = NoteCreated(id, user, title, body) + val toEvent get() = NoteCreated(id, user, title, body) } data class UpdateNote(val user: String, val id: String, val title: String?, val body: String?, val replyTo: ActorRef>): Command { - fun toEvent() = NoteUpdated(id, user, title, body) + val toEvent get() = NoteUpdated(id, user, title, body) } data class DeleteNote(val id: String, val rt: ActorRef>): Command { - fun toEvent() = NoteDeleted(id) + val toEvent get() = NoteDeleted(id) } data class NoteCreated(val id: String, val user: String, val title: String, val body: String) : Event { - fun toEntity() = Note(id, user, title, title.slug, body) - fun toResponse() = this.toEntity().toResponse() + val toEntity get() = Note(id, user, title, title.slug, body) + val toResponse get() = this.toEntity.toResponse() } -data class NoteUpdated(val id: String, val user: String, val title: String?, val body: String?): Event +data class NoteUpdated(val id: String, val user: String, val title: String?, val body: String?): Event { + val timestamp: ZonedDateTime get() = TSID.from(id).instant.atZone(CET) +} data class NoteDeleted(val id: String): Event +data class NoteDelta(val updated: ZonedDateTime, val what: String) + data class Note( override val id: String, val user: String, @@ -59,13 +63,14 @@ data class Note( val slug: String, val body: String, val created: ZonedDateTime = TSID.from(id).instant.atZone(CET), - val updated: ZonedDateTime = znow() + val updated: ZonedDateTime = znow, + val events: List = listOf() ): Entity { constructor(id: String, user: String, title: String, body: String): this(id, user, title, title.slug, body) fun update(nu: NoteUpdated): Note = this.copy( - title = nu.title ?: this.title, slug = nu.title?.slug ?: this.slug, body = nu.body ?: this.body, updated = znow() + title = nu.title ?: this.title, slug = nu.title?.slug ?: this.slug, body = nu.body ?: this.body, updated = znow, events = this.events + nu ) - fun toResponse() = NoteResponse(id, user, DTF.format(created), DTF.format(updated), title, slug, body) + fun toResponse() = NoteResponse(id, user, DTF.format(created), DTF.format(updated), title, slug, body, events.map { NoteDelta(it.timestamp, "???") }) override fun equals(other: Any?): Boolean = equals(this, other) override fun hashCode(): Int = id.hashCode() @@ -78,7 +83,8 @@ data class NoteResponse( val updated: String, val title: String, val slug: String, - val body: String + val body: String, + val deltas: List ) fun Route.notesRoute(processor: ActorRef, reader: Reader, scheduler: Scheduler, kfg: Konfig) = @@ -87,7 +93,7 @@ fun Route.notesRoute(processor: ActorRef, reader: Reader, scheduler: Sc post { val cnr = call.receive() val userId = userIdFromJWT(call) ?: return@post call.respond(HttpStatusCode.Unauthorized, "Unauthorized") - AskPattern.ask(processor, { rt -> cnr.toCommand(userId, rt) }, timeout, scheduler).await().let { + ask(processor, { rt -> cnr.toCommand(userId, rt) }, timeout, scheduler).await().let { if (it.isSuccess) { call.response.header("Location", "/api/notes/${it.value.id}") call.respond(HttpStatusCode.Created, it.value) @@ -109,7 +115,7 @@ fun Route.notesRoute(processor: ActorRef, reader: Reader, scheduler: Sc put { val unr = call.receive() val userId = userIdFromJWT(call) ?: return@put call.respond(HttpStatusCode.Unauthorized, "Unauthorized") - AskPattern.ask(processor, { rt -> unr.toCommand(userId, rt) }, timeout, scheduler).await().let { + ask(processor, { rt -> unr.toCommand(userId, rt) }, timeout, scheduler).await().let { when { it.isSuccess -> call.respond(HttpStatusCode.OK, it.value) else -> call.respond(HttpStatusCode.BadRequest, it.error.localizedMessage) @@ -119,7 +125,7 @@ fun Route.notesRoute(processor: ActorRef, reader: Reader, scheduler: Sc delete("{id?}") { val id = call.parameters["id"] ?: return@delete call.respond(HttpStatusCode.NotFound, "note id not specified") reader.findNote(id) ?: return@delete call.respond(HttpStatusCode.NotFound, "note not found for $id") - AskPattern.ask(processor, { rt -> DeleteNote(id, rt) }, timeout, scheduler).await().let { + ask(processor, { rt -> DeleteNote(id, rt) }, timeout, scheduler).await().let { when { it.isSuccess -> call.respond(HttpStatusCode.OK, mapOf("note" to id)) else -> call.respond(HttpStatusCode.InternalServerError, it.error.localizedMessage) diff --git a/src/main/kotlin/blog/model/State.kt b/src/main/kotlin/blog/model/State.kt index 3864c0b..bdc0d43 100644 --- a/src/main/kotlin/blog/model/State.kt +++ b/src/main/kotlin/blog/model/State.kt @@ -4,8 +4,8 @@ import java.io.Serializable data class State( private val users: Map = HashMap(9), - private val notes: Map = HashMap(9), - private val tasks: Map = HashMap(9), + private val notes: Map = HashMap(99), + private val tasks: Map = HashMap(99), private val tags : Map = HashMap(9), private val recovered: Boolean = false ): Serializable { @@ -28,5 +28,16 @@ data class State( fun taskCount() : Int = tasks.size fun deleteTask(id: String) : State = this.copy(tasks = this.tasks.minus(id)) + fun save(t: Tag) : State = this.copy(tags = this.tags.minus(t.id).plus(t.id to t)) + fun findTag(id: String) : Tag? = tags[id] + + inline fun find(id: String): T? = when (T::class) { + User::class -> findUser(id) as T? + Note::class -> findNote(id) as T? + Task::class -> findTask(id) as T? + Tag::class -> findTag(id) as T? + else -> null + } + fun setRecovered(): State = this.copy(recovered = true) } diff --git a/src/main/kotlin/blog/model/Tag.kt b/src/main/kotlin/blog/model/Tag.kt index 5e57664..f3a079b 100644 --- a/src/main/kotlin/blog/model/Tag.kt +++ b/src/main/kotlin/blog/model/Tag.kt @@ -1,22 +1,62 @@ package blog.model import akka.actor.typed.ActorRef +import akka.actor.typed.Scheduler +import akka.actor.typed.javadsl.AskPattern.ask import akka.pattern.StatusReply +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.auth.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import kotlinx.coroutines.future.await +import blog.config.Konfig +import blog.read.Reader -data class Tag(override val id: String, val label: String): Entity { +data class Tag(override val id: String, val label: String) : Entity { fun toResponse() = TagResponse(id, label) + override fun equals(other: Any?): Boolean = equals(this, other) + override fun hashCode(): Int = id.hashCode() } data class CreateTagRequest(val label: String) { - fun toCommand(replyTo: ActorRef>) = CreateTag(nextId(), label, replyTo) + fun toCommand(replyTo: ActorRef>) = CreateTag(nextId, label, replyTo) } -data class TagResponse(override val id: String, val label: String): Response +data class TagResponse(override val id: String, val label: String) : Response -data class CreateTag(val id: String, val label: String, val replyTo: ActorRef>): Command { - fun toEvent() = TagCreated(id, label) +data class CreateTag(val id: String, val label: String, val replyTo: ActorRef>) : Command { + val toEvent get() = TagCreated(id, label) } -data class TagCreated(val id: String, val label: String): Event { - fun toEntity() = Tag(id, label) +data class TagCreated(val id: String, val label: String) : Event { + val toEntity get() = Tag(id, label) +} + +fun Route.tagRoute(processor: ActorRef, reader: Reader, scheduler: Scheduler, kfg: Konfig) { + authenticate(kfg.jwt.realm) { + route("/api/tags") { + post { + val ctr = call.receive() + ask(processor, { rt -> ctr.toCommand(rt) }, timeout, scheduler).await().let { + if (it.isSuccess) { + call.response.header("Location", "/api/tags/${it.value.id}") + call.respond(HttpStatusCode.Created, it.value) + } else { + call.respond(HttpStatusCode.BadRequest, it.error.localizedMessage) + } + } + } + get { + call.respond(reader.allTags.map { it.toResponse() }) + } + get("{id?}") { + val id = call.parameters["id"] ?: return@get call.respond(HttpStatusCode.NotFound, "no tag id specified") + (reader.find(id) ?: return@get call.respond(HttpStatusCode.NotFound, "tag not found for $id")).let { + call.respond(it.toResponse()) + } + } + } + } } diff --git a/src/main/kotlin/blog/model/Task.kt b/src/main/kotlin/blog/model/Task.kt index cc77a3e..282be84 100644 --- a/src/main/kotlin/blog/model/Task.kt +++ b/src/main/kotlin/blog/model/Task.kt @@ -32,7 +32,7 @@ data class Task( val status: TaskStatus = TaskStatus.TODO, val private: Boolean = true, val created: ZonedDateTime = TSID.from(id).instant.atZone(CET), - val updated: ZonedDateTime = znow() + val updated: ZonedDateTime = znow ) : Entity { fun update(tu: TaskUpdated): Task = this.copy( title = tu.title ?: this.title, @@ -40,9 +40,10 @@ data class Task( body = tu.body ?: this.body, due = tu.due ?: this.due, status = tu.status ?: this.status, - updated = znow() + updated = znow ) - fun toResponse() = TaskResponse(id, created.fmt, updated.fmt, user, title, body, due.fmt, status.name) + + val toResponse get() = TaskResponse(id, created.fmt, updated.fmt, user, title, body, due.fmt, status.name) override fun equals(other: Any?): Boolean = equals(this, other) override fun hashCode(): Int = id.hashCode() } @@ -62,9 +63,9 @@ data class CreateTask( val body: String, val due: LocalDateTime, val replyTo: ActorRef>, - val id: String = nextId() + val id: String = nextId ) : Command { - fun toEvent() = TaskCreated(id, user, title, body, due) + val toEvent get() = TaskCreated(id, user, title, body, due) } data class UpdateTask( @@ -76,11 +77,11 @@ data class UpdateTask( val status: TaskStatus?, val replyTo: ActorRef> ) : Command { - fun toEvent() = TaskUpdated(user, id, title, body, due, status) + val toEvent get() = TaskUpdated(user, id, title, body, due, status) } data class DeleteTask(val id: String, val replyTo: ActorRef>): Command { - fun toEvent() = TaskDeleted(id) + val toEvent get() = TaskDeleted(id) } @@ -91,8 +92,8 @@ data class TaskCreated( val body: String, val due: LocalDateTime ) : Event { - fun toEntity() = Task(id, user, title, title.slug, body, due) - fun toResponse() = toEntity().toResponse() + val toEntity get() = Task(id, user, title, title.slug, body, due) + val toResponse get() = toEntity.toResponse } data class TaskUpdated( @@ -137,12 +138,12 @@ fun Route.tasksRoute(processor: ActorRef, reader: Reader, scheduler: Sc get { val rows = call.request.queryParameters["rows"]?.toInt() ?: 10 val start = call.request.queryParameters["start"]?.toInt() ?: 0 - call.respond(reader.allTasks(rows, start).map { it.toResponse() }) + call.respond(reader.allTasks(rows, start).map { it.toResponse }) } get("{id?}") { val id = call.parameters["id"] ?: return@get call.respond(HttpStatusCode.NotFound, "no task id specified") (reader.find(id) ?: return@get call.respond(HttpStatusCode.NotFound, "task not found for $id")).let { - call.respond(it.toResponse()) + call.respond(it.toResponse) } } put { diff --git a/src/main/kotlin/blog/model/User.kt b/src/main/kotlin/blog/model/User.kt index 4ad5c80..f0b2da5 100644 --- a/src/main/kotlin/blog/model/User.kt +++ b/src/main/kotlin/blog/model/User.kt @@ -46,11 +46,11 @@ data class CreateUser( val name: String, val password: String, val replyTo: ActorRef>, - val id: String = nextId(), + val id: String = nextId, ) : Command { constructor(rur: RegisterUserRequest, replyTo: ActorRef>) : this(rur.email, rur.name, rur.password, replyTo) - fun toEvent() = UserCreated(id, email, name.encode, password.hashed) + val toEvent get() = UserCreated(id, email, name.encode, password.hashed) } data class UpdateUser( @@ -59,7 +59,7 @@ data class UpdateUser( val password: String?, val replyTo: ActorRef>, ): Command { - fun toEvent() = UserUpdated(id, name, password?.hashed) + val toEvent get() = UserUpdated(id, name, password?.hashed) } // Events @@ -70,7 +70,7 @@ data class UserCreated( val name: String, val password: String, ) : Event { - fun toEntity(): User = User(id, email, name, password) + val toEntity: User get() = User(id, email, name, password) } data class UserUpdated( @@ -90,7 +90,7 @@ data class User( val password: String, val gravatar: String = email.gravatar, val joined: ZonedDateTime = TSID.from(id).instant.atZone(CET), - val updated: ZonedDateTime = znow() + val updated: ZonedDateTime = znow ) : Entity { fun toResponse(reader: Reader): UserResponse = UserResponse( id, @@ -102,7 +102,7 @@ data class User( reader.findTasksForUser(id).map(Task::toResponse) ) fun update(uu: UserUpdated): User = this.copy( - name = uu.name ?: this.name, password = uu.password ?: this.password, updated = znow() + name = uu.name ?: this.name, password = uu.password ?: this.password, updated = znow ) override fun hashCode(): Int = id.hashCode() override fun equals(other: Any?): Boolean = equals(this, other) @@ -148,7 +148,7 @@ fun Route.usersRoute(processor: ActorRef, reader: Reader, scheduler: Sc authenticate(kfg.jwt.realm) { get("/tasks") { val userId = userIdFromJWT(call) ?: return@get call.respondText("Unauthorized", status = Unauthorized) - call.respond(reader.findTasksForUser(userId).map { it.toResponse() }) + call.respond(reader.findTasksForUser(userId).map { it.toResponse }) } get("/me") { val id = userIdFromJWT(call) ?: return@get call.respondText("Unauthorized", status = Unauthorized) diff --git a/src/main/kotlin/blog/model/model.kt b/src/main/kotlin/blog/model/model.kt index c24ec89..3c6799b 100644 --- a/src/main/kotlin/blog/model/model.kt +++ b/src/main/kotlin/blog/model/model.kt @@ -40,16 +40,16 @@ val ZonedDateTime.fmt: String get() = DTF.format(this) val LocalDateTime.fmt: String get() = DTF.format(this) val CET: ZoneId = ZoneId.of("CET") -fun inow(): Instant = Instant.now() -fun znow(): ZonedDateTime = inow().atZone(CET) -fun now(): LocalDateTime = LocalDateTime.ofInstant(inow(), CET) +val inow: Instant get() = Instant.now() +val znow: ZonedDateTime get() = inow.atZone(CET) +val now: LocalDateTime get() = LocalDateTime.ofInstant(inow, CET) private val idFactory = TSID.Factory.builder() .withRandom(SecureRandom.getInstance("SHA1PRNG", "SUN")) .withNodeBits(8) .withNode(InetAddress.getLocalHost().address[3].toInt()and(0xFF)).build() -fun nextTSID(): TSID = idFactory.generate() -fun nextId(): String = nextTSID().toString() +val nextTSID: TSID get() = idFactory.generate() +val nextId: String get() = nextTSID.toString() fun slugify(s: String): String = s.trim().replace(" ", " ").lowercase().replace("[^ a-z0-9]".toRegex(), "").replace(' ', '-') val String.slug: String get() = slugify(this) diff --git a/src/main/kotlin/blog/module/Validation.kt b/src/main/kotlin/blog/module/Validation.kt index e678261..329b5e7 100644 --- a/src/main/kotlin/blog/module/Validation.kt +++ b/src/main/kotlin/blog/module/Validation.kt @@ -38,7 +38,7 @@ fun Application.validation(reader: Reader) { when { it.title.isBlank() -> ValidationResult.Invalid("title is blank") it.body.isBlank() -> ValidationResult.Invalid("body is blank") - it.due.isBefore(now()) -> ValidationResult.Invalid("due can not be in the passed") + it.due.isBefore(now) -> ValidationResult.Invalid("due can not be in the passed") else -> ValidationResult.Valid } } @@ -47,7 +47,7 @@ fun Application.validation(reader: Reader) { reader.notExistsTask(it.id) -> ValidationResult.Invalid("task not found for ${it.id}") it.title != null && it.title.isBlank() -> ValidationResult.Invalid("title is blank") it.body != null && it.body.isBlank() -> ValidationResult.Invalid("body is blank") - it.due != null && it.due.isBefore(now()) -> ValidationResult.Invalid("due can not be in the passed") + it.due != null && it.due.isBefore(now) -> ValidationResult.Invalid("due can not be in the passed") else -> ValidationResult.Valid } } diff --git a/src/main/kotlin/blog/read/Counts.kt b/src/main/kotlin/blog/read/Counts.kt index 44c19b6..a27df7f 100644 --- a/src/main/kotlin/blog/read/Counts.kt +++ b/src/main/kotlin/blog/read/Counts.kt @@ -3,5 +3,6 @@ package blog.read data class Counts( val users: Int, val notes: Int, - val tasks: Int + val tasks: Int, + val tags : Int ) diff --git a/src/main/kotlin/blog/read/Info.kt b/src/main/kotlin/blog/read/Info.kt index 66c42fa..83cb50d 100644 --- a/src/main/kotlin/blog/read/Info.kt +++ b/src/main/kotlin/blog/read/Info.kt @@ -9,14 +9,14 @@ import io.ktor.server.routing.* fun Route.info(reader: Reader) { route("/info") { get("/ready") { - if (reader.isReady()) { + if (reader.isReady) { call.respond(HttpStatusCode.OK, "Ready") } else { call.respond(HttpStatusCode.ServiceUnavailable, "Not Ready") } } get("/alive") { - if (reader.isReady()) { + if (reader.isReady) { call.respond(HttpStatusCode.OK, "Alive") } else { call.respond(HttpStatusCode.ServiceUnavailable, "Dead") diff --git a/src/main/kotlin/blog/read/Reader.kt b/src/main/kotlin/blog/read/Reader.kt index 224392d..9c9c8d2 100644 --- a/src/main/kotlin/blog/read/Reader.kt +++ b/src/main/kotlin/blog/read/Reader.kt @@ -7,6 +7,8 @@ import blog.model.Note import blog.model.NoteCreated import blog.model.NoteDeleted import blog.model.NoteUpdated +import blog.model.Tag +import blog.model.TagCreated import blog.model.Task import blog.model.TaskCreated import blog.model.TaskDeleted @@ -18,13 +20,12 @@ import blog.model.UserUpdated class Reader( private val users: MutableMap = ConcurrentHashMap(9), - private val tasks: MutableMap = ConcurrentHashMap(9), - private val notes: MutableMap = ConcurrentHashMap(9), + private val tasks: MutableMap = ConcurrentHashMap(99), + private val notes: MutableMap = ConcurrentHashMap(99), + private val tags: MutableMap = ConcurrentHashMap(9), private var serverReady: Boolean = false, private var recovered: Boolean = false, ) { - private val logger = LoggerFactory.getLogger("Reader") - fun findUser(id: String): User? = users[id] fun findUserByEmail(email: String): User? = users.values.find { it.email == email } fun exists(email: String): Boolean = findUserByEmail(email) != null @@ -32,13 +33,12 @@ class Reader( fun canNotAuthenticate(email: String, password: String): Boolean = !canAuthenticate(email, password) fun allUsers(rows: Int = 10, start: Int = 0): List = users.values.drop(start * rows).take(rows) - inline fun find(id: String): T? { - return when (T::class) { - User::class -> findUser(id) as T? - Note::class -> findNote(id) as T? - Task::class -> findTask(id) as T? - else -> throw IllegalArgumentException("Reader.find: Unsupported type: ${T::class}") - } + inline fun find(id: String): T? = when (T::class) { + User::class -> findUser(id) as T? + Note::class -> findNote(id) as T? + Task::class -> findTask(id) as T? + Tag::class -> findTag(id) as T? + else -> throw IllegalArgumentException("Reader.find: Unsupported type: ${T::class}") } fun findNote(id: String): Note? = notes[id] @@ -50,24 +50,32 @@ class Reader( fun findTasksForUser(id: String): List = tasks.values.filter { it.user == id } fun allTasks(rows: Int = 10, start: Int = 0): List = tasks.values.drop(start * rows).take(rows) + fun findTag(id: String): Tag? = tags[id] + val allTags: List get() = tags.values.toList() + fun setServerReady() { serverReady = true } fun setRecovered() { recovered = true } - fun isReady(): Boolean = serverReady && recovered + val isReady: Boolean get() = serverReady && recovered - fun counts(): Counts = Counts(users.size, notes.size, tasks.size) + fun counts(): Counts = Counts(users.size, notes.size, tasks.size, tags.size) fun processEvent(e: Event) { when (e) { - is UserCreated -> users[e.id] = e.toEntity() + is UserCreated -> users[e.id] = e.toEntity is UserUpdated -> if (users[e.id] != null) users[e.id] = users[e.id]!!.update(e) is UserDeleted -> users.remove(e.id) - is NoteCreated -> notes[e.id] = e.toEntity() + is NoteCreated -> notes[e.id] = e.toEntity is NoteUpdated -> if (notes[e.id] != null) notes[e.id] = notes[e.id]!!.update(e) is NoteDeleted -> notes.remove(e.id) - is TaskCreated -> tasks[e.id] = e.toEntity() + is TaskCreated -> tasks[e.id] = e.toEntity is TaskUpdated -> if (tasks[e.id] != null) tasks[e.id] = tasks[e.id]!!.update(e) is TaskDeleted -> tasks.remove(e.id) + is TagCreated -> tags[e.id] = e.toEntity else -> logger.warn("could not processEvent {}", e) } } + + companion object { + private val logger = LoggerFactory.getLogger("Reader") + } } diff --git a/src/main/kotlin/blog/write/Processor.kt b/src/main/kotlin/blog/write/Processor.kt index 7a33140..c5dab43 100644 --- a/src/main/kotlin/blog/write/Processor.kt +++ b/src/main/kotlin/blog/write/Processor.kt @@ -19,6 +19,7 @@ import org.slf4j.LoggerFactory import blog.model.Command import blog.model.CreateNote +import blog.model.CreateTag import blog.model.CreateTask import blog.model.CreateUser import blog.model.DeleteNote @@ -28,6 +29,8 @@ import blog.model.NoteCreated import blog.model.NoteDeleted import blog.model.NoteUpdated import blog.model.State +import blog.model.Tag +import blog.model.TagCreated import blog.model.TaskCreated import blog.model.TaskDeleted import blog.model.TaskUpdated @@ -56,18 +59,19 @@ class Processor(pid: PersistenceId, private val reader: Reader) : EventSourcedBe .onCommand(CreateTask::class.java, this::onCreateTask) .onCommand(UpdateTask::class.java, this::onUpdateTask) .onCommand(DeleteTask::class.java, this::onDeleteTask) + .onCommand(CreateTag::class.java, this::onCreateTag) .build() private fun onCreateUser(state: State, cmd: CreateUser): Effect = if (state.findUserByEmail(cmd.email) != null) { Effect().none().thenReply(cmd.replyTo) { StatusReply.error("${cmd.email} already registered") } } else { - cmd.toEvent().let { Effect().persist(it).thenReply(cmd.replyTo) { _ -> StatusReply.success(it.toEntity()) } } + cmd.toEvent.let { Effect().persist(it).thenReply(cmd.replyTo) { _ -> StatusReply.success(it.toEntity) } } } private fun onUpdateUser(state: State, cmd: UpdateUser): Effect = if (state.findUser(cmd.id) != null) { - cmd.toEvent().let { Effect().persist(it).thenReply(cmd.replyTo) { st -> StatusReply.success(st.findUser(it.id))} } + cmd.toEvent.let { Effect().persist(it).thenReply(cmd.replyTo) { st -> StatusReply.success(st.findUser(it.id))} } } else { Effect().none().thenReply(cmd.replyTo) { StatusReply.error("User with id ${cmd.id} not found") } } @@ -76,14 +80,14 @@ class Processor(pid: PersistenceId, private val reader: Reader) : EventSourcedBe if (state.findUser(cmd.user) == null) { Effect().none().thenReply(cmd.replyTo) { StatusReply.error("User ${cmd.user} not found") } } else { - cmd.toEvent().let { Effect().persist(it).thenReply(cmd.replyTo) { _ -> StatusReply.success(it.toResponse()) } } + cmd.toEvent.let { Effect().persist(it).thenReply(cmd.replyTo) { _ -> StatusReply.success(it.toResponse) } } } private fun onUpdateNote(state: State, cmd: UpdateNote): Effect = if (state.findNote(cmd.id) == null) { Effect().none().thenReply(cmd.replyTo) { StatusReply.error("Note with id ${cmd.id} not found for user with id ${cmd.user}") } } else { - cmd.toEvent().let { + cmd.toEvent.let { Effect().persist(it).thenReply(cmd.replyTo) { st -> StatusReply.success(st.findNote(it.id)?.toResponse()) } } } @@ -92,15 +96,15 @@ class Processor(pid: PersistenceId, private val reader: Reader) : EventSourcedBe if (state.findNote(cmd.id) == null) { Effect().none().thenReply(cmd.rt) { StatusReply.error("Note with id ${cmd.id} not found") } } else { - Effect().persist(cmd.toEvent()).thenReply(cmd.rt) { _ -> StatusReply.success(Done.done()) } + Effect().persist(cmd.toEvent).thenReply(cmd.rt) { _ -> StatusReply.success(Done.done()) } } private fun onCreateTask(state: State, cmd: CreateTask): Effect = if (state.findUser(cmd.user) == null) { Effect().none().thenReply(cmd.replyTo) { StatusReply.error("User ${cmd.user} not found") } } else { - cmd.toEvent().let { - Effect().persist(it).thenReply(cmd.replyTo) { _ -> StatusReply.success(it.toResponse()) } + cmd.toEvent.let { + Effect().persist(it).thenReply(cmd.replyTo) { _ -> StatusReply.success(it.toResponse) } } } @@ -108,8 +112,8 @@ class Processor(pid: PersistenceId, private val reader: Reader) : EventSourcedBe if (state.findTask(cmd.id) == null) { Effect().none().thenReply(cmd.replyTo) { StatusReply.error("Task with id ${cmd.id} not found for user with id ${cmd.user}") } } else { - cmd.toEvent().let { - Effect().persist(it).thenReply(cmd.replyTo) { st -> StatusReply.success(st.findTask(it.id)?.toResponse()) } + cmd.toEvent.let { + Effect().persist(it).thenReply(cmd.replyTo) { st -> StatusReply.success(st.findTask(it.id)?.toResponse) } } } @@ -117,20 +121,28 @@ class Processor(pid: PersistenceId, private val reader: Reader) : EventSourcedBe if (state.findTask(cmd.id) == null) { Effect().none().thenReply(cmd.replyTo) { StatusReply.error("Task ${cmd.id} not found") } } else { - Effect().persist(cmd.toEvent()).thenReply(cmd.replyTo) { StatusReply.success(Done.done()) } + Effect().persist(cmd.toEvent).thenReply(cmd.replyTo) { StatusReply.success(Done.done()) } + } + + private fun onCreateTag(state: State, cmd: CreateTag): Effect = + if (state.find(cmd.id) == null) { + Effect().persist(cmd.toEvent).thenReply(cmd.replyTo) { _ -> StatusReply.success(cmd.toEvent.toEntity.toResponse()) } + } else { + Effect().none().thenReply(cmd.replyTo) { StatusReply.error("Tag ${cmd.label} could not be created") } } override fun eventHandler(): EventHandler = newEventHandlerBuilder() .forAnyState() - .onEvent(UserCreated::class.java) { state, event -> state.save(event.toEntity()).also { reader.processEvent(event) } } + .onEvent(UserCreated::class.java) { state, event -> state.save(event.toEntity).also { reader.processEvent(event) } } .onEvent(UserUpdated::class.java) { state, event -> (state.findUser(event.id)?.let { state.save(it.update(event)) } ?: state).also { reader.processEvent(event) }} .onEvent(UserDeleted::class.java) { state, event -> state.deleteUser(event.id).also { reader.processEvent(event) } } - .onEvent(NoteCreated::class.java) { state, event -> state.save(event.toEntity()).also { reader.processEvent(event) } } + .onEvent(NoteCreated::class.java) { state, event -> state.save(event.toEntity).also { reader.processEvent(event) } } .onEvent(NoteUpdated::class.java) { state, event -> (state.findNote(event.id)?.let { state.save(it.update(event)) } ?: state).also { reader.processEvent(event) }} .onEvent(NoteDeleted::class.java) { state, event -> state.deleteNote(event.id).also { reader.processEvent(event) } } - .onEvent(TaskCreated::class.java) { state, event -> state.save(event.toEntity()).also { reader.processEvent(event) } } + .onEvent(TaskCreated::class.java) { state, event -> state.save(event.toEntity).also { reader.processEvent(event) } } .onEvent(TaskUpdated::class.java) { state, event -> (state.findTask(event.id)?.let { state.save(it.update(event)) } ?: state).also { reader.processEvent(event) }} .onEvent(TaskDeleted::class.java) { state, event -> state.deleteTask(event.id).also { reader.processEvent(event) } } + .onEvent(TagCreated::class.java) { state, event -> state.save(event.toEntity).also { reader.processEvent(event) } } .build() override fun signalHandler(): SignalHandler = newSignalHandlerBuilder().onSignal(RecoveryCompleted::class.java, this::recovered).build() diff --git a/src/test/kotlin/blog/model/ModelTests.kt b/src/test/kotlin/blog/model/ModelTests.kt index 1365409..4bd78dc 100644 --- a/src/test/kotlin/blog/model/ModelTests.kt +++ b/src/test/kotlin/blog/model/ModelTests.kt @@ -35,7 +35,7 @@ class ModelTests { @Test fun equals() { - val n = Note(nextId(), nextId(), "Title", "Body") + val n = Note(nextId, nextId, "Title", "Body") assertThat(equals(n, n)).isTrue assertThat(equals(n, null)).isFalse assertThat(equals(n, "not a note")).isFalse @@ -46,7 +46,7 @@ class ModelTests { assertThat(n.equals("not a note")).isFalse assertThat(n.equals(true)).isFalse - val o = Note(n.id, nextId(), "Title O", "Body O") + val o = Note(n.id, nextId, "Title O", "Body O") assertThat(equals(o, o)).isTrue assertThat(equals(n, o)).isTrue assertThat(equals(o, n)).isTrue @@ -54,11 +54,4 @@ class ModelTests { assertThat(o == n).isTrue assertThat(n == o).isTrue } - - @Test - fun nextIds() { - val tsid = nextTSID() - tsid.hashCode() - - } } diff --git a/src/test/kotlin/blog/model/NoteTests.kt b/src/test/kotlin/blog/model/NoteTests.kt index 927aab6..0236d30 100644 --- a/src/test/kotlin/blog/model/NoteTests.kt +++ b/src/test/kotlin/blog/model/NoteTests.kt @@ -18,7 +18,7 @@ class NoteTests { private val probeNoteRes = testKit.createTestProbe>().ref private val probeNoteDel = testKit.createTestProbe>().ref - private val userId = nextId() + private val userId = nextId @Test fun `create request to command to event to entity`() { @@ -30,13 +30,13 @@ class NoteTests { assertThat(cn.title).isEqualTo("title") assertThat(cn.body).isEqualTo("body") - val nc = cn.toEvent() + val nc = cn.toEvent assertThat(nc.id).isNotNull assertThat(nc.user).isEqualTo(userId) assertThat(nc.title).isEqualTo("title") assertThat(nc.body).isEqualTo("body") - val note = nc.toEntity() + val note = nc.toEntity assertThat(note.id).isNotNull() assertThat(note.user).isEqualTo(userId) assertThat(note.title).isEqualTo("title") @@ -45,7 +45,7 @@ class NoteTests { @Test fun `update request to command to event`() { - val run = UpdateNoteRequest(nextId(), "title", "body") + val run = UpdateNoteRequest(nextId, "title", "body") val un: UpdateNote = run.toCommand(userId, probeNoteRes) assertThat(un.id).isNotNull @@ -53,13 +53,13 @@ class NoteTests { assertThat(un.title).isEqualTo("title") assertThat(un.body).isEqualTo("body") - val nu: NoteUpdated = un.toEvent() + val nu: NoteUpdated = un.toEvent assertThat(nu.id).isNotNull assertThat(nu.user).isEqualTo(userId) assertThat(nu.title).isEqualTo("title") assertThat(nu.body).isEqualTo("body") - val note = Note(nextId(), userId, "Title", "Body") + val note = Note(nextId, userId, "Title", "Body") val updated = note.update(nu) assertThat(updated.id).isEqualTo(note.id) assertThat(updated.user).isEqualTo(userId) @@ -69,8 +69,8 @@ class NoteTests { @Test fun `delete command to event`() { - val dn = DeleteNote(nextId(), probeNoteDel) - val de: NoteDeleted = dn.toEvent() + val dn = DeleteNote(nextId, probeNoteDel) + val de: NoteDeleted = dn.toEvent assertThat(de.id).isNotNull } } diff --git a/src/test/kotlin/blog/model/StateTests.kt b/src/test/kotlin/blog/model/StateTests.kt index abb4a38..9838fba 100644 --- a/src/test/kotlin/blog/model/StateTests.kt +++ b/src/test/kotlin/blog/model/StateTests.kt @@ -11,7 +11,7 @@ class StateTests { @Test fun `new user`() { val state = State() - val user = User(nextId(), "test@tester.nl", "Tester", pw) + val user = User(nextId, "test@tester.nl", "Tester", pw) val state2 = state.save(user) assertThat(state2.userCount()).isEqualTo(1) @@ -19,7 +19,7 @@ class StateTests { assertThat(found).isNotNull assertThat(found?.name).isEqualTo("Tester") - val notFound = state2.findUser(nextId()) + val notFound = state2.findUser(nextId) assertThat(notFound).isNull() assertThat(state2.allUsers()).hasSize(1) @@ -28,7 +28,7 @@ class StateTests { @Test fun `update user`() { val state = State() - val user = User(nextId(), "test@tester.nl", "Tester", pw) + val user = User(nextId, "test@tester.nl", "Tester", pw) val state2 = state.save(user) assertThat(state2.userCount()).isEqualTo(1) } @@ -36,8 +36,8 @@ class StateTests { @Test fun `new note`() { val state = State() - val user = User(nextId(), "test@tester.nl", "Tester", pw) - val note = Note(nextId(), user.id, "Test", "Testing, 1.. 2..") + val user = User(nextId, "test@tester.nl", "Tester", pw) + val note = Note(nextId, user.id, "Test", "Testing, 1.. 2..") val state2 = state.save(user) val state3 = state2.save(note) assertThat(state3.userCount()).isEqualTo(1) @@ -50,8 +50,8 @@ class StateTests { @Test fun `update note`() { val state = State() - val user = User(nextId(), "test@tester.nl", "Tester", pw) - val note = Note(nextId(), user.id, "Test", "Testing, 1.. 2..") + val user = User(nextId, "test@tester.nl", "Tester", pw) + val note = Note(nextId, user.id, "Test", "Testing, 1.. 2..") val state2 = state.save(user) val state3 = state2.save(note) assertThat(state3.userCount()).isEqualTo(1) @@ -73,9 +73,9 @@ class StateTests { @Test fun `new task`() { val state = State() - val user = User(nextId(), "test@tester.nl", "Tester", pw) + val user = User(nextId, "test@tester.nl", "Tester", pw) val state2 = state.save(user) - val task = Task(nextId(), user.id, "Test", "test", "Tasking, 1.. 2..", LocalDateTime.now().plusHours(1)) + val task = Task(nextId, user.id, "Test", "test", "Tasking, 1.. 2..", LocalDateTime.now().plusHours(1)) val state3 = state2.save(task) assertThat(state3.taskCount()).isEqualTo(1) assertThat(state3.findTask(task.id)).isNotNull diff --git a/src/test/kotlin/blog/model/TagTests.kt b/src/test/kotlin/blog/model/TagTests.kt new file mode 100644 index 0000000..5dbfa1b --- /dev/null +++ b/src/test/kotlin/blog/model/TagTests.kt @@ -0,0 +1,46 @@ +package blog.model + +import java.util.UUID +import akka.actor.testkit.typed.javadsl.TestKitJunitResource +import akka.pattern.StatusReply +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.Test + + +class TagTests { + + private val testKit = TestKitJunitResource( + """akka.persistence.journal.plugin = "akka.persistence.journal.inmem" + akka.persistence.snapshot-store.plugin = "akka.persistence.snapshot-store.local" + akka.persistence.snapshot-store.local.dir = "build/snapshot-${UUID.randomUUID()}" + """ + ) + private val probe = testKit.createTestProbe>().ref + + @Test + fun `request to command to event to entity to response`() { + val ctr = CreateTagRequest("test label") + val ct = ctr.toCommand(probe) + assertThat(ct).isNotNull + assertThat(ct.id).isNotNull + assertThat(ct.label).isEqualTo("test label").isEqualTo(ctr.label) + + val tc = ct.toEvent + assertThat(tc.id).isEqualTo(ct.id) + assertThat(tc.label).isEqualTo(ct.label).isEqualTo(ctr.label) + + val tag = tc.toEntity + assertThat(tag.id).isEqualTo(tc.id).isEqualTo(ct.id) + assertThat(tag.label).isEqualTo(tc.label).isEqualTo(ct.label).isEqualTo(ctr.label) + + val tr = tag.toResponse() + assertThat(tr.id).isEqualTo(tc.id).isEqualTo(ct.id).isEqualTo(tag.id) + assertThat(tr.label).isEqualTo(tc.label).isEqualTo(ct.label).isEqualTo(ctr.label).isEqualTo(tag.label) + } + + @AfterAll + fun cleanup() { + testKit.system().terminate() + } +} diff --git a/src/test/kotlin/blog/model/TaskTests.kt b/src/test/kotlin/blog/model/TaskTests.kt index 6281a45..956dc3f 100644 --- a/src/test/kotlin/blog/model/TaskTests.kt +++ b/src/test/kotlin/blog/model/TaskTests.kt @@ -17,11 +17,11 @@ class TaskTests { """ ) private val probeTaskRes = testKit.createTestProbe>().ref - private val userId = nextId() + private val userId = nextId @Test fun `create task request, command, event, entity and response`() { - val ctr = CreateTaskRequest("title", "body", now().plusDays(2)) + val ctr = CreateTaskRequest("title", "body", now.plusDays(2)) val ct = ctr.toCommand(userId, probeTaskRes) assertThat(ct.id).isNotNull assertThat(ct.title).isEqualTo("title").isEqualTo(ctr.title) @@ -29,21 +29,21 @@ class TaskTests { assertThat(ct.due).isAfter(LocalDateTime.now()).isEqualTo(ctr.due) assertThat(ct.user).isEqualTo(userId) - val tc = ct.toEvent() + val tc = ct.toEvent assertThat(tc.id).isEqualTo(ct.id) assertThat(tc.title).isEqualTo("title").isEqualTo(ct.title).isEqualTo(ctr.title) assertThat(tc.body).isEqualTo("body").isEqualTo(ct.body).isEqualTo(ctr.body) assertThat(tc.due).isEqualTo(ct.due).isEqualTo(ctr.due).isAfter(LocalDateTime.now()) assertThat(tc.user).isEqualTo(userId).isEqualTo(ct.user) - val task = tc.toEntity() + val task = tc.toEntity assertThat(task.id).isEqualTo(tc.id) assertThat(task.title).isEqualTo("title").isEqualTo(tc.title).isEqualTo(ct.title).isEqualTo(ctr.title) assertThat(task.body).isEqualTo("body").isEqualTo(tc.body).isEqualTo(ct.body).isEqualTo(ctr.body) assertThat(task.due).isEqualTo(tc.due).isEqualTo(ct.due).isEqualTo(ctr.due).isAfter(LocalDateTime.now()) assertThat(task.user).isEqualTo(userId).isEqualTo(tc.user).isEqualTo(ct.user) - val res = task.toResponse() + val res = task.toResponse assertThat(res.id).isEqualTo(TSID.from(task.id).toString()) assertThat(res.title).isEqualTo(task.title) assertThat(res.body).isEqualTo(task.body) @@ -53,8 +53,8 @@ class TaskTests { @Test fun `update task request, command and event`() { - val taskId = nextId() - val utr = UpdateTaskRequest(TSID.from(taskId).toString(), "new title", "new body", now().plusDays(3), TaskStatus.DOING) + val taskId = nextId + val utr = UpdateTaskRequest(TSID.from(taskId).toString(), "new title", "new body", now.plusDays(3), TaskStatus.DOING) val ut = utr.toCommand(userId, probeTaskRes) assertThat(ut.id).isEqualTo(taskId) assertThat(ut.user).isEqualTo(userId) @@ -63,8 +63,8 @@ class TaskTests { assertThat(ut.due).isEqualTo(utr.due).isAfter(LocalDateTime.now()) assertThat(ut.status).isEqualTo(TaskStatus.DOING) - val task = Task(taskId, userId, "title", "title".slug, "body", now().plusDays(1), TaskStatus.REVIEW) - var updated = task.update(ut.toEvent()) + val task = Task(taskId, userId, "title", "title".slug, "body", now.plusDays(1), TaskStatus.REVIEW) + var updated = task.update(ut.toEvent) assertThat(updated.title).isEqualTo("new title") assertThat(updated.body).isEqualTo("new body") diff --git a/src/test/kotlin/blog/model/UserTests.kt b/src/test/kotlin/blog/model/UserTests.kt index fa3c04e..36f8b24 100644 --- a/src/test/kotlin/blog/model/UserTests.kt +++ b/src/test/kotlin/blog/model/UserTests.kt @@ -35,7 +35,7 @@ class UserTests { @Test fun `command to event`() { val c = CreateUser(email, name, password, probe) - val e = c.toEvent() + val e = c.toEvent assertThat(e.email).isEqualTo(c.email) assertThat(e.name).isEqualTo(c.name) assertThat(e.id).isNotNull @@ -44,8 +44,8 @@ class UserTests { @Test fun `event to entity`() { - val uc = UserCreated(nextId(), email, name, password) - val u = uc.toEntity() + val uc = UserCreated(nextId, email, name, password) + val u = uc.toEntity assertThat(u.id).isEqualTo(uc.id) assertThat(u.email).isEqualTo(uc.email) assertThat(u.name).isEqualTo(uc.name) @@ -55,23 +55,23 @@ class UserTests { @Test fun `update entity`() { - val u = User(nextId(), email, name, password.hashed) + val u = User(nextId, email, name, password.hashed) var uu = UpdateUser(u.id, "Anders", null, probe) - var v = u.update(uu.toEvent()) + var v = u.update(uu.toEvent) assertThat(v.id).isEqualTo(u.id) assertThat(v.email).isEqualTo(u.email).isEqualTo(email) assertThat(v.name).isEqualTo("Anders") assertThat(v.password).isEqualTo(u.password).isEqualTo(password.hashed) uu = UpdateUser(u.id, null, "anders!?", probe) - v = u.update(uu.toEvent()) + v = u.update(uu.toEvent) assertThat(v.id).isEqualTo(u.id) assertThat(v.email).isEqualTo(u.email).isEqualTo(email) assertThat(v.name).isEqualTo(u.name).isEqualTo(name) assertThat(v.password).isNotEqualTo(u.password).isEqualTo("anders!?".hashed) uu = UpdateUser(u.id, null, null, probe) - v = u.update(uu.toEvent()) + v = u.update(uu.toEvent) assertThat(v.id).isEqualTo(u.id) assertThat(v.email).isEqualTo(u.email).isEqualTo(email) assertThat(v.name).isEqualTo(u.name).isEqualTo(name) diff --git a/src/test/kotlin/blog/read/ReaderTests.kt b/src/test/kotlin/blog/read/ReaderTests.kt index 2217b8d..bae54f4 100644 --- a/src/test/kotlin/blog/read/ReaderTests.kt +++ b/src/test/kotlin/blog/read/ReaderTests.kt @@ -13,7 +13,7 @@ import blog.model.Task class ReaderTests { - private val userId = nextId() + private val userId = nextId @Test fun `new user`() { @@ -22,7 +22,7 @@ class ReaderTests { assertThat(reader.allUsers()).hasSize(1) assertThat(reader.findUser(userId)).isNotNull - reader.processEvent(UserCreated(nextId(), "break@tester.nl", "Breaker", "Welkom124!")) + reader.processEvent(UserCreated(nextId, "break@tester.nl", "Breaker", "Welkom124!")) assertThat(reader.allUsers()).hasSize(2) val user: User? = reader.find(userId) @@ -34,7 +34,7 @@ class ReaderTests { @Test fun `new note`() { val reader = Reader() - val noteId = nextId() + val noteId = nextId reader.processEvent(NoteCreated(noteId, userId, "title", "body")) assertThat(reader.allNotes()).hasSize(1) assertThat(reader.findNotesForUser(userId)).hasSize(1) @@ -46,7 +46,7 @@ class ReaderTests { @Test fun `new task`() { val reader = Reader() - val taskId = nextId() + val taskId = nextId reader.processEvent(TaskCreated(taskId, userId, "title", "body", LocalDateTime.now().plusHours(4))) assertThat(reader.allTasks()).hasSize(1) assertThat(reader.findTasksForUser(userId)).hasSize(1) @@ -59,11 +59,11 @@ class ReaderTests { reader.processEvent(UserCreated(userId, "test@tester.nl", "Tester", "Welkom123!")) assertThat(reader.allUsers()).hasSize(1) - reader.processEvent(NoteCreated(nextId(), userId, "title", "body")) + reader.processEvent(NoteCreated(nextId, userId, "title", "body")) assertThat(reader.allNotes()).hasSize(1) assertThat(reader.findNotesForUser(userId)).hasSize(1) - reader.processEvent(TaskCreated(nextId(), userId, "title", "body", LocalDateTime.now().plusHours(4))) + reader.processEvent(TaskCreated(nextId, userId, "title", "body", LocalDateTime.now().plusHours(4))) assertThat(reader.allTasks()).hasSize(1) assertThat(reader.findTasksForUser(userId)).hasSize(1) }