Skip to content

Commit

Permalink
Add ReserveSlotUseCase
Browse files Browse the repository at this point in the history
Implemented second use case responsible for slot reservation.
Implemented reservation-rules in domain model.
  • Loading branch information
michal-kowalcze committed Oct 23, 2021
1 parent f1b0e23 commit 7b7961b
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,44 @@ import java.time.LocalTime
data class DaySchedule(
val day: LocalDate,
val slots: List<Slot>,
)
) {

fun reserveSlot(index: Int): DaySchedule {
verifyIndexIsValid(index)

if (slots[index].reserved) {
throw SlotAlreadyReservedException(index)
}

val modifiedSlots = slots.toMutableList()
modifiedSlots.set(index, slots[index].makeReservation())
return copy(slots = modifiedSlots)
}

private fun verifyIndexIsValid(index: Int) {
if (index < 0 || index >= slots.size) {
throw InvalidSlotIndexException(index, slots.size)
}
}


}

data class Slot(
val occupied: Boolean,
val reserved: Boolean,
val start: LocalTime,
val end: LocalTime,
)
) {
fun makeReservation(): Slot = copy(reserved = true)
}


data class SlotId(
val day: LocalDate,
val index: Int,
)
)

class InvalidSlotIndexException(index: Int, slotsCount: Int) :
IllegalArgumentException("Slot[$index] is not available. Slots count is: $slotsCount")

class SlotAlreadyReservedException(index: Int) : IllegalStateException("Slot[$index] is already reserved")
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package eu.kowalcze.michal.arch.clean.example.domain.usecase

import eu.kowalcze.michal.arch.clean.example.domain.model.DaySchedule
import eu.kowalcze.michal.arch.clean.example.domain.model.SlotId
import eu.kowalcze.michal.arch.clean.example.domain.repository.DayScheduleRepository

class ReserveSlotUseCase(
private val dayScheduleRepository: DayScheduleRepository,
private val getScheduleUseCase: GetScheduleUseCase,
) {

fun reserve(slotId: SlotId): DaySchedule {
val daySchedule = getScheduleUseCase.getSchedule(scheduleDay = slotId.day)

val modifiedSchedule = daySchedule.reserveSlot(slotId.index)

return dayScheduleRepository.save(modifiedSchedule)
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package eu.kowalcze.michal.arch.clean.example.domain.model

import io.kotest.assertions.throwables.shouldThrow
import io.kotest.matchers.shouldBe
import org.junit.jupiter.api.Test
import java.time.LocalDate
import java.time.LocalTime

internal class DayScheduleTest {

@Test
fun `Should reserve a slot`() {
// given
val today = LocalDate.now()
val givenSchedule =
DaySchedule(today, listOf(Slot(reserved = false, start = LocalTime.MIN, end = LocalTime.MAX)))


// when
val daySchedule = givenSchedule.reserveSlot(0)

// then
daySchedule.slots[0].reserved shouldBe true
}

@Test
fun `Should fail when slot is already reserved`() {
// given
val today = LocalDate.now()
val givenSchedule =
DaySchedule(today, listOf(Slot(reserved = true, start = LocalTime.MIN, end = LocalTime.MAX)))

// expect
val exception =
shouldThrow<SlotAlreadyReservedException> { givenSchedule.reserveSlot(0) }

exception.message shouldBe "Slot[0] is already reserved"
}

@Test
fun `Should fail when slot index is out of `() {
// given
val today = LocalDate.now()
val givenSchedule =
DaySchedule(today, listOf(Slot(reserved = true, start = LocalTime.MIN, end = LocalTime.MAX)))

// expect
val exception =
shouldThrow<InvalidSlotIndexException> { givenSchedule.reserveSlot(1) }

exception.message shouldBe "Slot[1] is not available. Slots count is: 1"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package eu.kowalcze.michal.arch.clean.example.domain.usecase

import eu.kowalcze.michal.arch.clean.example.domain.model.DaySchedule
import eu.kowalcze.michal.arch.clean.example.domain.model.DayScheduleCreator
import eu.kowalcze.michal.arch.clean.example.domain.model.Slot
import eu.kowalcze.michal.arch.clean.example.domain.model.SlotId
import eu.kowalcze.michal.arch.clean.example.infrastructure.InMemoryDayScheduleRepository
import io.kotest.matchers.shouldBe
import io.kotest.matchers.types.shouldBeSameInstanceAs
import io.mockk.every
import io.mockk.mockk
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import java.time.LocalDate
import java.time.LocalTime

internal class ReserveSlotUseCaseTest {
private lateinit var daySchedulerRepository: InMemoryDayScheduleRepository
private lateinit var dayScheduleCreator: DayScheduleCreator
private lateinit var getScheduleUseCase: GetScheduleUseCase
private lateinit var reserveSlotUseCase: ReserveSlotUseCase

@BeforeEach
fun setup() {
dayScheduleCreator = mockk(relaxed = true)
daySchedulerRepository = InMemoryDayScheduleRepository()
getScheduleUseCase = GetScheduleUseCase(daySchedulerRepository, dayScheduleCreator)
reserveSlotUseCase = ReserveSlotUseCase(daySchedulerRepository, getScheduleUseCase)
}

@Test
fun `Should reserve a slot`() {
// given
val today = LocalDate.now()
val givenSchedule =
DaySchedule(today, listOf(Slot(reserved = false, start = LocalTime.MIN, end = LocalTime.MAX)))
every { dayScheduleCreator.create(today) }.returns(givenSchedule)

// when
val daySchedule = reserveSlotUseCase.reserve(SlotId(today, index = 0))

// then
daySchedule.slots[0].reserved shouldBe true
daySchedulerRepository.itemsCount() shouldBe 1
daySchedulerRepository.get(today) shouldBeSameInstanceAs daySchedule
}

}

0 comments on commit 7b7961b

Please sign in to comment.