Fix tests

This commit is contained in:
Paul-Henri Froidmont 2025-10-13 15:46:22 +02:00
parent efdc50eb1d
commit 87bd780f9f
Signed by: phfroidmont
GPG key ID: BE948AFD7E7873BE
34 changed files with 230 additions and 303 deletions

View file

@ -3,15 +3,13 @@ package lu.foyer
import zio.*
import zio.schema.Schema
import java.util.UUID
final case class Entity[T](entityId: String, data: T, version: Long)
final case class Event[T](entityId: String, data: T, eventId: String)
final case class Entity[T](entityId: UUID, data: T, version: Long)
final case class Event[T](entityId: UUID, data: T, eventId: UUID)
trait StateRepository[Data] extends Repository[Entity[Data], UUID]
trait EventRepository[Data] extends Repository[Event[Data], UUID]:
def fetchOne(entityId: UUID, eventId: UUID): Task[Option[Event[Data]]]
def fetchMany(entityId: UUID, page: Page): Task[Paged[Event[Data]]]
trait StateRepository[Data] extends Repository[Entity[Data], String]
trait EventRepository[Data] extends Repository[Event[Data], String]:
def fetchOne(entityId: String, eventId: String): Task[Option[Event[Data]]]
def fetchMany(entityId: String, page: Page): Task[Paged[Event[Data]]]
trait Reducer[Event, State]:
def fromEmpty: PartialFunction[Event, State]
@ -30,13 +28,13 @@ trait CommandHandler[+Command, +Event, +State]:
def commandSchema: Schema[?]
trait CommandHandlerCreate[Command: Schema, Event] extends CommandHandler[Command, Event, Nothing]:
def onCommand(entityId: UUID, command: Command): Task[Event]
def onCommand(entityId: String, command: Command): Task[Event]
val isCreate = true
val commandSchema = summon[Schema[Command]]
trait CommandHandlerUpdate[Command: Schema, Event, State]
extends CommandHandler[Command, Event, State]:
def onCommand(entityId: UUID, state: State, command: Command): Task[Event]
def onCommand(entityId: String, state: State, command: Command): Task[Event]
val isCreate = false
val commandSchema = summon[Schema[Command]]
@ -46,7 +44,7 @@ class CommandEngine[Command, Event, State](
val eventRepo: EventRepository[Event],
val stateRepo: StateRepository[State]):
def handleCommand(command: Command, name: String, entityId: UUID)
def handleCommand(command: Command, name: String, entityId: String)
: Task[(lu.foyer.Event[Event], lu.foyer.Entity[State])] =
for
handler <- ZIO
@ -61,14 +59,14 @@ class CommandEngine[Command, Event, State](
newEntity = Entity(entityId, newState, entityOption.map(_.version).getOrElse(1))
_ <- if entityOption.isEmpty then stateRepo.insert(newEntity.entityId, newEntity)
else stateRepo.update(newEntity.entityId, newEntity)
eventEntity <- Random.nextUUID.map(Event(newEntity.entityId, event, _))
eventEntity <- Random.nextUUID.map(id => Event(newEntity.entityId, event, id.toString))
_ <- eventRepo.insert(eventEntity.eventId, eventEntity)
yield (eventEntity, newEntity)
private def transition(
command: Command,
name: String,
entityId: UUID,
entityId: String,
entityOption: Option[Entity[State]],
handler: CommandHandler[Command, Event, State]
): Task[(Event, Option[State])] = (entityOption, handler) match

View file

@ -4,8 +4,6 @@ import zio.*
import zio.schema.*
import zio.schema.annotation.fieldName
import java.util.UUID
final case class Page(
@fieldName("page[number]")
number: Option[Int],
@ -14,6 +12,9 @@ final case class Page(
@fieldName("page[totals]")
totals: Option[Boolean])
derives Schema
object Page:
val default = Page(None, None, totals = Some(true))
final case class Paged[T](items: List[T], totals: Option[Long])
trait Repository[Entity, Id]:
@ -22,15 +23,16 @@ trait Repository[Entity, Id]:
def insert(id: Id, entity: Entity): Task[Unit]
def update(id: Id, entity: Entity): Task[Unit]
trait InMemoryRepository[State](entities: Ref[Map[UUID, State]]) extends Repository[State, UUID]:
def fetchOne(id: UUID): Task[Option[State]] = entities.get.map(_.get(id))
trait InMemoryRepository[State](entities: Ref[Map[String, State]])
extends Repository[State, String]:
def fetchOne(id: String): Task[Option[State]] = entities.get.map(_.get(id))
def fetchMany(page: Page): Task[Paged[State]] = entities.get.map(entities =>
val items =
entities.values
.drop(page.number.getOrElse(0) * page.size.getOrElse(50)).take(page.size.getOrElse(50))
Paged(items.toList, if page.totals.getOrElse(false) then Some(entities.size) else None)
Paged(items.toList, if page.totals.getOrElse(true) then Some(entities.size) else None)
)
def insert(id: UUID, entity: State): Task[Unit] =
def insert(id: String, entity: State): Task[Unit] =
entities.update(entities => entities.updated(id, entity))
def update(id: UUID, entity: State): Task[Unit] =
def update(id: String, entity: State): Task[Unit] =
entities.update(entities => entities.updated(id, entity))

View file

@ -3,8 +3,6 @@ package clients
import zio.schema.*
import java.time.LocalDate
enum ClientCommand derives Schema:
case Create(
lastName: ClientLastName,

View file

@ -5,8 +5,6 @@ import zio.schema.*
import zio.schema.annotation.caseName
import zio.schema.annotation.discriminatorName
import java.time.LocalDate
@discriminatorName("eventType")
sealed trait ClientEvent derives Schema
object ClientEvent:

View file

@ -2,7 +2,6 @@ package lu.foyer
package clients
import zio.*
import java.util.UUID
object ClientHandlers:
val layer: ULayer[List[CommandHandler[ClientCommand, ClientEvent, ClientState]]] =
@ -16,7 +15,7 @@ object ClientHandlers:
object CreateHandler extends CommandHandlerCreate[ClientCommand.Create, ClientEvent.Created]:
val name = "create"
def onCommand(entityId: UUID, command: ClientCommand.Create): Task[ClientEvent.Created] =
def onCommand(entityId: String, command: ClientCommand.Create): Task[ClientEvent.Created] =
ZIO.succeed(
ClientEvent.Created(
command.lastName,
@ -32,7 +31,7 @@ object CreateHandler extends CommandHandlerCreate[ClientCommand.Create, ClientEv
object UpdateHandler
extends CommandHandlerUpdate[ClientCommand.Update, ClientEvent.Updated, ClientState.Actif]:
val name = "update"
def onCommand(entityId: UUID, state: ClientState.Actif, command: ClientCommand.Update)
def onCommand(entityId: String, state: ClientState.Actif, command: ClientCommand.Update)
: Task[ClientEvent.Updated] =
ZIO.succeed(
ClientEvent.Updated(
@ -49,6 +48,6 @@ object UpdateHandler
object DisableHandler
extends CommandHandlerUpdate[ClientCommand.Disable, ClientEvent.Disabled, ClientState.Actif]:
val name = "disable"
def onCommand(entityId: UUID, state: ClientState.Actif, command: ClientCommand.Disable)
def onCommand(entityId: String, state: ClientState.Actif, command: ClientCommand.Disable)
: Task[ClientEvent.Disabled] =
ZIO.succeed(ClientEvent.Disabled(command.reason))

View file

@ -10,7 +10,7 @@ class ClientReducer() extends Reducer[ClientEvent, ClientState]:
override val fromState =
case (s: ClientState.Actif, e: ClientEvent.Updated) => s.update(e)
case (s: ClientState.Actif, e: ClientEvent.Disabled) => s.disable(e)
case (s: ClientState.Actif, _: ClientEvent.Disabled) => s.disable()
object ClientReducer:
val layer: ULayer[Reducer[ClientEvent, ClientState]] = ZLayer.succeed(ClientReducer())

View file

@ -5,8 +5,6 @@ import zio.schema.*
import zio.schema.annotation.caseName
import zio.schema.annotation.discriminatorName
import java.time.LocalDate
@discriminatorName("statusType")
sealed trait ClientState derives Schema
object ClientState:
@ -32,7 +30,7 @@ object ClientState:
e.email.orElse(email),
e.address.orElse(address)
)
def disable(e: ClientEvent.Disabled) =
def disable() =
ClientState.Inactif(
lastName,
firstName,

View file

@ -3,9 +3,6 @@ package contracts
import zio.schema.*
import java.time.LocalDate
import java.util.UUID
enum ContractCommand derives Schema:
case Subscribe(
product: ProductType,

View file

@ -5,8 +5,6 @@ import zio.schema.*
import zio.schema.annotation.caseName
import zio.schema.annotation.discriminatorName
import java.time.LocalDate
@discriminatorName("eventType")
sealed trait ContractEvent derives Schema
object ContractEvent:

View file

@ -1,9 +1,9 @@
package lu.foyer
package contracts
import lu.foyer.clients.*
import zio.*
import java.util.UUID
import lu.foyer.clients.*
object ContractHandlers:
val layer =
@ -29,7 +29,7 @@ class SubscribeHandler(
val name = "create"
def onCommand(entityId: UUID, command: ContractCommand.Subscribe)
def onCommand(entityId: String, command: ContractCommand.Subscribe)
: Task[ContractEvent.Subscribed] =
for
holder <- clientStateRepo.fetchOne(command.holder).someOrFailException
@ -39,7 +39,6 @@ class SubscribeHandler(
ZIO.succeed(address)
case _ =>
ZIO.fail(new IllegalArgumentException(s"No active holder found for ${command.holder}"))
premium = premiumService.computePremium(command.formula, command.vehicle, residentialAddress)
premium <-
ZIO
.fromEither(
@ -60,7 +59,7 @@ class AmendHandler(clientStateRepo: StateRepository[ClientState], premiumService
val name = "amend"
def onCommand(entityId: UUID, state: ContractState, command: ContractCommand.Amend)
def onCommand(entityId: String, state: ContractState, command: ContractCommand.Amend)
: Task[ContractEvent.Amended] =
for
holder <- clientStateRepo.fetchOne(state.holder).someOrFailException
@ -96,7 +95,7 @@ class ApproveHandler(employeeService: EmployeeService)
val name = "approve"
def onCommand(entityId: UUID, state: ContractState.Pending, command: ContractCommand.Approve)
def onCommand(entityId: String, state: ContractState.Pending, command: ContractCommand.Approve)
: Task[ContractEvent.Approved] =
for
user <- ZIO.succeed("") // TODO current user
@ -114,7 +113,7 @@ class RejectHandler(employeeService: EmployeeService)
val name = "reject"
def onCommand(entityId: UUID, state: ContractState.Pending, command: ContractCommand.Reject)
def onCommand(entityId: String, state: ContractState.Pending, command: ContractCommand.Reject)
: Task[ContractEvent.Rejected] =
for
user <- ZIO.succeed("") // TODO current user
@ -132,6 +131,6 @@ class TerminateHandler()
val name = "reject"
def onCommand(entityId: UUID, state: ContractState, command: ContractCommand.Terminate)
def onCommand(entityId: String, state: ContractState, command: ContractCommand.Terminate)
: Task[ContractEvent.Terminated] =
ZIO.succeed(ContractEvent.Terminated(command.reason))

View file

@ -10,17 +10,17 @@ class ContractReducer() extends Reducer[ContractEvent, ContractState]:
override val fromState =
case (s: ContractState.Actif, e: ContractEvent.Amended) => s.amend(e)
case (s: ContractState.Actif, e: ContractEvent.Terminated) => s.terminate(e)
case (s: ContractState.Actif, _: ContractEvent.Terminated) => s.terminate()
case (s: ContractState.PendingSubscription, e: ContractEvent.Amended) => s.amend(e)
case (s: ContractState.PendingSubscription, e: ContractEvent.Approved) => s.approve(e)
case (s: ContractState.PendingSubscription, e: ContractEvent.Rejected) => s.reject(e)
case (s: ContractState.PendingSubscription, e: ContractEvent.Terminated) => s.terminate(e)
case (s: ContractState.PendingSubscription, _: ContractEvent.Approved) => s.approve()
case (s: ContractState.PendingSubscription, _: ContractEvent.Rejected) => s.reject()
case (s: ContractState.PendingSubscription, _: ContractEvent.Terminated) => s.terminate()
case (s: ContractState.PendingAmendment, e: ContractEvent.Amended) => s.amend(e)
case (s: ContractState.PendingAmendment, e: ContractEvent.Approved) => s.approve(e)
case (s: ContractState.PendingAmendment, e: ContractEvent.Rejected) => s.reject(e)
case (s: ContractState.PendingAmendment, e: ContractEvent.Terminated) => s.terminate(e)
case (s: ContractState.PendingAmendment, _: ContractEvent.Approved) => s.approve()
case (s: ContractState.PendingAmendment, _: ContractEvent.Rejected) => s.reject()
case (s: ContractState.PendingAmendment, _: ContractEvent.Terminated) => s.terminate()
object ContractReducer:
val layer: ULayer[Reducer[ContractEvent, ContractState]] = ZLayer.succeed(ContractReducer())

View file

@ -5,8 +5,6 @@ import zio.schema.*
import zio.schema.annotation.caseName
import zio.schema.annotation.discriminatorName
import java.time.LocalDate
@discriminatorName("statusType")
sealed trait ContractState derives Schema:
def product: ProductType
@ -32,7 +30,7 @@ object ContractState:
def amend(e: ContractEvent.Amended) =
Actif(e.product, holder, e.vehicle, e.formula, e.premium)
def terminate(e: ContractEvent.Terminated) =
def terminate() =
Terminated(product, holder, vehicle, formula, premium)
@discriminatorName("statusType")
@ -50,13 +48,13 @@ object ContractState:
def amend(e: ContractEvent.Amended) =
PendingSubscription(e.product, holder, e.vehicle, e.formula, e.premium)
def approve(e: ContractEvent.Approved) =
def approve() =
Actif(product, holder, vehicle, formula, premium)
def reject(e: ContractEvent.Rejected) =
def reject() =
Terminated(product, holder, vehicle, formula, premium)
def terminate(e: ContractEvent.Terminated) =
def terminate() =
Terminated(product, holder, vehicle, formula, premium)
final case class PendingChanges(
@ -79,7 +77,7 @@ object ContractState:
def amend(e: ContractEvent.Amended) =
copy(pendingChanges = PendingChanges(e.product, e.vehicle, e.formula, e.premium))
def approve(e: ContractEvent.Approved) =
def approve() =
Actif(
pendingChanges.product,
holder,
@ -88,10 +86,10 @@ object ContractState:
pendingChanges.premium
)
def reject(e: ContractEvent.Rejected) =
def reject() =
Actif(product, holder, vehicle, formula, premium)
def terminate(e: ContractEvent.Terminated) =
def terminate() =
ContractState.Terminated(product, holder, vehicle, formula, premium)
@caseName("terminated")

View file

@ -1,7 +1,5 @@
package lu.foyer
package contracts
import lu.foyer.clients.Address
trait EmployeeService:
def fetchOne(subject: String): Either[String, Employee]