diff --git a/pom.xml b/pom.xml
index 5602ea6..39bac99 100644
--- a/pom.xml
+++ b/pom.xml
@@ -64,12 +64,13 @@
kotlin-logging-jvm
3.0.5
-
- com.itextpdf
- itextpdf
- 5.5.8
-
-
+
+ com.itextpdf
+ itext-core
+ 8.0.4
+ pom
+
+
org.springframework
spring-webflux
6.0.7
@@ -88,7 +89,19 @@
jakarta
${querydsl.version}
-
+
+ com.ninja-squad
+ springmockk
+ 4.0.2
+ test
+
+
+ org.mock-server
+ mockserver-maven-plugin
+ 5.15.0
+
+
+
${project.basedir}/src/main/kotlin
@@ -98,6 +111,11 @@
org.springframework.boot
spring-boot-maven-plugin
+
+ org.mock-server
+ mockserver-maven-plugin
+ 5.15.0
+
org.jetbrains.kotlin
kotlin-maven-plugin
diff --git a/src/main/kotlin/org/breizhcamp/konter/application/dto/EventDTO.kt b/src/main/kotlin/org/breizhcamp/konter/application/dto/EventDTO.kt
deleted file mode 100644
index bcce1a0..0000000
--- a/src/main/kotlin/org/breizhcamp/konter/application/dto/EventDTO.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package org.breizhcamp.konter.application.dto
-
-data class EventDTO(
- val id: Int,
- val year: Int,
- val name: String?,
-)
diff --git a/src/main/kotlin/org/breizhcamp/konter/application/dto/HallDTO.kt b/src/main/kotlin/org/breizhcamp/konter/application/dto/HallDTO.kt
index e313b54..d418a4f 100644
--- a/src/main/kotlin/org/breizhcamp/konter/application/dto/HallDTO.kt
+++ b/src/main/kotlin/org/breizhcamp/konter/application/dto/HallDTO.kt
@@ -3,4 +3,5 @@ package org.breizhcamp.konter.application.dto
data class HallDTO(
val id: Int,
val name: String?,
+ val trackId: Int?
)
diff --git a/src/main/kotlin/org/breizhcamp/konter/application/dto/ManualSessionDTO.kt b/src/main/kotlin/org/breizhcamp/konter/application/dto/ManualSessionDTO.kt
new file mode 100644
index 0000000..e6f6600
--- /dev/null
+++ b/src/main/kotlin/org/breizhcamp/konter/application/dto/ManualSessionDTO.kt
@@ -0,0 +1,12 @@
+package org.breizhcamp.konter.application.dto
+
+import org.breizhcamp.konter.domain.entities.enums.SessionFormatEnum
+import org.breizhcamp.konter.domain.entities.enums.SessionThemeEnum
+
+data class ManualSessionDTO(
+ val id: Int,
+ val title: String,
+ val description: String,
+ val format: SessionFormatEnum,
+ val theme: SessionThemeEnum
+)
diff --git a/src/main/kotlin/org/breizhcamp/konter/application/dto/SessionDTO.kt b/src/main/kotlin/org/breizhcamp/konter/application/dto/SessionDTO.kt
index ac58ea2..75e4dc4 100644
--- a/src/main/kotlin/org/breizhcamp/konter/application/dto/SessionDTO.kt
+++ b/src/main/kotlin/org/breizhcamp/konter/application/dto/SessionDTO.kt
@@ -19,9 +19,7 @@ data class SessionDTO(
val status: SessionStatusEnum,
val submitted: LocalDateTime,
val ownerNotes: String,
- val hall: HallDTO?,
- val beginning: LocalDateTime?,
- val end: LocalDateTime?,
val videoURL: String?,
val rating: BigDecimal?,
+ val slot: SlotDTO?,
)
diff --git a/src/main/kotlin/org/breizhcamp/konter/application/dto/SlotDTO.kt b/src/main/kotlin/org/breizhcamp/konter/application/dto/SlotDTO.kt
new file mode 100644
index 0000000..77bce50
--- /dev/null
+++ b/src/main/kotlin/org/breizhcamp/konter/application/dto/SlotDTO.kt
@@ -0,0 +1,18 @@
+package org.breizhcamp.konter.application.dto
+
+import java.time.Duration
+import java.time.LocalTime
+import java.util.*
+
+data class SlotDTO(
+ val id: UUID,
+ val day: Int,
+ val session: SessionDTO?,
+ val halls: List,
+ val start: LocalTime,
+ val duration: Duration,
+ val barcode: String?,
+ val span: Int,
+ val title: String?,
+ val assignable: Boolean
+)
diff --git a/src/main/kotlin/org/breizhcamp/konter/application/dto/TalkDTO.kt b/src/main/kotlin/org/breizhcamp/konter/application/dto/TalkDTO.kt
new file mode 100644
index 0000000..446af53
--- /dev/null
+++ b/src/main/kotlin/org/breizhcamp/konter/application/dto/TalkDTO.kt
@@ -0,0 +1,25 @@
+package org.breizhcamp.konter.application.dto
+
+import com.fasterxml.jackson.annotation.JsonFormat
+import com.fasterxml.jackson.databind.PropertyNamingStrategies
+import com.fasterxml.jackson.databind.annotation.JsonNaming
+import java.time.LocalDateTime
+
+@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class)
+data class TalkDTO(
+ @JsonFormat(shape = JsonFormat.Shape.STRING)
+ val id: Int,
+ val name: String,
+ val eventStart: LocalDateTime,
+ val eventEnd: LocalDateTime,
+ val eventType: String,
+ val format: String,
+ val venue: String,
+ @JsonFormat(shape = JsonFormat.Shape.STRING)
+ val venueId: Int,
+ val speakers: String,
+ val videoUrl: String?,
+ val filesUrl: String?,
+ val slidesUrl: String?,
+ val description: String?
+)
diff --git a/src/main/kotlin/org/breizhcamp/konter/application/requests/EventCreationReq.kt b/src/main/kotlin/org/breizhcamp/konter/application/requests/EventCreationReq.kt
deleted file mode 100644
index c652d8b..0000000
--- a/src/main/kotlin/org/breizhcamp/konter/application/requests/EventCreationReq.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package org.breizhcamp.konter.application.requests
-
-data class EventCreationReq(
- val year: Int
-)
diff --git a/src/main/kotlin/org/breizhcamp/konter/application/requests/HallCreationReq.kt b/src/main/kotlin/org/breizhcamp/konter/application/requests/HallCreationReq.kt
index e150ddb..bd4e68d 100644
--- a/src/main/kotlin/org/breizhcamp/konter/application/requests/HallCreationReq.kt
+++ b/src/main/kotlin/org/breizhcamp/konter/application/requests/HallCreationReq.kt
@@ -1,5 +1,6 @@
package org.breizhcamp.konter.application.requests
data class HallCreationReq(
- val name: String
+ val name: String,
+ val trackId: Int?,
)
diff --git a/src/main/kotlin/org/breizhcamp/konter/application/requests/HallPatchReq.kt b/src/main/kotlin/org/breizhcamp/konter/application/requests/HallPatchReq.kt
new file mode 100644
index 0000000..aee2a7d
--- /dev/null
+++ b/src/main/kotlin/org/breizhcamp/konter/application/requests/HallPatchReq.kt
@@ -0,0 +1,6 @@
+package org.breizhcamp.konter.application.requests
+
+data class HallPatchReq(
+ val name: String,
+ val trackId: Int?
+)
diff --git a/src/main/kotlin/org/breizhcamp/konter/application/requests/SessionCreationReq.kt b/src/main/kotlin/org/breizhcamp/konter/application/requests/SessionCreationReq.kt
new file mode 100644
index 0000000..2a0e012
--- /dev/null
+++ b/src/main/kotlin/org/breizhcamp/konter/application/requests/SessionCreationReq.kt
@@ -0,0 +1,11 @@
+package org.breizhcamp.konter.application.requests
+
+import org.breizhcamp.konter.domain.entities.enums.SessionFormatEnum
+import org.breizhcamp.konter.domain.entities.enums.SessionThemeEnum
+
+data class SessionCreationReq(
+ val title: String,
+ val description: String,
+ val format: SessionFormatEnum,
+ val theme: SessionThemeEnum,
+)
diff --git a/src/main/kotlin/org/breizhcamp/konter/application/requests/SessionPatchReq.kt b/src/main/kotlin/org/breizhcamp/konter/application/requests/SessionPatchReq.kt
new file mode 100644
index 0000000..bad3266
--- /dev/null
+++ b/src/main/kotlin/org/breizhcamp/konter/application/requests/SessionPatchReq.kt
@@ -0,0 +1,20 @@
+package org.breizhcamp.konter.application.requests
+
+import org.breizhcamp.konter.domain.entities.enums.SessionFormatEnum
+import org.breizhcamp.konter.domain.entities.enums.SessionThemeEnum
+
+data class SessionPatchReq(
+ val title: String?,
+ val description: String?,
+ val format: SessionFormatEnum?,
+ val theme: SessionThemeEnum?,
+) {
+ companion object {
+ fun empty(): SessionPatchReq = SessionPatchReq(
+ title = null,
+ description = null,
+ format = null,
+ theme = null
+ )
+ }
+}
diff --git a/src/main/kotlin/org/breizhcamp/konter/application/requests/SlotCreationReq.kt b/src/main/kotlin/org/breizhcamp/konter/application/requests/SlotCreationReq.kt
new file mode 100644
index 0000000..d043fb9
--- /dev/null
+++ b/src/main/kotlin/org/breizhcamp/konter/application/requests/SlotCreationReq.kt
@@ -0,0 +1,16 @@
+package org.breizhcamp.konter.application.requests
+
+import org.hibernate.annotations.JdbcTypeCode
+import org.hibernate.type.SqlTypes
+import java.time.Duration
+import java.time.LocalTime
+
+data class SlotCreationReq(
+ val start: LocalTime,
+ val day: Int,
+ @JdbcTypeCode(SqlTypes.INTERVAL_SECOND)
+ val duration: Duration,
+ val title: String?,
+ val hallIds: List,
+ val assignable: Boolean
+)
diff --git a/src/main/kotlin/org/breizhcamp/konter/application/requests/SlotPatchReq.kt b/src/main/kotlin/org/breizhcamp/konter/application/requests/SlotPatchReq.kt
new file mode 100644
index 0000000..2dc42e9
--- /dev/null
+++ b/src/main/kotlin/org/breizhcamp/konter/application/requests/SlotPatchReq.kt
@@ -0,0 +1,6 @@
+package org.breizhcamp.konter.application.requests
+
+data class SlotPatchReq(
+ val title: String?,
+ val assignable: Boolean
+)
diff --git a/src/main/kotlin/org/breizhcamp/konter/application/rest/HallController.kt b/src/main/kotlin/org/breizhcamp/konter/application/rest/HallController.kt
index c77389f..63e7ced 100644
--- a/src/main/kotlin/org/breizhcamp/konter/application/rest/HallController.kt
+++ b/src/main/kotlin/org/breizhcamp/konter/application/rest/HallController.kt
@@ -3,9 +3,14 @@ package org.breizhcamp.konter.application.rest
import mu.KotlinLogging
import org.breizhcamp.konter.application.dto.HallDTO
import org.breizhcamp.konter.application.requests.HallCreationReq
+import org.breizhcamp.konter.application.requests.HallPatchReq
import org.breizhcamp.konter.domain.entities.Hall
-import org.breizhcamp.konter.domain.use_cases.HallCreate
-import org.breizhcamp.konter.domain.use_cases.HallList
+import org.breizhcamp.konter.domain.entities.exceptions.EventNotFoundException
+import org.breizhcamp.konter.domain.use_cases.HallAssociateEvent
+import org.breizhcamp.konter.domain.use_cases.HallCRUD
+import org.breizhcamp.konter.domain.use_cases.HallSetOrder
+import org.springframework.http.HttpStatus
+import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
private val logger = KotlinLogging.logger { }
@@ -13,33 +18,77 @@ private val logger = KotlinLogging.logger { }
@RestController
@RequestMapping("/api/halls")
class HallController (
- private val hallCreate: HallCreate,
- private val hallList: HallList,
+ private val hallCRUD: HallCRUD,
+ private val hallAssociateEvent: HallAssociateEvent,
+ private val hallSetOrder: HallSetOrder
) {
@GetMapping
fun listAll(): List {
logger.info { "Listing all Halls" }
- return hallList.listAll().map { it.toDto() }
+ return hallCRUD.listAll().map { it.toDto() }
+ }
+
+ @GetMapping("/{eventId}")
+ fun listByEvent(@PathVariable eventId: Int): List {
+ logger.info { "Listing Halls available for Event:$eventId" }
+
+ return hallCRUD.listByEvent(eventId).map { it.toDto() }
}
@PostMapping
fun createHall(@RequestBody req: HallCreationReq): HallDTO {
- logger.info { "Creating a Hall with name ${req.name}" }
+ logger.info { "Creating a Hall with name ${req.name} and trackId ${req.trackId}" }
- return hallCreate.createHall(req).toDto()
+ return hallCRUD.create(req).toDto()
}
- @GetMapping("/{eventId}")
- fun listByEvent(@PathVariable eventId: Int): List {
- logger.info { "Listing Halls available for Event<$eventId>" }
+ @PatchMapping("/{id}")
+ fun patchHall(@PathVariable id: Int, @RequestBody req: HallPatchReq): HallDTO {
+ logger.info { "Updating Hall:$id" }
- return hallList.listByEvent(eventId).map { it.toDto() }
+ return hallCRUD.update(id, req).toDto()
}
+
+ @DeleteMapping("/{id}")
+ fun deleteHall(@PathVariable id: Int) {
+ logger.info { "Deleting Hall:$id" }
+
+ return hallCRUD.delete(id)
+ }
+
+ @PostMapping("/{id}/event/{eventId}/{order}")
+ fun associateToEvent(@PathVariable id: Int, @PathVariable eventId: Int, @PathVariable order: Int): ResponseEntity<*> {
+ logger.info { "Associating Hall:$id to Event:$eventId" }
+
+ return try {
+ ResponseEntity.ok(hallAssociateEvent.associate(id, eventId, order).toDto())
+ } catch (e: EventNotFoundException) {
+ logger.error { e }
+
+ ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.message)
+ }
+ }
+
+ @DeleteMapping("/{id}/event/{eventId}")
+ fun dissociateFromEvent(@PathVariable id: Int, @PathVariable eventId: Int): HallDTO {
+ logger.info { "Dissociating Hall:$id from Event:$eventId" }
+
+ return hallAssociateEvent.dissociate(id, eventId).toDto()
+ }
+
+ @PatchMapping("/{id}/event/{eventId}/{order}")
+ fun updateOrderForEvent(@PathVariable id: Int, @PathVariable eventId: Int, @PathVariable order: Int): HallDTO {
+ logger.info { "Updating order of Hall:$id for Event:$eventId to $order" }
+
+ return hallSetOrder.setOrder(id, eventId, order).toDto()
+ }
+
}
fun Hall.toDto() = HallDTO(
id = id,
name = name,
+ trackId = trackId
)
\ No newline at end of file
diff --git a/src/main/kotlin/org/breizhcamp/konter/application/rest/SessionController.kt b/src/main/kotlin/org/breizhcamp/konter/application/rest/SessionController.kt
index c16924e..98bc12a 100644
--- a/src/main/kotlin/org/breizhcamp/konter/application/rest/SessionController.kt
+++ b/src/main/kotlin/org/breizhcamp/konter/application/rest/SessionController.kt
@@ -2,16 +2,19 @@ package org.breizhcamp.konter.application.rest
import jakarta.servlet.http.HttpServletResponse
import mu.KotlinLogging
+import org.breizhcamp.konter.application.dto.ManualSessionDTO
import org.breizhcamp.konter.application.dto.SessionDTO
+import org.breizhcamp.konter.application.requests.SessionCreationReq
+import org.breizhcamp.konter.application.requests.SessionPatchReq
+import org.breizhcamp.konter.domain.entities.ManualSession
import org.breizhcamp.konter.domain.entities.Session
import org.breizhcamp.konter.domain.entities.SessionFilter
-import org.breizhcamp.konter.domain.use_cases.SessionGenerateCards
-import org.breizhcamp.konter.domain.use_cases.SessionImport
-import org.breizhcamp.konter.domain.use_cases.SessionList
+import org.breizhcamp.konter.domain.use_cases.*
import org.springframework.http.HttpHeaders
import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.*
import org.springframework.web.multipart.MultipartFile
+import java.util.*
private val logger = KotlinLogging.logger {}
@@ -20,28 +23,31 @@ private val logger = KotlinLogging.logger {}
class SessionController (
private val sessionImport: SessionImport,
private val sessionGenerateCards: SessionGenerateCards,
+ private val eventGet: EventGet,
private val sessionList: SessionList,
+ private val slotSetSession: SlotSetSession,
+ private val manualSessionCRUD: ManualSessionCRUD
) {
- @GetMapping("/{year}")
- fun listSessions(@PathVariable year: Int): List {
- logger.info { "Listing Sessions from year $year" }
+ @GetMapping("/{eventId}")
+ fun listSessions(@PathVariable eventId: Int): List {
+ logger.info { "Listing Sessions from Event:$eventId" }
- return sessionList.list(year).map { it.toDto() }
+ return sessionList.list(eventId).map { it.toDto() }
}
- @PostMapping("/{year}/filter")
- fun filterSessions(@PathVariable year: Int, @RequestBody sessionFilter: SessionFilter): List {
- logger.info { "Filtering Sessions from year $year" }
+ @PostMapping("/{eventId}/filter")
+ fun filterSessions(@PathVariable eventId: Int, @RequestBody sessionFilter: SessionFilter): List {
+ logger.info { "Filtering Sessions from Event:$eventId" }
- return sessionList.filter(year, sessionFilter).map { it.toDto() }
+ return sessionList.filter(eventId, sessionFilter).map { it.toDto() }
}
- @PostMapping("/{year}/import")
- fun importCsv(@PathVariable year: Int, file: MultipartFile) {
- logger.info { "Importing Sessions for year $year" }
+ @PostMapping("/{eventId}/import")
+ fun importCsv(@PathVariable eventId: Int, file: MultipartFile) {
+ logger.info { "Importing Sessions for Event:$eventId" }
- sessionImport.importCsv(year, file.inputStream)
+ sessionImport.importCsv(eventId, file.inputStream)
}
@PostMapping("/evaluations/import")
@@ -51,18 +57,73 @@ class SessionController (
sessionImport.importEvaluationCsv(file.inputStream)
}
- @GetMapping("/{year}/export")
- fun exportCards(@PathVariable year: Int, output: HttpServletResponse) {
- logger.info { "Generating Sessions cards for year $year" }
+ @GetMapping("/{eventId}/export")
+ fun exportCards(@PathVariable eventId: Int, output: HttpServletResponse) {
+ logger.info { "Generating Sessions cards for Event:$eventId" }
+ val name = StringBuilder()
+ .append("attachment; filename=")
+ .append("\"session_cards_")
+ .append(eventGet.getById(eventId).name)
+ .append(".pdf\"")
+ .toString()
- output.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"session_cards_$year.pdf\"")
+ output.setHeader(HttpHeaders.CONTENT_DISPOSITION, name)
output.contentType = MediaType.APPLICATION_PDF_VALUE
- sessionGenerateCards.generatePdf(year, output.outputStream)
+ sessionGenerateCards.generatePdf(eventId, output.outputStream)
+ }
+
+ @PostMapping("/{id}/slot/id/{slotId}")
+ fun setSlot(@PathVariable id: Int, @PathVariable slotId: UUID): SessionDTO {
+ logger.info { "Setting Session:$id to Slot:$slotId" }
+
+ return slotSetSession.setById(id, slotId).toDto()
+ }
+
+ @PostMapping("/{id}/slot/barcode/{barcode}")
+ fun setSlot(@PathVariable id: Int, @PathVariable barcode: String): SessionDTO {
+ logger.info { "Setting Session:$id to Slot with barcode $barcode" }
+
+ return slotSetSession.setByBarcode(id, barcode).toDto()
+ }
+
+ @PostMapping("/manual/{eventId}")
+ fun createManualSession(@PathVariable eventId: Int, @RequestBody request: SessionCreationReq): ManualSessionDTO {
+ logger.info { "Creating ManualSession for Event:$eventId with title:${request.title}" }
+
+ return manualSessionCRUD.create(request, eventId).toDto()
+ }
+
+ @GetMapping("/manual/{id}")
+ fun getManualSession(@PathVariable id: Int): ManualSessionDTO {
+ logger.info { "Retrieving ManualSession:$id" }
+
+ return manualSessionCRUD.get(id).toDto()
+ }
+
+ @GetMapping("/manual/list/{eventId}")
+ fun listManualSessions(@PathVariable eventId: Int): List {
+ logger.info { "Retrieving all ManualSession in Event:$eventId" }
+
+ return manualSessionCRUD.list(eventId).map { it.toDto() }
+ }
+
+ @PatchMapping("/manual/{id}")
+ fun patchManualSession(@PathVariable id: Int, @RequestBody request: SessionPatchReq): ManualSessionDTO {
+ logger.info { "Updating ManualSession:$id" }
+
+ return manualSessionCRUD.update(id, request).toDto()
+ }
+
+ @DeleteMapping("/manual/{id}")
+ fun deleteManualSession(@PathVariable id: Int) {
+ logger.info { "Deleting ManualSession:$id" }
+
+ manualSessionCRUD.delete(id)
}
}
-fun Session.toDto() = SessionDTO(
+fun Session.toDto(): SessionDTO = SessionDTO(
id = id,
title = title,
description = description,
@@ -74,9 +135,15 @@ fun Session.toDto() = SessionDTO(
status = status,
submitted = submitted,
ownerNotes = ownerNotes,
- hall = hall?.toDto(),
- beginning = beginning,
- end = end,
videoURL = videoURL,
- rating = rating
+ rating = rating,
+ slot = slot?.toDto()
+)
+
+fun ManualSession.toDto(): ManualSessionDTO = ManualSessionDTO(
+ id = id,
+ title = title,
+ description = description,
+ format = format,
+ theme = theme
)
\ No newline at end of file
diff --git a/src/main/kotlin/org/breizhcamp/konter/application/rest/SlotController.kt b/src/main/kotlin/org/breizhcamp/konter/application/rest/SlotController.kt
new file mode 100644
index 0000000..7bffde2
--- /dev/null
+++ b/src/main/kotlin/org/breizhcamp/konter/application/rest/SlotController.kt
@@ -0,0 +1,181 @@
+package org.breizhcamp.konter.application.rest
+
+import jakarta.servlet.http.HttpServletResponse
+import mu.KotlinLogging
+import org.breizhcamp.konter.application.dto.SlotDTO
+import org.breizhcamp.konter.application.dto.TalkDTO
+import org.breizhcamp.konter.application.requests.SlotCreationReq
+import org.breizhcamp.konter.application.requests.SlotPatchReq
+import org.breizhcamp.konter.domain.entities.Hall
+import org.breizhcamp.konter.domain.entities.Slot
+import org.breizhcamp.konter.domain.entities.Talk
+import org.breizhcamp.konter.domain.entities.enums.getLabel
+import org.breizhcamp.konter.domain.entities.exceptions.EventNoBeginException
+import org.breizhcamp.konter.domain.entities.exceptions.HallNotFoundException
+import org.breizhcamp.konter.domain.entities.exceptions.TimeConflictException
+import org.breizhcamp.konter.domain.use_cases.*
+import org.springframework.http.HttpHeaders
+import org.springframework.http.HttpStatus
+import org.springframework.http.MediaType
+import org.springframework.http.ResponseEntity
+import org.springframework.web.bind.annotation.*
+import java.util.*
+
+private val logger = KotlinLogging.logger { }
+
+@RestController
+@RequestMapping("/api/slots")
+class SlotController (
+ private val slotCrud: SlotCRUD,
+ private val eventGet: EventGet,
+ private val slotGenerateProgram: SlotGenerateProgram,
+ private val slotAssociateHall: SlotAssociateHall,
+ private val getTalks: GetTalks
+) {
+
+ @GetMapping("/event/{id}")
+ fun listSlotForEvent(@PathVariable id: Int): Map>> {
+ logger.info { "Listing all slots in Event:$id grouping by Hall" }
+
+ val values: Map>> = slotCrud.list(id)
+ val result = mutableMapOf>>()
+
+ for (entry in values) {
+ val key = entry.key
+ val map: Map> = entry.value
+
+ val rowMap = mutableMapOf>()
+
+ for (subEntry in map) {
+ val subKey = subEntry.key.id
+ val list = subEntry.value.map { it.toDto() }
+
+ rowMap[subKey] = list
+ }
+
+ result[key] = rowMap
+ }
+
+ return result
+ }
+
+ @PostMapping("/{eventId}")
+ fun addSlotToHall(
+ @PathVariable eventId: Int,
+ @RequestBody slotReq: SlotCreationReq
+ ): ResponseEntity {
+ logger.info { "Adding slot in Event:$eventId" }
+
+ return try {
+ ResponseEntity.ok(slotCrud.create(eventId, slotReq).toDto())
+ } catch (e: TimeConflictException) {
+ logger.error { e }
+ ResponseEntity.status(HttpStatus.CONFLICT).body(e.message)
+ } catch (e: HallNotFoundException) {
+ logger.error { e }
+ ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.message)
+ }
+ }
+
+ @GetMapping("/{id}")
+ fun getSlot(@PathVariable id: UUID): SlotDTO {
+ logger.info { "Retrieving Slot:$id" }
+
+ return slotCrud.get(id).toDto()
+ }
+
+ @DeleteMapping("/{id}")
+ fun deleteSlot(@PathVariable id: UUID) {
+ logger.info { "Deleting Slot:$id" }
+
+ slotCrud.delete(id)
+ }
+
+ @PatchMapping("/{id}")
+ fun update(@PathVariable id: UUID, @RequestBody request: SlotPatchReq): SlotDTO {
+ logger.info { "Patching Slot:$id" }
+
+ return slotCrud.update(id, request).toDto()
+ }
+
+ @GetMapping("/program/{eventId}")
+ fun exportProgram(@PathVariable eventId: Int, output: HttpServletResponse) {
+ logger.info { "Generating program for Event:$eventId" }
+
+ val name = StringBuilder()
+ .append("attachment; filename=")
+ .append("\"program_")
+ .append(eventGet.getById(eventId).name)
+ .append(".pdf\"")
+ .toString()
+
+ output.setHeader(HttpHeaders.CONTENT_DISPOSITION, name)
+ output.contentType = MediaType.APPLICATION_PDF_VALUE
+
+ slotGenerateProgram.generateEmptyProgramPdf(eventId, output.outputStream)
+ }
+
+ @PostMapping("/hall/{slotId}/{eventId}/{hallId}")
+ fun assignHallToSlot(
+ @PathVariable slotId: UUID, @PathVariable eventId: Int, @PathVariable hallId: Int
+ ): ResponseEntity {
+ logger.info { "Assigning Hall:$hallId to Slot:$slotId in Event:$eventId" }
+
+ return try {
+ ResponseEntity.ok(slotAssociateHall.associate(slotId, eventId, hallId).toDto())
+ } catch (e: TimeConflictException) {
+ logger.error { e }
+ ResponseEntity.status(HttpStatus.CONFLICT).body(e.message)
+ }
+ }
+
+ @DeleteMapping("/hall/{slotId}/{hallId}")
+ fun resignHallFromSlot(@PathVariable slotId: UUID, @PathVariable hallId: Int) {
+ logger.info { "Resigning Hall:$hallId from Slot:$slotId" }
+
+ slotAssociateHall.dissociate(slotId, hallId)
+ }
+
+ @GetMapping("/talks/{eventId}")
+ fun listTalks(@PathVariable eventId: Int): ResponseEntity<*> {
+ logger.info { "Retrieving Talks from Event:$eventId" }
+
+ return try {
+ ResponseEntity.ok(getTalks.list(eventId).map { it.toDto() })
+ } catch (e: EventNoBeginException) {
+ logger.error { e }
+ ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.message)
+ }
+ }
+}
+
+fun Slot.toDto(): SlotDTO = SlotDTO(
+ id = id,
+ day = day,
+ session = session?.toDto(),
+ halls = halls.map{ it.toDto() },
+ start = start,
+ duration = duration,
+ barcode = barcode,
+ span = span,
+ title = title,
+ assignable = assignable
+)
+
+fun Talk.toDto(): TalkDTO {
+ return TalkDTO(
+ id = id,
+ name = name,
+ eventStart = eventStart,
+ eventEnd = eventEnd,
+ eventType = eventType.getLabel(),
+ format = format.getLabel(),
+ venue = requireNotNull(hall.name) { "Name not found for Hall:${hall.id}" },
+ venueId = requireNotNull(hall.trackId) { "Track ID not found for Hall:${hall.id}" },
+ speakers = speakers.joinToString(", ") { "${it.firstname} ${it.lastname}" },
+ videoUrl = videoUrl,
+ filesUrl = filesUrl,
+ slidesUrl = slidesUrl,
+ description = description
+ )
+}
\ No newline at end of file
diff --git a/src/main/kotlin/org/breizhcamp/konter/config/KonterConfig.kt b/src/main/kotlin/org/breizhcamp/konter/config/KonterConfig.kt
index ca5a0c5..8eb6c19 100644
--- a/src/main/kotlin/org/breizhcamp/konter/config/KonterConfig.kt
+++ b/src/main/kotlin/org/breizhcamp/konter/config/KonterConfig.kt
@@ -9,7 +9,7 @@ data class KonterConfig(
data class KalonConfig(
val enabled: Boolean,
- val url: String,
+ val url: String = "",
val secured: Boolean,
val apiKey: String?,
diff --git a/src/main/kotlin/org/breizhcamp/konter/domain/entities/Event.kt b/src/main/kotlin/org/breizhcamp/konter/domain/entities/Event.kt
index cffa183..9ea9211 100644
--- a/src/main/kotlin/org/breizhcamp/konter/domain/entities/Event.kt
+++ b/src/main/kotlin/org/breizhcamp/konter/domain/entities/Event.kt
@@ -1,7 +1,14 @@
package org.breizhcamp.konter.domain.entities
+import com.fasterxml.jackson.annotation.JsonProperty
+import java.time.LocalDate
+
data class Event(
val id: Int,
val year: Int,
val name: String?,
+ @JsonProperty("debutEvent")
+ val begin: LocalDate?,
+ @JsonProperty("finEvent")
+ val end: LocalDate?,
)
diff --git a/src/main/kotlin/org/breizhcamp/konter/domain/entities/Hall.kt b/src/main/kotlin/org/breizhcamp/konter/domain/entities/Hall.kt
index bfd62d5..b5c053d 100644
--- a/src/main/kotlin/org/breizhcamp/konter/domain/entities/Hall.kt
+++ b/src/main/kotlin/org/breizhcamp/konter/domain/entities/Hall.kt
@@ -3,4 +3,5 @@ package org.breizhcamp.konter.domain.entities
data class Hall(
val id: Int,
val name: String?,
+ val trackId: Int?,
)
diff --git a/src/main/kotlin/org/breizhcamp/konter/domain/entities/ManualSession.kt b/src/main/kotlin/org/breizhcamp/konter/domain/entities/ManualSession.kt
new file mode 100644
index 0000000..d8372b4
--- /dev/null
+++ b/src/main/kotlin/org/breizhcamp/konter/domain/entities/ManualSession.kt
@@ -0,0 +1,13 @@
+package org.breizhcamp.konter.domain.entities
+
+import org.breizhcamp.konter.domain.entities.enums.SessionFormatEnum
+import org.breizhcamp.konter.domain.entities.enums.SessionThemeEnum
+
+data class ManualSession(
+ val id: Int,
+ val title: String,
+ val description: String,
+ val event: Event,
+ val format: SessionFormatEnum,
+ val theme: SessionThemeEnum
+)
diff --git a/src/main/kotlin/org/breizhcamp/konter/domain/entities/Session.kt b/src/main/kotlin/org/breizhcamp/konter/domain/entities/Session.kt
index b213a0f..08b3c48 100644
--- a/src/main/kotlin/org/breizhcamp/konter/domain/entities/Session.kt
+++ b/src/main/kotlin/org/breizhcamp/konter/domain/entities/Session.kt
@@ -20,9 +20,7 @@ data class Session(
val submitted: LocalDateTime,
val ownerNotes: String,
val event: Event,
- val hall: Hall?,
- val beginning: LocalDateTime?,
- val end: LocalDateTime?,
val videoURL: String?,
val rating: BigDecimal?,
+ val slot: Slot?
)
diff --git a/src/main/kotlin/org/breizhcamp/konter/domain/entities/Slot.kt b/src/main/kotlin/org/breizhcamp/konter/domain/entities/Slot.kt
new file mode 100644
index 0000000..d2ddfd5
--- /dev/null
+++ b/src/main/kotlin/org/breizhcamp/konter/domain/entities/Slot.kt
@@ -0,0 +1,20 @@
+package org.breizhcamp.konter.domain.entities
+
+import java.time.Duration
+import java.time.LocalTime
+import java.util.*
+
+data class Slot(
+ val id: UUID,
+ val day: Int,
+ val session: Session?,
+ val manualSession: ManualSession?,
+ val event: Event,
+ val halls: List,
+ val start: LocalTime,
+ val duration: Duration,
+ val barcode: String?,
+ val span: Int,
+ val title: String?,
+ val assignable: Boolean
+)
diff --git a/src/main/kotlin/org/breizhcamp/konter/domain/entities/SpeakerFilter.kt b/src/main/kotlin/org/breizhcamp/konter/domain/entities/SpeakerFilter.kt
deleted file mode 100644
index ef85e0f..0000000
--- a/src/main/kotlin/org/breizhcamp/konter/domain/entities/SpeakerFilter.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package org.breizhcamp.konter.domain.entities
-
-data class SpeakerFilter(
- val lastname: String?,
- val firstname: String?,
- val email: String?,
-) {
- companion object {
- fun empty() = SpeakerFilter(null, null, null)
- }
-}
diff --git a/src/main/kotlin/org/breizhcamp/konter/domain/entities/Talk.kt b/src/main/kotlin/org/breizhcamp/konter/domain/entities/Talk.kt
new file mode 100644
index 0000000..8ebae1b
--- /dev/null
+++ b/src/main/kotlin/org/breizhcamp/konter/domain/entities/Talk.kt
@@ -0,0 +1,20 @@
+package org.breizhcamp.konter.domain.entities
+
+import org.breizhcamp.konter.domain.entities.enums.SessionFormatEnum
+import org.breizhcamp.konter.domain.entities.enums.SessionThemeEnum
+import java.time.LocalDateTime
+
+data class Talk(
+ val id: Int,
+ val name: String,
+ val eventStart: LocalDateTime,
+ val eventEnd: LocalDateTime,
+ val eventType: SessionThemeEnum,
+ val format: SessionFormatEnum,
+ val hall: Hall,
+ val speakers: List,
+ val videoUrl: String?,
+ val filesUrl: String?,
+ val slidesUrl: String?,
+ val description: String?
+)
diff --git a/src/main/kotlin/org/breizhcamp/konter/domain/entities/exceptions/EventNoBeginException.kt b/src/main/kotlin/org/breizhcamp/konter/domain/entities/exceptions/EventNoBeginException.kt
new file mode 100644
index 0000000..2953455
--- /dev/null
+++ b/src/main/kotlin/org/breizhcamp/konter/domain/entities/exceptions/EventNoBeginException.kt
@@ -0,0 +1,3 @@
+package org.breizhcamp.konter.domain.entities.exceptions
+
+class EventNoBeginException(message: String): Exception(message)
\ No newline at end of file
diff --git a/src/main/kotlin/org/breizhcamp/konter/domain/entities/exceptions/EventNotFoundException.kt b/src/main/kotlin/org/breizhcamp/konter/domain/entities/exceptions/EventNotFoundException.kt
new file mode 100644
index 0000000..c843b71
--- /dev/null
+++ b/src/main/kotlin/org/breizhcamp/konter/domain/entities/exceptions/EventNotFoundException.kt
@@ -0,0 +1,3 @@
+package org.breizhcamp.konter.domain.entities.exceptions
+
+class EventNotFoundException(message: String): Exception(message)
\ No newline at end of file
diff --git a/src/main/kotlin/org/breizhcamp/konter/domain/entities/exceptions/HallNotFoundException.kt b/src/main/kotlin/org/breizhcamp/konter/domain/entities/exceptions/HallNotFoundException.kt
new file mode 100644
index 0000000..80a966a
--- /dev/null
+++ b/src/main/kotlin/org/breizhcamp/konter/domain/entities/exceptions/HallNotFoundException.kt
@@ -0,0 +1,3 @@
+package org.breizhcamp.konter.domain.entities.exceptions
+
+class HallNotFoundException(message: String): Exception(message)
\ No newline at end of file
diff --git a/src/main/kotlin/org/breizhcamp/konter/domain/entities/exceptions/TimeConflictException.kt b/src/main/kotlin/org/breizhcamp/konter/domain/entities/exceptions/TimeConflictException.kt
new file mode 100644
index 0000000..971c052
--- /dev/null
+++ b/src/main/kotlin/org/breizhcamp/konter/domain/entities/exceptions/TimeConflictException.kt
@@ -0,0 +1,3 @@
+package org.breizhcamp.konter.domain.entities.exceptions
+
+class TimeConflictException(message: String) : Exception(message)
\ No newline at end of file
diff --git a/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/EventGet.kt b/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/EventGet.kt
new file mode 100644
index 0000000..da60a20
--- /dev/null
+++ b/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/EventGet.kt
@@ -0,0 +1,12 @@
+package org.breizhcamp.konter.domain.use_cases
+
+import org.breizhcamp.konter.domain.entities.Event
+import org.breizhcamp.konter.domain.use_cases.ports.EventPort
+import org.springframework.stereotype.Service
+
+@Service
+class EventGet (
+ private val eventPort: EventPort
+) {
+ fun getById(id: Int): Event = eventPort.getById(id)
+}
\ No newline at end of file
diff --git a/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/GetTalks.kt b/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/GetTalks.kt
new file mode 100644
index 0000000..ffd0c13
--- /dev/null
+++ b/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/GetTalks.kt
@@ -0,0 +1,28 @@
+package org.breizhcamp.konter.domain.use_cases
+
+import jakarta.transaction.Transactional
+import org.breizhcamp.konter.domain.entities.Talk
+import org.breizhcamp.konter.domain.entities.exceptions.EventNoBeginException
+import org.breizhcamp.konter.domain.use_cases.ports.EventPort
+import org.breizhcamp.konter.domain.use_cases.ports.KalonPort
+import org.springframework.stereotype.Service
+
+@Service
+class GetTalks(
+ private val eventPort: EventPort,
+ private val kalonPort: KalonPort
+) {
+ @Throws
+ @Transactional
+ fun list(eventId: Int): List {
+ var event = eventPort.getById(eventId)
+ if (event.begin == null) {
+ eventPort.save(kalonPort.getEvents())
+ event = eventPort.getById(eventId)
+ if (event.begin == null) {
+ throw EventNoBeginException("No beginning date found for Event:$eventId")
+ }
+ }
+ return eventPort.exportTalks(eventId)
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/HallAssociateEvent.kt b/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/HallAssociateEvent.kt
new file mode 100644
index 0000000..c0a18c4
--- /dev/null
+++ b/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/HallAssociateEvent.kt
@@ -0,0 +1,28 @@
+package org.breizhcamp.konter.domain.use_cases
+
+import org.breizhcamp.konter.domain.entities.Hall
+import org.breizhcamp.konter.domain.entities.exceptions.HallNotFoundException
+import org.breizhcamp.konter.domain.use_cases.ports.EventPort
+import org.breizhcamp.konter.domain.use_cases.ports.HallPort
+import org.breizhcamp.konter.domain.use_cases.ports.KalonPort
+import org.springframework.stereotype.Service
+
+@Service
+class HallAssociateEvent(
+ private val hallPort: HallPort,
+ private val eventPort: EventPort,
+ private val kalonPort: KalonPort,
+) {
+ @Throws
+ fun associate(id: Int, eventId: Int, order: Int): Hall {
+ if (!eventPort.existsById(eventId)) {
+ eventPort.save(kalonPort.getEvents())
+ if (!eventPort.existsById(eventId)) {
+ throw HallNotFoundException("No Event with id $eventId found")
+ }
+ }
+
+ return hallPort.associateToEvent(id, eventId, order)
+ }
+ fun dissociate(id: Int, eventId: Int): Hall = hallPort.dissociateFromEvent(id, eventId)
+}
\ No newline at end of file
diff --git a/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/HallCRUD.kt b/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/HallCRUD.kt
new file mode 100644
index 0000000..93377f2
--- /dev/null
+++ b/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/HallCRUD.kt
@@ -0,0 +1,18 @@
+package org.breizhcamp.konter.domain.use_cases
+
+import org.breizhcamp.konter.application.requests.HallCreationReq
+import org.breizhcamp.konter.application.requests.HallPatchReq
+import org.breizhcamp.konter.domain.entities.Hall
+import org.breizhcamp.konter.domain.use_cases.ports.HallPort
+import org.springframework.stereotype.Service
+
+@Service
+class HallCRUD (
+ private val hallPort: HallPort
+) {
+ fun create(req: HallCreationReq): Hall = hallPort.create(req)
+ fun update(id: Int, req: HallPatchReq): Hall = hallPort.update(id, req)
+ fun delete(id: Int) = hallPort.delete(id)
+ fun listAll(): List = hallPort.list(null)
+ fun listByEvent(eventId: Int): List = hallPort.list(eventId)
+}
\ No newline at end of file
diff --git a/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/HallCreate.kt b/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/HallCreate.kt
deleted file mode 100644
index 9dd44df..0000000
--- a/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/HallCreate.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-package org.breizhcamp.konter.domain.use_cases
-
-import org.breizhcamp.konter.application.requests.HallCreationReq
-import org.breizhcamp.konter.domain.entities.Hall
-import org.breizhcamp.konter.domain.use_cases.ports.HallPort
-import org.springframework.stereotype.Service
-
-@Service
-class HallCreate (
- private val hallPort: HallPort
-) {
- fun createHall(req: HallCreationReq): Hall = hallPort.create(req)
-}
\ No newline at end of file
diff --git a/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/HallList.kt b/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/HallSetOrder.kt
similarity index 64%
rename from src/main/kotlin/org/breizhcamp/konter/domain/use_cases/HallList.kt
rename to src/main/kotlin/org/breizhcamp/konter/domain/use_cases/HallSetOrder.kt
index 0a851e3..ac4916c 100644
--- a/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/HallList.kt
+++ b/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/HallSetOrder.kt
@@ -5,9 +5,10 @@ import org.breizhcamp.konter.domain.use_cases.ports.HallPort
import org.springframework.stereotype.Service
@Service
-class HallList (
+class HallSetOrder(
private val hallPort: HallPort
) {
- fun listAll(): List = hallPort.list(null)
- fun listByEvent(eventId: Int): List = hallPort.list(eventId)
+
+ fun setOrder(id: Int, eventId: Int, order: Int): Hall = hallPort.setOrderInEvent(id, eventId, order)
+
}
\ No newline at end of file
diff --git a/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/ManualSessionCRUD.kt b/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/ManualSessionCRUD.kt
new file mode 100644
index 0000000..80aabd3
--- /dev/null
+++ b/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/ManualSessionCRUD.kt
@@ -0,0 +1,27 @@
+package org.breizhcamp.konter.domain.use_cases
+
+import org.breizhcamp.konter.application.requests.SessionCreationReq
+import org.breizhcamp.konter.application.requests.SessionPatchReq
+import org.breizhcamp.konter.domain.use_cases.ports.ManualSessionPort
+import org.springframework.stereotype.Service
+
+@Service
+class ManualSessionCRUD(
+ private val manualSessionPort: ManualSessionPort
+) {
+
+ fun create(request: SessionCreationReq, eventId: Int) =
+ manualSessionPort.create(request, eventId)
+
+ fun get(id: Int) =
+ manualSessionPort.getById(id)
+
+ fun list(eventId: Int) =
+ manualSessionPort.getAllByEventId(eventId)
+
+ fun update(id: Int, request: SessionPatchReq) =
+ manualSessionPort.update(id, request)
+
+ fun delete(id: Int) =
+ manualSessionPort.delete(id)
+}
\ No newline at end of file
diff --git a/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/SessionGenerateCards.kt b/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/SessionGenerateCards.kt
index d9578ae..53bb8d8 100644
--- a/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/SessionGenerateCards.kt
+++ b/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/SessionGenerateCards.kt
@@ -1,7 +1,23 @@
package org.breizhcamp.konter.domain.use_cases
-import com.itextpdf.text.*
-import com.itextpdf.text.pdf.*
+import com.itextpdf.barcodes.BarcodeEAN
+import com.itextpdf.io.font.constants.StandardFonts
+import com.itextpdf.kernel.colors.Color
+import com.itextpdf.kernel.colors.ColorConstants
+import com.itextpdf.kernel.font.PdfFont
+import com.itextpdf.kernel.font.PdfFontFactory
+import com.itextpdf.kernel.geom.PageSize
+import com.itextpdf.kernel.pdf.PdfDocument
+import com.itextpdf.kernel.pdf.PdfWriter
+import com.itextpdf.layout.Document
+import com.itextpdf.layout.element.Cell
+import com.itextpdf.layout.element.Image
+import com.itextpdf.layout.element.Paragraph
+import com.itextpdf.layout.element.Table
+import com.itextpdf.layout.properties.HorizontalAlignment
+import com.itextpdf.layout.properties.TextAlignment
+import com.itextpdf.layout.properties.UnitValue
+import com.itextpdf.layout.properties.VerticalAlignment
import mu.KotlinLogging
import org.apache.commons.lang3.StringUtils.substring
import org.breizhcamp.konter.domain.entities.enums.SessionThemeEnum
@@ -18,52 +34,85 @@ class SessionGenerateCards (
private val sessionPort: SessionPort
) {
- @Throws(DocumentException::class)
- fun generatePdf(year: Int, out: OutputStream) {
+ // Fonts used in the document
+ private val font: PdfFont = PdfFontFactory.createFont(StandardFonts.HELVETICA_BOLD)
+ private val fontColor: Color = ColorConstants.DARK_GRAY
+ private val fontSize = 8f
+ private val fontSize6 = 6f
+ private val fontSize10 = 10f
+ private val fontLight: PdfFont = PdfFontFactory.createFont(StandardFonts.HELVETICA)
+ private val fontSizeLight = 5f
- // Define document size and margins
- val document = Document(PageSize.A4, 10f, 10f, 10f, 10f)
- val writer = PdfWriter.getInstance(document, out)
-
- // Fonts used in the document
- val font = Font(Font.FontFamily.HELVETICA, 8f, Font.BOLD, BaseColor.DARK_GRAY)
- val font6 = Font(Font.FontFamily.HELVETICA, 6f, Font.BOLD, BaseColor.DARK_GRAY)
- val font10 = Font(Font.FontFamily.HELVETICA, 10f, Font.BOLD, BaseColor.DARK_GRAY)
- val fontLight = Font(Font.FontFamily.HELVETICA, 5f, Font.NORMAL, BaseColor.DARK_GRAY)
+ fun generatePdf(eventId: Int, out: OutputStream) {
- document.open()
+ // Define document size and margins
+ val writer = PdfWriter(out)
+ val pdfDoc = PdfDocument(writer).apply {
+ defaultPageSize = PageSize.A4
+ }
+ val document = Document(pdfDoc).apply {
+ setFont(font)
+ setFontColor(fontColor)
+ setMargins(10f, 10f, 10f, 10f)
+ }
// The document only has a full width table with three columns
- val table = PdfPTable(3)
- table.widthPercentage = 100f
+ val table = Table(3).apply {
+ width = UnitValue.createPercentValue(100f)
+ setPadding(0f)
+ }
// Map from a SessionTheme to a color
- val bgTracksColor: MutableMap = EnumMap(SessionThemeEnum::class.java)
+ val bgTracksColor: MutableMap = EnumMap(SessionThemeEnum::class.java)
+
+ // Create a card for each session in the given event
+ val sessions = sessionPort.getAllByEventId(eventId, true)
- // Create a card for each session in the given year
- val sessions = sessionPort.getAllByEventYear(year)
sessions.forEach {
// A card is a full width table with two columns
- val innerTable = PdfPTable(2)
- innerTable.widthPercentage = 100f
+ val innerTable = Table(2).apply {
+ isKeepTogether = true
+ width = UnitValue.createPercentValue(100f)
+ setMargin(0f)
+ }
// One cell contains the format
- val formatPh = Phrase(Chunk(it.format.getLabel(), font))
- val format = PdfPCell(formatPh)
+ val formatPh = baseParagraph().apply {
+ setPaddingBottom(0f)
+ setVerticalAlignment(VerticalAlignment.BOTTOM)
+ add(it.format.getLabel())
+ }
+ val format = Cell().apply {
+ width = UnitValue.createPercentValue(50f)
+ setPaddingTop(0f)
+ setPaddingBottom(0f)
+ add(formatPh)
+ }
innerTable.addCell(format)
// One cell contains the theme and has a corresponding background color
- val track = PdfPCell(Phrase(substring(it.theme.getLabel(), 0, 20), font))
- track.horizontalAlignment = Element.ALIGN_RIGHT
- track.backgroundColor = bgTracksColor.computeIfAbsent(it.theme) { getColor(bgTracksColor.size + 1) }
- innerTable.addCell(track)
+ val themePh = baseParagraph().apply {
+ setTextAlignment(TextAlignment.RIGHT)
+ setVerticalAlignment(VerticalAlignment.BOTTOM)
+ add(substring(it.theme.getLabel(), 0, 20))
+ }
+ val theme = Cell().apply {
+ setHorizontalAlignment(HorizontalAlignment.RIGHT)
+ setBackgroundColor(bgTracksColor.computeIfAbsent(it.theme) { getColor(bgTracksColor.size + 1) })
+ setPaddingTop(0f)
+ setPaddingBottom(0f)
+ add(themePh)
+ }
+ innerTable.addCell(theme)
// The second row has a full width cell
- val centralCell = PdfPCell()
- centralCell.colspan = 2
- centralCell.horizontalAlignment = Element.ALIGN_JUSTIFIED
- centralCell.fixedHeight = 100.0f
+ val centralCell = Cell(1, 2).apply {
+ isKeepTogether = true
+ height = UnitValue.createPointValue(95.0f)
+ setHorizontalAlignment(HorizontalAlignment.LEFT)
+ setPaddingBottom(0f)
+ }
// Get the first speaker's short name
// Example : Claire LUCAS -> C. LUCAS
@@ -81,61 +130,101 @@ class SessionGenerateCards (
}
// Add the computed speaker's name to the central cell
- val spk = Paragraph(speaker, font)
- spk.alignment = Paragraph.ALIGN_CENTER
- centralCell.addElement(spk)
+ val spk = baseParagraph().apply {
+ setPadding(0f)
+ setMarginTop(3f)
+ setMarginBottom(1f)
+ setTextAlignment(TextAlignment.CENTER)
+ add(speaker)
+ }
+
+ centralCell.add(spk)
// Add session's title to the central cell
- val ttl = Paragraph(10f, it.title, font10)
- ttl.alignment = Paragraph.ALIGN_CENTER
- ttl.spacingBefore = 3f
- ttl.keepTogether = true
- centralCell.addElement(ttl)
+ val ttl = baseParagraph().apply {
+ isKeepTogether = true
+ setFontSize(fontSize10)
+ setFixedLeading(10f)
+ setTextAlignment(TextAlignment.CENTER)
+ setMarginBottom(0f)
+ add(it.title)
+ }
+ centralCell.add(ttl)
// Add the description to the central cell (if it exceeds 400 chars, it gets truncated to 400)
- val description: Paragraph
val sizeMaxDesc = 400
val desc: String = it.description
- description = if (desc.length > sizeMaxDesc) {
- Paragraph(Paragraph(desc.substring(0, sizeMaxDesc).replace("\n\n".toRegex(), "\n"), font6))
- } else {
- Paragraph(Paragraph(desc.replace("\n\n".toRegex(), "\n"), font6))
+ val description = baseParagraph().apply {
+ setFontSize(fontSize6)
+ setTextAlignment(TextAlignment.JUSTIFIED)
+ setMarginTop(3f)
+ setMarginBottom(0f)
}
- description.alignment = Paragraph.ALIGN_JUSTIFIED
- description.spacingBefore = 3f
- centralCell.addElement(description)
+
+ // Truncate the description if it exceeds 400 characters
+ description.add(if (desc.length > sizeMaxDesc) {
+ desc.substring(0, sizeMaxDesc).replace("\n\n".toRegex(), "\n")
+ } else {
+ desc.replace("\n\n".toRegex(), "\n")
+ })
+
+ centralCell.add(description)
innerTable.addCell(centralCell)
// Create the barcode with the session's id
- val barcode = BarcodeEAN()
- barcode.codeType = Barcode.EAN8
val code = "9" + it.id.toString().padStart(6, '0')
- barcode.code = code + BarcodeEAN.calculateEANParity(code)
- barcode.barHeight = 15f
- barcode.font = null
+ val barcode = BarcodeEAN(pdfDoc).apply {
+ this.code = code + BarcodeEAN.calculateEANParity(code)
+ codeType = BarcodeEAN.EAN8
+ barHeight = 13f
+ font = null
+ }
+ sessionPort.addBarcode(it.id, barcode.code)
// Add the barcode at the third row, first cell
- val barcodePh = Phrase()
- val barCell = PdfPCell(barcodePh)
- barcodePh.add(Chunk(barcode.createImageWithBarcode(writer.directContent, null, null), 0f, 0f))
- barcodePh.add(Chunk(" " + it.id, fontLight))
- barCell.horizontalAlignment = Element.ALIGN_LEFT
- barCell.verticalAlignment = Element.ALIGN_BOTTOM
- barCell.setPadding(1f)
- barCell.paddingLeft = 8f
- barCell.fixedHeight = 15f
+ val barcodePh = baseParagraph().apply {
+ setMargin(0f)
+ setPadding(0f)
+ setFont(fontLight)
+ setFontSize(fontSizeLight)
+ add(Image(barcode.createFormXObject(pdfDoc)))
+ add(" " + it.id)
+ }
+
+ val barCell = Cell().apply {
+ setHorizontalAlignment(HorizontalAlignment.LEFT)
+ setVerticalAlignment(VerticalAlignment.BOTTOM)
+ setPadding(1f)
+ setPaddingBottom(0f)
+ setPaddingLeft(8f)
+ height = UnitValue.createPointValue(15f)
+ add(barcodePh)
+ }
+
innerTable.addCell(barCell)
// Add the session's evaluation result to the last cell
- val note = PdfPCell(Phrase("note : " + (it.rating?.toString() ?: "N/A"), font))
- note.horizontalAlignment = Element.ALIGN_LEFT
- note.verticalAlignment = Element.ALIGN_BOTTOM
+ val notePh = baseParagraph().apply {
+ setMarginBottom(0f)
+ setMarginTop(0f)
+ setVerticalAlignment(VerticalAlignment.BOTTOM)
+ add("note : " + (it.rating?.toString() ?: "N/A"))
+ }
+ val note = Cell().apply {
+ setHorizontalAlignment(HorizontalAlignment.LEFT)
+ setVerticalAlignment(VerticalAlignment.BOTTOM)
+ setMaxHeight(barCell.height)
+ setPaddingBottom(0f)
+ setPaddingTop(0f)
+ add(notePh)
+ }
+
innerTable.addCell(note)
table.addCell(innerTable)
}
// Fill the last row of the table with empty cells to get to a multiple of three
- for (i in 0 until sessions.size % 3) {
+ for (i in 0 until 3 - sessions.size % 3) {
table.addCell("")
}
@@ -143,21 +232,27 @@ class SessionGenerateCards (
document.add(table)
document.close()
- logger.info { "Generated Session cards for year $year" }
+ logger.info { "Generated Session cards for Event:$eventId" }
}
- private fun getColor(idx: Int): BaseColor {
+ private fun getColor(idx: Int): Color {
return when (idx) {
- 1 -> BaseColor.MAGENTA
- 2 -> BaseColor.PINK
- 3 -> BaseColor.YELLOW
- 4 -> BaseColor.GREEN
- 5 -> BaseColor.ORANGE
- 6 -> BaseColor.GRAY
- 7 -> BaseColor.RED
- 8 -> BaseColor.CYAN
- 9 -> BaseColor.LIGHT_GRAY
- else -> BaseColor.WHITE
+ 1 -> ColorConstants.MAGENTA
+ 2 -> ColorConstants.PINK
+ 3 -> ColorConstants.YELLOW
+ 4 -> ColorConstants.GREEN
+ 5 -> ColorConstants.ORANGE
+ 6 -> ColorConstants.GRAY
+ 7 -> ColorConstants.RED
+ 8 -> ColorConstants.CYAN
+ 9 -> ColorConstants.LIGHT_GRAY
+ else -> ColorConstants.WHITE
}
}
+
+ private fun baseParagraph() = Paragraph().apply {
+ setFont(font)
+ setFontSize(fontSize)
+ setFontColor(fontColor)
+ }
}
\ No newline at end of file
diff --git a/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/SessionImport.kt b/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/SessionImport.kt
index 8ec10e4..ffcdd1c 100644
--- a/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/SessionImport.kt
+++ b/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/SessionImport.kt
@@ -2,7 +2,6 @@ package org.breizhcamp.konter.domain.use_cases
import mu.KotlinLogging
import org.apache.commons.csv.CSVFormat
-import org.breizhcamp.konter.application.requests.EventCreationReq
import org.breizhcamp.konter.domain.entities.Evaluation
import org.breizhcamp.konter.domain.entities.Event
import org.breizhcamp.konter.domain.entities.Session
@@ -31,18 +30,19 @@ class SessionImport (
private val kalonPort: KalonPort,
) {
- fun importCsv(year: Int, file: InputStream) {
- if (!eventPort.existsByYear(year)) {
- logger.info { "No Event with year=$year found, importing from Kalon" }
+ fun importCsv(eventId: Int, file: InputStream) {
+ if (!eventPort.existsById(eventId)) {
+ logger.info { "No Event with id=$eventId found, importing from Kalon" }
eventPort.save(kalonPort.getEvents())
}
- if (!eventPort.existsByYear(year)) {
- logger.info { "No Event with year=$year found, creating one" }
+ if (!eventPort.existsById(eventId)) {
+ logger.info { "No Event with id=$eventId found, exiting" }
- eventPort.create(EventCreationReq(year))
+ file.close()
+ return
}
- val event: Event = eventPort.getByYear(year)
+ val event: Event = eventPort.getById(eventId)
val sessions = CSVFormat.Builder.create().apply {
setIgnoreSurroundingSpaces(true)
@@ -67,17 +67,15 @@ class SessionImport (
.withLocale(Locale.FRENCH)),
ownerNotes = it[11].trim(),
event = event,
- hall = null,
- beginning = null,
- end = null,
videoURL = null,
rating = null,
+ slot = null,
)
}
logger.info { "Saving [${sessions.size}] Sessions" }
- sessions.forEach(sessionPort::save)
+ sessions.forEach(sessionPort::import)
}
fun importEvaluationCsv(file: InputStream) {
diff --git a/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/SessionList.kt b/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/SessionList.kt
index 5fbc6bf..33d5a9d 100644
--- a/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/SessionList.kt
+++ b/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/SessionList.kt
@@ -9,6 +9,6 @@ import org.springframework.stereotype.Service
class SessionList(
private val sessionPort: SessionPort
) {
- fun list(year: Int): List = sessionPort.getAllByEventYear(year)
- fun filter(year: Int, filter: SessionFilter): List = sessionPort.filterByEventYear(year, filter)
+ fun list(eventId: Int): List = sessionPort.getAllByEventId(eventId, false)
+ fun filter(eventId: Int, filter: SessionFilter): List = sessionPort.filterByEventId(eventId, filter)
}
\ No newline at end of file
diff --git a/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/SlotAssociateHall.kt b/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/SlotAssociateHall.kt
new file mode 100644
index 0000000..7681928
--- /dev/null
+++ b/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/SlotAssociateHall.kt
@@ -0,0 +1,16 @@
+package org.breizhcamp.konter.domain.use_cases
+
+import org.breizhcamp.konter.domain.entities.Slot
+import org.breizhcamp.konter.domain.use_cases.ports.SlotPort
+import org.springframework.stereotype.Service
+import java.util.*
+
+@Service
+class SlotAssociateHall (
+ private val slotPort: SlotPort
+) {
+
+ fun associate(id: UUID, eventId: Int, hallId: Int): Slot = slotPort.associateHall(id, eventId, hallId)
+ fun dissociate(id: UUID, hallId: Int) = slotPort.dissociateHall(id, hallId)
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/SlotCRUD.kt b/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/SlotCRUD.kt
new file mode 100644
index 0000000..80b7a08
--- /dev/null
+++ b/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/SlotCRUD.kt
@@ -0,0 +1,22 @@
+package org.breizhcamp.konter.domain.use_cases
+
+import org.breizhcamp.konter.application.requests.SlotCreationReq
+import org.breizhcamp.konter.application.requests.SlotPatchReq
+import org.breizhcamp.konter.domain.entities.Hall
+import org.breizhcamp.konter.domain.entities.Slot
+import org.breizhcamp.konter.domain.use_cases.ports.SlotPort
+import org.springframework.stereotype.Service
+import java.util.*
+
+@Service
+class SlotCRUD (
+ private val slotPort: SlotPort
+) {
+
+ fun create(eventId: Int, req: SlotCreationReq): Slot = slotPort.create(eventId, req)
+ fun get(id: UUID): Slot = slotPort.getById(id)
+ fun list(eventId: Int): Map>> = slotPort.getProgram(eventId)
+ fun update(id: UUID, request: SlotPatchReq): Slot = slotPort.update(id, request)
+ fun delete(id: UUID) = slotPort.remove(id)
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/SlotGenerateProgram.kt b/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/SlotGenerateProgram.kt
new file mode 100644
index 0000000..16ef326
--- /dev/null
+++ b/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/SlotGenerateProgram.kt
@@ -0,0 +1,195 @@
+package org.breizhcamp.konter.domain.use_cases
+
+import com.itextpdf.barcodes.BarcodeEAN
+import com.itextpdf.io.font.constants.StandardFonts
+import com.itextpdf.kernel.font.PdfFontFactory
+import com.itextpdf.kernel.geom.PageSize
+import com.itextpdf.kernel.pdf.PdfDocument
+import com.itextpdf.kernel.pdf.PdfWriter
+import com.itextpdf.layout.Document
+import com.itextpdf.layout.element.Cell
+import com.itextpdf.layout.element.Image
+import com.itextpdf.layout.element.Paragraph
+import com.itextpdf.layout.element.Table
+import com.itextpdf.layout.layout.LayoutArea
+import com.itextpdf.layout.layout.LayoutContext
+import com.itextpdf.layout.properties.HorizontalAlignment
+import com.itextpdf.layout.properties.TextAlignment
+import com.itextpdf.layout.properties.UnitValue
+import com.itextpdf.layout.properties.VerticalAlignment
+import mu.KotlinLogging
+import org.breizhcamp.konter.domain.use_cases.ports.HallPort
+import org.breizhcamp.konter.domain.use_cases.ports.SlotPort
+import org.springframework.stereotype.Service
+import java.io.OutputStream
+import java.time.Duration
+import java.time.LocalTime
+
+private val logger = KotlinLogging.logger { }
+
+@Service
+class SlotGenerateProgram (
+ private val slotPort: SlotPort,
+ private val hallPort: HallPort
+) {
+
+ fun generateEmptyProgramPdf(eventId: Int, out: OutputStream) {
+ val font = PdfFontFactory.createFont(StandardFonts.HELVETICA)
+ val bold = PdfFontFactory.createFont(StandardFonts.HELVETICA_BOLD)
+
+ val titleSize = 30f
+ val textSize = 13f
+
+ val segmentMinutes = 15L
+
+ // Define document size, orientation and margins
+ val writer = PdfWriter(out)
+ val pdfDoc = PdfDocument(writer)
+ pdfDoc.defaultPageSize = PageSize.A3
+ val document = Document(pdfDoc)
+ document.setFont(font)
+ document.setFontSize(textSize)
+ document.setMargins(10f, 10f, 10f, 10f)
+
+
+ val program = slotPort.getProgram(eventId).toSortedMap()
+ val halls = hallPort.list(eventId)
+
+ program.onEachIndexed { pageNumber, (day, tracks) ->
+ // Set title
+ val title = Paragraph().setTextAlignment(TextAlignment.CENTER).setFont(bold).setFontSize(titleSize).add("Programme jour $day")
+ document.add(title)
+
+ val preRenderer = title.createRendererSubTree().setParent(document.renderer)
+ val preRender = preRenderer.layout(LayoutContext(LayoutArea(pageNumber, PageSize.A3)))
+ val remainingHeight = PageSize.A3.height - preRender.occupiedArea.bBox.height
+
+ // Add a table for each day, with one column for each hall
+ val table = Table(halls.size).apply {
+ width = UnitValue.createPercentValue(100f)
+ isKeepTogether = true
+ height = UnitValue.createPointValue(remainingHeight)
+ setMarginTop(10f)
+ }
+
+ // Table header
+ halls.forEach {
+ val hallName = Paragraph().apply {
+ setFont(bold)
+ setTextAlignment(TextAlignment.CENTER)
+ add(it.name)
+ }
+
+ val hallCell = Cell().apply {
+ setHorizontalAlignment(HorizontalAlignment.CENTER)
+ setVerticalAlignment(VerticalAlignment.MIDDLE)
+ height = UnitValue.createPointValue(25f)
+ add(hallName)
+ }
+
+ table.addCell(hallCell)
+ }
+
+ val allSlots = tracks.flatMap { (_, slots) -> slots }
+
+ if (allSlots.isNotEmpty()) {
+ var minTime = allSlots.minOf { it.start }
+ minTime -= Duration.ofMinutes(minTime.minute.mod(segmentMinutes))
+
+ var maxTime = allSlots.maxOf { it.start.plus(it.duration) }
+ if (maxTime.minute.mod(15) != 0) {
+ maxTime += Duration.ofMinutes(15 - maxTime.minute.mod(segmentMinutes))
+ }
+
+ val segments = minTime.until(maxTime, java.time.temporal.ChronoUnit.MINUTES).div(segmentMinutes)
+
+ for (i in 0..
+ if (tracks.containsKey(hall)) {
+ val slot = requireNotNull(tracks[hall])
+ .find {
+ it.start == time || (
+ it.start.isAfter(time) &&
+ it.start.isBefore(time.plus(Duration.ofMinutes(segmentMinutes))))
+ }
+ if (slot == null) {
+ val slotsOverTime = requireNotNull(tracks[hall])
+ .filter { it.start.isBefore(time) && it.start.plus(it.duration).isAfter(time) }
+ if (slotsOverTime.isNotEmpty()) {
+ occupiedColumns += slotsOverTime.first().span
+ }
+ } else {
+ var rowspan = slot.duration.toMinutes()
+ if (rowspan.mod(segmentMinutes) != 0L) {
+ rowspan += (segmentMinutes - rowspan.mod(segmentMinutes))
+ }
+
+ if (occupiedColumns < index) {
+ for (j in 0..)
- fun create(request: EventCreationReq): Int
+ fun exportTalks(id: Int): List
}
\ No newline at end of file
diff --git a/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/ports/HallPort.kt b/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/ports/HallPort.kt
index 74078a2..a83e321 100644
--- a/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/ports/HallPort.kt
+++ b/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/ports/HallPort.kt
@@ -1,11 +1,18 @@
package org.breizhcamp.konter.domain.use_cases.ports
import org.breizhcamp.konter.application.requests.HallCreationReq
+import org.breizhcamp.konter.application.requests.HallPatchReq
import org.breizhcamp.konter.domain.entities.Hall
interface HallPort {
+ fun get(id: Int): Hall
fun list(eventId: Int?): List
fun create(req: HallCreationReq): Hall
+ fun associateToEvent(id: Int, eventId: Int, order: Int): Hall
+ fun dissociateFromEvent(id: Int, eventId: Int): Hall
+ fun setOrderInEvent(id: Int, eventId: Int, order: Int): Hall
+ fun update(id: Int, req: HallPatchReq): Hall
+ fun delete(id: Int)
}
\ No newline at end of file
diff --git a/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/ports/ManualSessionPort.kt b/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/ports/ManualSessionPort.kt
new file mode 100644
index 0000000..9cc51a6
--- /dev/null
+++ b/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/ports/ManualSessionPort.kt
@@ -0,0 +1,15 @@
+package org.breizhcamp.konter.domain.use_cases.ports
+
+import org.breizhcamp.konter.application.requests.SessionCreationReq
+import org.breizhcamp.konter.application.requests.SessionPatchReq
+import org.breizhcamp.konter.domain.entities.ManualSession
+
+interface ManualSessionPort {
+
+ fun create(request: SessionCreationReq, eventId: Int): ManualSession
+ fun getById(id: Int): ManualSession
+ fun getAllByEventId(eventId: Int): List
+ fun update(id: Int, request: SessionPatchReq): ManualSession
+ fun delete(id: Int)
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/ports/SessionPort.kt b/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/ports/SessionPort.kt
index 69399fa..0a2ea2b 100644
--- a/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/ports/SessionPort.kt
+++ b/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/ports/SessionPort.kt
@@ -3,13 +3,17 @@ package org.breizhcamp.konter.domain.use_cases.ports
import org.breizhcamp.konter.domain.entities.Evaluation
import org.breizhcamp.konter.domain.entities.Session
import org.breizhcamp.konter.domain.entities.SessionFilter
+import java.util.*
interface SessionPort {
fun getById(id: Int): Session
- fun save(session: Session)
- fun getAllByEventYear(year: Int): List
+ fun import(session: Session)
+ fun getAllByEventId(eventId: Int, sortByFormat: Boolean): List
fun saveEvaluation(evaluation: Evaluation)
- fun filterByEventYear(year: Int, filter: SessionFilter): List
+ fun filterByEventId(eventId: Int, filter: SessionFilter): List
+ fun addBarcode(id: Int, barcode: String)
+ fun setSlotById(id: Int, slotId: UUID): Session
+ fun setSlotByBarcode(id: Int, barcode: String): Session
}
\ No newline at end of file
diff --git a/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/ports/SlotPort.kt b/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/ports/SlotPort.kt
new file mode 100644
index 0000000..f0515e1
--- /dev/null
+++ b/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/ports/SlotPort.kt
@@ -0,0 +1,21 @@
+package org.breizhcamp.konter.domain.use_cases.ports
+
+import org.breizhcamp.konter.application.requests.SlotCreationReq
+import org.breizhcamp.konter.application.requests.SlotPatchReq
+import org.breizhcamp.konter.domain.entities.Hall
+import org.breizhcamp.konter.domain.entities.Slot
+import java.util.*
+
+interface SlotPort {
+
+ @Throws
+ fun create(eventId: Int, req: SlotCreationReq): Slot
+ fun getById(id: UUID): Slot
+ fun getProgram(eventId: Int): Map>>
+ fun update(id: UUID, request: SlotPatchReq): Slot
+ fun remove(id: UUID)
+ @Throws
+ fun associateHall(id: UUID, eventId: Int, hallId: Int): Slot
+ fun dissociateHall(id: UUID, hallId: Int)
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/ports/SpeakerPort.kt b/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/ports/SpeakerPort.kt
index e031b35..9f1727b 100644
--- a/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/ports/SpeakerPort.kt
+++ b/src/main/kotlin/org/breizhcamp/konter/domain/use_cases/ports/SpeakerPort.kt
@@ -1,13 +1,11 @@
package org.breizhcamp.konter.domain.use_cases.ports
import org.breizhcamp.konter.domain.entities.Speaker
-import org.breizhcamp.konter.domain.entities.SpeakerFilter
import java.util.*
interface SpeakerPort {
fun list(): List
- fun filter(filter: SpeakerFilter): List
fun get(id: UUID): Speaker
fun getByNameAndEmail(name: String, email: String): Speaker
fun save(speaker: Speaker)
diff --git a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/EventAdapter.kt b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/EventAdapter.kt
index e902665..25eb870 100644
--- a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/EventAdapter.kt
+++ b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/EventAdapter.kt
@@ -1,33 +1,119 @@
package org.breizhcamp.konter.infrastructure.db
-import org.breizhcamp.konter.application.requests.EventCreationReq
import org.breizhcamp.konter.domain.entities.Event
+import org.breizhcamp.konter.domain.entities.Talk
+import org.breizhcamp.konter.domain.entities.exceptions.HallNotFoundException
import org.breizhcamp.konter.domain.use_cases.ports.EventPort
import org.breizhcamp.konter.infrastructure.db.mappers.toDB
import org.breizhcamp.konter.infrastructure.db.mappers.toEvent
+import org.breizhcamp.konter.infrastructure.db.mappers.toHall
+import org.breizhcamp.konter.infrastructure.db.mappers.toSpeaker
+import org.breizhcamp.konter.infrastructure.db.model.EventDB
+import org.breizhcamp.konter.infrastructure.db.model.HallDB
+import org.breizhcamp.konter.infrastructure.db.model.SlotDB
import org.breizhcamp.konter.infrastructure.db.repos.EventRepo
+import org.breizhcamp.konter.infrastructure.db.repos.HallRepo
import org.springframework.stereotype.Component
+import java.time.LocalDate
+import java.time.temporal.ChronoUnit
@Component
class EventAdapter (
- private val eventRepo: EventRepo
+ private val eventRepo: EventRepo,
+ private val hallRepo: HallRepo
): EventPort {
- override fun existsByYear(year: Int): Boolean =
- eventRepo.existsByYear(year)
+ override fun existsById(id: Int): Boolean =
+ eventRepo.existsById(id)
+ @Throws
override fun getById(id: Int): Event =
eventRepo.findById(id).get().toEvent()
- override fun getByYear(year: Int): Event =
- eventRepo.findByYear(year).toEvent()
-
override fun save(event: Event) {
- eventRepo.save(event.toDB())
+ val oldEvent = eventRepo.findById(event.id)
+ var eventToSave = event.toDB()
+ if (oldEvent.isPresent) {
+ val halls = oldEvent.get().halls
+ eventToSave = eventToSave.copy(halls = halls)
+ }
+ eventRepo.save(eventToSave)
}
override fun save(events: List) = events.forEach { this.save(it) }
+ override fun exportTalks(id: Int): List {
+ val event = eventRepo.findById(id).get()
+ val halls = hallRepo.getAllByAvailableEventId(id)
+ val slots = eventRepo.listSlotsHeld(id)
+
+ val manualSessionSlots = slots.filter { it.manualSession != null && it.manualSession.speakers.isNotEmpty() }
+ val sessionSlots = slots.filter { it.session != null && it.manualSession == null }
+
+ return manualSessionSlots
+ .asSequence()
+ .map { it.toManualTalk(event, halls) }
+ .plus(
+ sessionSlots
+ .asSequence()
+ .map { it.toImportTalk(event, halls) }
+ )
+ .toList()
+ }
+
+}
+
+fun SlotDB.toManualTalk(event: EventDB, availableHalls: List): Talk {
+ val session = requireNotNull(manualSession)
+ { "Slot toManualTalk called on slot with a null manualSession : Slot:$id" }
+ val halls = availableHalls.filter { it in halls }
+
+ if (halls.isEmpty()) {
+ throw HallNotFoundException("No Hall in Slot:${id} corresponds to an hall in Event:${event.id}")
+ }
+ val date = computeDate(event)
+
+ return Talk(
+ id = session.id,
+ name = session.title,
+ eventStart = start.atDate(date),
+ eventEnd = start.plus(duration).atDate(date),
+ eventType = session.theme,
+ format = session.format,
+ hall = halls.first().toHall(),
+ speakers = session.speakers.map { it.toSpeaker() },
+ videoUrl = null,
+ filesUrl = null,
+ slidesUrl = null,
+ description = session.description
+ )
+}
+
+fun SlotDB.toImportTalk(event: EventDB, availableHalls: List): Talk {
+ val session = requireNotNull(session)
+ { "Slot toTalk called on slot with a null session : Slot:$id" }
+ val halls = availableHalls.filter { it in halls }
+
+ if (halls.isEmpty()) {
+ throw HallNotFoundException("No Hall in Slot:${id} corresponds to an hall in Event:${event.id}")
+ }
+ val date = computeDate(event)
- override fun create(request: EventCreationReq): Int =
- eventRepo.createEvent(request.year)
+ return Talk(
+ id = session.id,
+ name = session.title,
+ eventStart = start.atDate(date),
+ eventEnd = start.plus(duration).atDate(date),
+ eventType = session.theme,
+ format = session.format,
+ hall = halls.first().toHall(),
+ speakers = session.speakers.map { it.toSpeaker() },
+ videoUrl = null,
+ filesUrl = null,
+ slidesUrl = null,
+ description = session.description
+ )
+}
-}
\ No newline at end of file
+fun SlotDB.computeDate(event: EventDB): LocalDate =
+ requireNotNull(event.begin)
+ { "Beginning of event should have been set before exporting talks" }
+ .plus((day - 1).toLong(), ChronoUnit.DAYS)
diff --git a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/HallAdapter.kt b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/HallAdapter.kt
index ea7f119..79723fd 100644
--- a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/HallAdapter.kt
+++ b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/HallAdapter.kt
@@ -1,6 +1,8 @@
package org.breizhcamp.konter.infrastructure.db
+import jakarta.transaction.Transactional
import org.breizhcamp.konter.application.requests.HallCreationReq
+import org.breizhcamp.konter.application.requests.HallPatchReq
import org.breizhcamp.konter.domain.entities.Hall
import org.breizhcamp.konter.domain.use_cases.ports.HallPort
import org.breizhcamp.konter.infrastructure.db.mappers.toHall
@@ -11,6 +13,9 @@ import org.springframework.stereotype.Component
class HallAdapter (
private val hallRepo: HallRepo
) : HallPort{
+ override fun get(id: Int): Hall =
+ hallRepo.findById(id).get().toHall()
+
override fun list(eventId: Int?): List {
eventId?.let {
return hallRepo.getAllByAvailableEventId(eventId)
@@ -19,9 +24,42 @@ class HallAdapter (
return hallRepo.findAll().map { it.toHall() }
}
+ @Transactional
override fun create(req: HallCreationReq): Hall {
val id = hallRepo.create(req.name)
+ if (req.trackId != null) {
+ hallRepo.addTrackIdToHall(id, req.trackId)
+ }
+ return hallRepo.findById(id).get().toHall()
+ }
+
+ @Transactional
+ override fun associateToEvent(id: Int, eventId: Int, order: Int): Hall {
+ hallRepo.associateToEvent(id, eventId, order)
+ return hallRepo.findById(id).get().toHall()
+ }
+
+ @Transactional
+ override fun dissociateFromEvent(id: Int, eventId: Int): Hall {
+ val hallsAfter = hallRepo.getAllByOrderAfterHallInEvent(eventId, id)
+ hallRepo.dissociateFromEvent(id, eventId)
+ hallsAfter.forEach { hallRepo.decreaseOrder(it.id, eventId) }
+ return hallRepo.findById(id).get().toHall()
+ }
+
+ @Transactional
+ override fun setOrderInEvent(id: Int, eventId: Int, order: Int): Hall {
+ hallRepo.setOrderInEvent(id, eventId, order)
return hallRepo.findById(id).get().toHall()
}
+ @Transactional
+ override fun update(id: Int, req: HallPatchReq): Hall {
+ val hall = hallRepo.findById(id).get().copy(name = req.name, trackId = req.trackId)
+ return hallRepo.save(hall).toHall()
+ }
+
+ @Transactional
+ override fun delete(id: Int) = hallRepo.deleteById(id)
+
}
\ No newline at end of file
diff --git a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/ManualSessionAdapter.kt b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/ManualSessionAdapter.kt
new file mode 100644
index 0000000..bc8e3ed
--- /dev/null
+++ b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/ManualSessionAdapter.kt
@@ -0,0 +1,52 @@
+package org.breizhcamp.konter.infrastructure.db
+
+import jakarta.transaction.Transactional
+import org.breizhcamp.konter.application.requests.SessionCreationReq
+import org.breizhcamp.konter.application.requests.SessionPatchReq
+import org.breizhcamp.konter.domain.entities.ManualSession
+import org.breizhcamp.konter.domain.use_cases.ports.ManualSessionPort
+import org.breizhcamp.konter.infrastructure.db.mappers.toManualSession
+import org.breizhcamp.konter.infrastructure.db.repos.ManualSessionRepo
+import org.springframework.stereotype.Component
+
+
+@Component
+class ManualSessionAdapter(
+ private val manualSessionRepo: ManualSessionRepo
+) : ManualSessionPort {
+
+ @Transactional
+ override fun create(request: SessionCreationReq, eventId: Int): ManualSession {
+ val id = manualSessionRepo.create(
+ eventId,
+ request.title,
+ request.description,
+ request.format,
+ request.theme
+ )
+
+ return getById(id)
+ }
+
+ override fun getById(id: Int): ManualSession =
+ manualSessionRepo.findById(id).get().toManualSession()
+
+ override fun getAllByEventId(eventId: Int): List =
+ manualSessionRepo.getAllByEventId(eventId).map { it.toManualSession() }
+
+ @Transactional
+ override fun update(id: Int, request: SessionPatchReq): ManualSession {
+ var session = manualSessionRepo.findById(id).get()
+
+ request.title?.let { session = session.copy(title = it) }
+ request.description?.let { session = session.copy(description = it) }
+ request.format?.let { session = session.copy(format = it) }
+ request.theme?.let { session = session.copy(theme = it) }
+
+ return manualSessionRepo.save(session).toManualSession()
+ }
+
+ @Transactional
+ override fun delete(id: Int) =
+ manualSessionRepo.deleteById(id)
+}
\ No newline at end of file
diff --git a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/SessionAdapter.kt b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/SessionAdapter.kt
index c5c7aa3..4239592 100644
--- a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/SessionAdapter.kt
+++ b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/SessionAdapter.kt
@@ -1,13 +1,16 @@
package org.breizhcamp.konter.infrastructure.db
+import jakarta.transaction.Transactional
import org.breizhcamp.konter.domain.entities.Evaluation
import org.breizhcamp.konter.domain.entities.Session
import org.breizhcamp.konter.domain.entities.SessionFilter
import org.breizhcamp.konter.domain.use_cases.ports.SessionPort
import org.breizhcamp.konter.infrastructure.db.mappers.toDB
import org.breizhcamp.konter.infrastructure.db.mappers.toSession
+import org.breizhcamp.konter.infrastructure.db.model.SessionDB
import org.breizhcamp.konter.infrastructure.db.repos.SessionRepo
import org.springframework.stereotype.Component
+import java.util.*
@Component
class SessionAdapter (
@@ -16,18 +19,50 @@ class SessionAdapter (
override fun getById(id: Int): Session =
sessionRepo.findById(id).get().toSession()
- override fun save(session: Session) {
- sessionRepo.save(session.toDB())
+ override fun import(session: Session) {
+ var toSave: SessionDB = session.toDB()
+
+ if (sessionRepo.existsById(session.id)) {
+ val saved = sessionRepo.findById(session.id).get()
+ toSave = toSave.copy(
+ slot = saved.slot,
+ videoURL = saved.videoURL,
+ barcode = saved.barcode,
+ )
+ }
+
+ sessionRepo.save(toSave)
}
- override fun getAllByEventYear(year: Int): List =
- sessionRepo.filter(year, SessionFilter.empty()).map { it.toSession() }
+ override fun getAllByEventId(eventId: Int, sortByFormat: Boolean): List =
+ sessionRepo.filter(eventId, SessionFilter.empty(), sortByFormat).map { it.toSession() }
+ @Transactional
override fun saveEvaluation(evaluation: Evaluation) {
val session = evaluation.session.copy(rating = evaluation.rating)
sessionRepo.save(session.toDB())
}
- override fun filterByEventYear(year: Int, filter: SessionFilter): List =
- sessionRepo.filter(year, filter).map { it.toSession() }
+ override fun filterByEventId(eventId: Int, filter: SessionFilter): List =
+ sessionRepo.filter(eventId, filter, false).map { it.toSession() }
+
+ @Transactional
+ override fun addBarcode(id: Int, barcode: String) {
+ sessionRepo.addBarcode(id, barcode)
+ }
+
+ @Transactional
+ override fun setSlotById(id: Int, slotId: UUID): Session {
+ if (sessionRepo.hasSlotById(id)) {
+ sessionRepo.removeSlotById(id)
+ }
+ sessionRepo.setSlotById(id, slotId)
+ return this.getById(id)
+ }
+
+ @Transactional
+ override fun setSlotByBarcode(id: Int, barcode: String): Session {
+ val slotId = sessionRepo.getSlotIdByBarcode(barcode)
+ return setSlotById(id, slotId)
+ }
}
\ No newline at end of file
diff --git a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/SlotAdapter.kt b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/SlotAdapter.kt
new file mode 100644
index 0000000..432a7d1
--- /dev/null
+++ b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/SlotAdapter.kt
@@ -0,0 +1,217 @@
+package org.breizhcamp.konter.infrastructure.db
+
+import com.itextpdf.barcodes.BarcodeEAN
+import jakarta.transaction.Transactional
+import org.breizhcamp.konter.application.requests.SlotCreationReq
+import org.breizhcamp.konter.application.requests.SlotPatchReq
+import org.breizhcamp.konter.domain.entities.Hall
+import org.breizhcamp.konter.domain.entities.Slot
+import org.breizhcamp.konter.domain.entities.exceptions.HallNotFoundException
+import org.breizhcamp.konter.domain.entities.exceptions.TimeConflictException
+import org.breizhcamp.konter.domain.use_cases.ports.SlotPort
+import org.breizhcamp.konter.infrastructure.db.mappers.*
+import org.breizhcamp.konter.infrastructure.db.model.HallDB
+import org.breizhcamp.konter.infrastructure.db.model.SlotDB
+import org.breizhcamp.konter.infrastructure.db.repos.HallRepo
+import org.breizhcamp.konter.infrastructure.db.repos.SlotRepo
+import org.springframework.stereotype.Component
+import java.time.LocalTime
+import java.util.*
+
+@Component
+class SlotAdapter (
+ private val slotRepo: SlotRepo,
+ private val hallRepo: HallRepo,
+): SlotPort {
+
+ @Throws
+ @Transactional
+ override fun create(eventId: Int, req: SlotCreationReq): Slot {
+ req.hallIds.forEach { hallId ->
+ throwIfOverlapped(hallId, eventId, req)
+ }
+
+ val halls = hallRepo.getAllByAvailableEventId(eventId)
+ .filter { it.id in req.hallIds }
+
+ val hall = halls.firstOrNull()
+ val barcode: String?
+ if (hall?.trackId != null) {
+ barcode = computeBarcode(
+ req.day,
+ eventId,
+ requireNotNull(hall.trackId) {
+ "Hall:${hall.id}.trackId should not be null at this point"
+ },
+ req.start
+ )
+ } else {
+ throw HallNotFoundException("Hall not found or does not have a trackId assigned")
+ }
+
+ slotRepo.create(hall.id, eventId, req.day, req.start, req.duration.seconds, barcode, req.title, req.assignable)
+ val slotId = slotRepo.getByBarcodeAndEventId(barcode, eventId).id
+
+ halls.filter { it != hall }.map { it.id }.forEach { hallId ->
+ associateHall(slotId, eventId, hallId)
+ }
+
+ return slotRepo.getByBarcodeAndEventId(barcode, eventId).toSlot()
+ }
+
+ @Throws
+ fun throwIfOverlapped(hallId: Int, eventId: Int, req: SlotCreationReq, thisSlot: SlotDB? = null) {
+ val start = req.start
+ val end = req.start.plus(req.duration)
+ val day = req.day
+
+ val existingSlots: MutableList =
+ slotRepo.getByHallIdAndEventId(hallId, eventId)
+ .toMutableList()
+
+ thisSlot?.let { existingSlots.removeIf { slot -> slot.id == it.id } }
+
+ val overlappingSlot = existingSlots.filter {
+ it.day == day
+ }.map { slot ->
+ Pair(slot.start, slot.start.plus(slot.duration))
+ }.find { range ->
+ (start >= range.first && start < range.second) ||
+ (end > range.first && end <= range.second) ||
+ (start < range.first && end > range.second)
+ }
+
+ if (overlappingSlot != null) {
+ val hall = hallRepo.findById(hallId).get()
+ throw TimeConflictException(
+ "Slot overlaps with an existing slot in Hall ${hall.name} (Track ${hall.trackId}), which begins at " +
+ "${overlappingSlot.first} and ends at " +
+ "${overlappingSlot.second}"
+ )
+ }
+ }
+
+ fun computeBarcode(day: Int, eventId: Int, trackId: Int, start: LocalTime): String {
+ val barcodeBuilder = StringBuilder()
+ barcodeBuilder.append(day)
+ barcodeBuilder.append(eventId)
+ barcodeBuilder.append(trackId)
+ barcodeBuilder.append("${start.hour}".padStart(2, '0'))
+ barcodeBuilder.append("${start.minute}".padStart(2, '0'))
+ barcodeBuilder.append("0".repeat(12 - barcodeBuilder.length))
+
+ val result = barcodeBuilder.toString()
+ return result + BarcodeEAN.calculateEANParity(result)
+ }
+
+ override fun getById(id: UUID): Slot = slotRepo.findById(id).get().toSlot()
+
+ override fun getProgram (eventId: Int): Map>> {
+ val allSlots = slotRepo.getAllByEventId(eventId)
+ val availableHalls = hallRepo.getAllByAvailableEventId(eventId)
+ val dayMap = mutableMapOf>>()
+
+ val days = allSlots.map { it.day }.toSortedSet()
+
+ for (day in days) {
+ val daySlots = allSlots
+ .toSet()
+ .filter { it.day == day }
+ .sortedBy { it.start }
+ .toMutableList()
+ val tracks = mutableMapOf>()
+
+ availableHalls.forEach { hall ->
+ val slots = daySlots
+ .filter { it.halls.contains(hall) }
+ .toMutableList()
+ val trackSlots = mutableListOf()
+ for (slot in slots) {
+ val spanSlot = slot.toSpanSlot(availableHalls, hall)
+ val newSlot = slot
+ .copy(halls = slot.halls
+ .filter { spanSlot.halls
+ .find { h -> h.id == it.id } != null
+ }.toSet())
+ slots[slots.indexOf(slot)] = newSlot
+ daySlots[daySlots.indexOf(slot)] = newSlot
+
+ trackSlots.add(spanSlot)
+ }
+
+ tracks[hall.toHall()] = trackSlots
+ }
+
+ dayMap[day] = tracks
+ }
+
+ return dayMap
+ }
+
+ override fun update(id: UUID, request: SlotPatchReq): Slot {
+ val oldSlot = slotRepo.findById(id).get()
+ var newSlot = oldSlot.copy()
+
+ request.title?.let { newSlot = newSlot.copy(title = it) }
+ newSlot = newSlot.copy(assignable = request.assignable)
+
+ return slotRepo.save(newSlot).toSlot()
+ }
+
+ @Transactional
+ override fun remove(id: UUID) =
+ slotRepo.deleteById(id)
+
+ @Transactional
+ override fun associateHall(id: UUID, eventId: Int, hallId: Int): Slot {
+ val slot = slotRepo.findById(id).get()
+ val req = SlotCreationReq(
+ start = slot.start,
+ day = slot.day,
+ duration = slot.duration,
+ title = slot.title,
+ hallIds = slot.halls.map { it.id },
+ assignable = slot.assignable
+ )
+ throwIfOverlapped(hallId, eventId, req, slot)
+ slotRepo.associateToHallAndEvent(id, hallId, eventId)
+
+ return getById(id)
+ }
+
+ @Transactional
+ override fun dissociateHall(id: UUID, hallId: Int) {
+ slotRepo.dissociateFromHall(id, hallId)
+ val slot = slotRepo.findById(id).get()
+ if (slot.halls.isEmpty()) {
+ slotRepo.deleteById(id)
+ }
+ }
+
+ private fun SlotDB.toSpanSlot(availableHalls: List, hall: HallDB): Slot {
+ val newHalls = halls.toMutableList()
+ var span = 0
+ var index = availableHalls.indexOf(hall)
+
+ while (index < availableHalls.size && halls.contains(availableHalls[index])) {
+ newHalls.remove(availableHalls[index])
+ span++
+ index++
+ }
+
+ return Slot(
+ id = id,
+ day = day,
+ session = session?.toLimitedSession(),
+ title = title,
+ manualSession = manualSession?.toManualSession(),
+ event = event.toEvent(),
+ halls = newHalls.map { it.toHall() },
+ start = start,
+ barcode = barcode,
+ duration = duration,
+ span = span,
+ assignable = assignable
+ )
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/SpeakerAdapter.kt b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/SpeakerAdapter.kt
index b91a155..8a03100 100644
--- a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/SpeakerAdapter.kt
+++ b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/SpeakerAdapter.kt
@@ -1,7 +1,6 @@
package org.breizhcamp.konter.infrastructure.db
import org.breizhcamp.konter.domain.entities.Speaker
-import org.breizhcamp.konter.domain.entities.SpeakerFilter
import org.breizhcamp.konter.domain.use_cases.ports.SpeakerPort
import org.breizhcamp.konter.infrastructure.db.mappers.toDB
import org.breizhcamp.konter.infrastructure.db.mappers.toSpeaker
@@ -16,10 +15,7 @@ class SpeakerAdapter (
override fun list(): List =
speakerRepo.findAll().map { it.toSpeaker() }
- override fun filter(filter: SpeakerFilter): List {
- TODO("Not yet implemented")
- }
-
+ @Throws
override fun get(id: UUID): Speaker =
speakerRepo.findById(id).get().toSpeaker()
diff --git a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/mappers/EventMapper.kt b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/mappers/EventMapper.kt
index d60ef28..cb22809 100644
--- a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/mappers/EventMapper.kt
+++ b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/mappers/EventMapper.kt
@@ -6,11 +6,16 @@ import org.breizhcamp.konter.infrastructure.db.model.EventDB
fun EventDB.toEvent() = Event(
id = id,
year = year,
- name = name
+ name = name,
+ begin = begin,
+ end = end
)
fun Event.toDB() = EventDB(
id = id,
year = year,
- name = name
+ name = name,
+ halls = emptySet(),
+ begin = begin,
+ end = end
)
\ No newline at end of file
diff --git a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/mappers/HallMapper.kt b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/mappers/HallMapper.kt
index 84778d1..565f92c 100644
--- a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/mappers/HallMapper.kt
+++ b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/mappers/HallMapper.kt
@@ -3,12 +3,14 @@ package org.breizhcamp.konter.infrastructure.db.mappers
import org.breizhcamp.konter.domain.entities.Hall
import org.breizhcamp.konter.infrastructure.db.model.HallDB
-fun HallDB.toHall() = Hall(
+fun HallDB.toHall(): Hall = Hall(
id = id,
- name = name
+ name = name,
+ trackId = trackId,
)
-fun Hall.toDB() = HallDB(
+fun Hall.toDB(): HallDB = HallDB(
id = id,
- name = name
+ name = name,
+ trackId = trackId,
)
\ No newline at end of file
diff --git a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/mappers/SessionMapper.kt b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/mappers/SessionMapper.kt
index 849986f..c1063d2 100644
--- a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/mappers/SessionMapper.kt
+++ b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/mappers/SessionMapper.kt
@@ -1,9 +1,11 @@
package org.breizhcamp.konter.infrastructure.db.mappers
+import org.breizhcamp.konter.domain.entities.ManualSession
import org.breizhcamp.konter.domain.entities.Session
+import org.breizhcamp.konter.infrastructure.db.model.ManualSessionDB
import org.breizhcamp.konter.infrastructure.db.model.SessionDB
-fun SessionDB.toSession() = Session(
+fun SessionDB.toSession(): Session = Session(
id = id,
title = title,
description = description,
@@ -15,15 +17,31 @@ fun SessionDB.toSession() = Session(
status = status,
submitted = submitted,
ownerNotes = ownerNotes,
+ videoURL = videoURL,
+ rating = rating,
event = event.toEvent(),
- hall = hall?.toHall(),
- beginning = beginning,
- end = end,
+ slot = slot?.toLimitedSlot()
+)
+
+fun SessionDB.toLimitedSession(): Session = Session(
+ id = id,
+ title = title,
+ description = description,
+ owner = owner.toSpeaker(),
+ speakers = speakers.map { it.toSpeaker() },
+ format = format,
+ theme = theme,
+ niveau = niveau,
+ status = status,
+ submitted = submitted,
+ ownerNotes = ownerNotes,
videoURL = videoURL,
- rating = rating
+ rating = rating,
+ event = event.toEvent(),
+ slot = null
)
-fun Session.toDB() = SessionDB(
+fun Session.toDB(): SessionDB = SessionDB(
id = id,
title = title,
description = description,
@@ -36,9 +54,27 @@ fun Session.toDB() = SessionDB(
submitted = submitted,
ownerNotes = ownerNotes,
event = event.toDB(),
- hall = hall?.toDB(),
- beginning = beginning,
- end = end,
videoURL = videoURL,
- rating = rating
-)
\ No newline at end of file
+ rating = rating,
+ barcode = null,
+ slot = slot?.toDB()
+)
+
+fun ManualSessionDB.toManualSession() = ManualSession(
+ id = id,
+ title = title,
+ description = description,
+ event = event.toEvent(),
+ format = format,
+ theme = theme
+)
+
+fun ManualSession.toDB() = ManualSessionDB(
+ id = id,
+ title = title,
+ description = description,
+ event = event.toDB(),
+ format = format,
+ theme = theme,
+ speakers = emptySet()
+)
diff --git a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/mappers/SlotMapper.kt b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/mappers/SlotMapper.kt
new file mode 100644
index 0000000..8472651
--- /dev/null
+++ b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/mappers/SlotMapper.kt
@@ -0,0 +1,48 @@
+package org.breizhcamp.konter.infrastructure.db.mappers
+
+import org.breizhcamp.konter.domain.entities.Slot
+import org.breizhcamp.konter.infrastructure.db.model.SlotDB
+
+fun SlotDB.toSlot(): Slot = Slot(
+ id = id,
+ day = day,
+ start = start,
+ duration = duration,
+ halls = halls.map { it.toHall() },
+ session = session?.toLimitedSession(),
+ manualSession = manualSession?.toManualSession(),
+ event = event.toEvent(),
+ barcode = barcode,
+ span = 1,
+ title = title,
+ assignable = assignable
+)
+
+fun SlotDB.toLimitedSlot(): Slot = Slot(
+ id = id,
+ day = day,
+ start = start,
+ duration = duration,
+ halls = halls.map { it.toHall() },
+ session = null,
+ manualSession = null,
+ event = event.toEvent(),
+ barcode = barcode,
+ span = 1,
+ title = title,
+ assignable = assignable
+)
+
+fun Slot.toDB(): SlotDB = SlotDB(
+ id = id,
+ day = day,
+ session = session?.toDB(),
+ manualSession = manualSession?.toDB(),
+ event = event.toDB(),
+ halls = halls.map{ it.toDB() }.toSet(),
+ start = start,
+ duration = duration,
+ barcode = barcode,
+ title = title,
+ assignable = assignable
+)
\ No newline at end of file
diff --git a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/model/EventDB.kt b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/model/EventDB.kt
index a6c29f6..ae13711 100644
--- a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/model/EventDB.kt
+++ b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/model/EventDB.kt
@@ -1,13 +1,24 @@
package org.breizhcamp.konter.infrastructure.db.model
import jakarta.persistence.*
+import java.time.LocalDate
@Entity
@Table(name = "event")
data class EventDB(
@Id
- @GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Int,
val year: Int,
val name: String?,
+ @ManyToMany
+ @JoinTable(
+ name = "available",
+ joinColumns = [JoinColumn(name = "event_id")],
+ inverseJoinColumns = [JoinColumn(name = "hall_id")]
+ )
+ val halls: Set,
+ @Column(name = "event_begin")
+ val begin: LocalDate?,
+ @Column(name = "event_end")
+ val end: LocalDate?
)
diff --git a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/model/HallDB.kt b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/model/HallDB.kt
index 3bfcc4f..6e7b635 100644
--- a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/model/HallDB.kt
+++ b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/model/HallDB.kt
@@ -1,5 +1,6 @@
package org.breizhcamp.konter.infrastructure.db.model
+import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.Id
import jakarta.persistence.Table
@@ -10,4 +11,6 @@ data class HallDB(
@Id
val id: Int,
val name: String?,
+ @Column(name = "track_id")
+ val trackId: Int?
)
diff --git a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/model/ManualSessionDB.kt b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/model/ManualSessionDB.kt
new file mode 100644
index 0000000..8ba2ad3
--- /dev/null
+++ b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/model/ManualSessionDB.kt
@@ -0,0 +1,26 @@
+package org.breizhcamp.konter.infrastructure.db.model
+
+import jakarta.persistence.*
+import org.breizhcamp.konter.domain.entities.enums.SessionFormatEnum
+import org.breizhcamp.konter.domain.entities.enums.SessionThemeEnum
+
+@Entity
+@Table(name = "manual_session")
+data class ManualSessionDB(
+ @Id
+ val id: Int,
+ val title: String,
+ val description: String,
+ @ManyToOne
+ @JoinColumn(name = "event_id", nullable = false)
+ val event: EventDB,
+ val format: SessionFormatEnum,
+ val theme: SessionThemeEnum,
+ @ManyToMany
+ @JoinTable(
+ name = "manual_presents",
+ joinColumns = [JoinColumn(name = "manual_session_id")],
+ inverseJoinColumns = [JoinColumn(name = "speaker_id")]
+ )
+ val speakers: Set
+)
diff --git a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/model/SessionDB.kt b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/model/SessionDB.kt
index db5abdd..cfef734 100644
--- a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/model/SessionDB.kt
+++ b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/model/SessionDB.kt
@@ -39,13 +39,13 @@ data class SessionDB(
@ManyToOne
@JoinColumn(name = "event_id", nullable = true)
val event: EventDB,
- @ManyToOne
- @JoinColumn(name = "hall_id", nullable = true)
- val hall: HallDB?,
- val beginning: LocalDateTime?,
- @Column(name = "\"end\"")
- val end: LocalDateTime?,
@Column(name = "video_url")
val videoURL: String?,
val rating: BigDecimal?,
+ val barcode: String?,
+ @OneToOne(
+ targetEntity = SlotDB::class,
+ mappedBy = "session"
+ )
+ val slot: SlotDB?
)
diff --git a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/model/SlotDB.kt b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/model/SlotDB.kt
new file mode 100644
index 0000000..d69d8de
--- /dev/null
+++ b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/model/SlotDB.kt
@@ -0,0 +1,75 @@
+package org.breizhcamp.konter.infrastructure.db.model
+
+import jakarta.persistence.*
+import org.hibernate.annotations.JdbcTypeCode
+import org.hibernate.type.SqlTypes
+import java.time.Duration
+import java.time.LocalTime
+import java.util.*
+
+@Entity
+@Table(name = "slot")
+data class SlotDB(
+ @Id
+ @GeneratedValue(strategy = GenerationType.UUID)
+ val id: UUID,
+ val day: Int,
+ @OneToOne
+ @JoinColumn(name = "session_id")
+ val session: SessionDB?,
+ @OneToOne
+ @JoinColumn(name = "manual_session_id")
+ val manualSession: ManualSessionDB?,
+ @ManyToOne
+ @JoinTable(
+ name = "held",
+ joinColumns = [JoinColumn(name = "slot_id")],
+ inverseJoinColumns = [JoinColumn(name = "event_id")]
+ )
+ val event: EventDB,
+ @ManyToMany
+ @JoinTable(
+ name = "held",
+ joinColumns = [JoinColumn(name = "slot_id")],
+ inverseJoinColumns = [JoinColumn(name = "hall_id")]
+ )
+ val halls: Set,
+ val start: LocalTime,
+ @JdbcTypeCode(SqlTypes.INTERVAL_SECOND)
+ val duration: Duration,
+ val barcode: String?,
+ val title: String?,
+ val assignable: Boolean
+) {
+ override fun hashCode(): Int {
+ var hash = 0
+
+ for (item in this.javaClass.declaredFields.filter {
+ it.name != SlotDB::session.name &&
+ it.name != SlotDB::manualSession.name
+ }) {
+ hash += item.hashCode()
+ hash *= 32
+ }
+
+ return hash
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as SlotDB
+
+ if (id != other.id) return false
+ if (day != other.day) return false
+ if (event != other.event) return false
+ if (halls != other.halls) return false
+ if (start != other.start) return false
+ if (duration != other.duration) return false
+ if (barcode != other.barcode) return false
+ if (title != other.title) return false
+
+ return true
+ }
+}
diff --git a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/repos/EventRepo.kt b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/repos/EventRepo.kt
index 3e3a951..ca61e38 100644
--- a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/repos/EventRepo.kt
+++ b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/repos/EventRepo.kt
@@ -1,15 +1,14 @@
package org.breizhcamp.konter.infrastructure.db.repos
import org.breizhcamp.konter.infrastructure.db.model.EventDB
+import org.breizhcamp.konter.infrastructure.db.model.SlotDB
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query
interface EventRepo: JpaRepository {
- fun existsByYear(year: Int): Boolean
- fun findByYear(year: Int): EventDB
@Query("""
- INSERT INTO event(year) VALUES(?) RETURNING id
- """, nativeQuery = true)
- fun createEvent(year: Int): Int
+ SELECT slot FROM SlotDB slot WHERE slot.event.id = ?1
+ """)
+ fun listSlotsHeld(id: Int): List
}
\ No newline at end of file
diff --git a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/repos/HallRepo.kt b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/repos/HallRepo.kt
index aee3790..b5d3183 100644
--- a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/repos/HallRepo.kt
+++ b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/repos/HallRepo.kt
@@ -9,18 +9,55 @@ import org.springframework.stereotype.Repository
@Repository
interface HallRepo: JpaRepository {
- @Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("""
INSERT INTO hall(name) VALUES (?) RETURNING id
""", nativeQuery = true)
fun create(name: String): Int
+ @Modifying
+ @Query("""
+ UPDATE HallDB hall SET hall.trackId = ?2 WHERE hall.id = ?1
+ """)
+ fun addTrackIdToHall(id: Int, trackId: Int)
+
@Query("""
SELECT * FROM hall
WHERE id IN (
- SELECT hall_id FROM available WHERE event_id = ?
+ SELECT hall_id FROM available WHERE event_id = ?1
)
+ ORDER BY (SELECT "order" FROM available WHERE event_id = ?1 AND hall_id = id)
""", nativeQuery = true)
fun getAllByAvailableEventId(eventId: Int): List
+ @Query("""
+ SELECT h.* FROM hall h JOIN available a ON h.id = a.hall_id WHERE a.event_id = ?1 AND "order" > (
+ SELECT "order" FROM available av WHERE av.hall_id = ?2 AND av.event_id = ?1
+ )
+ """, nativeQuery = true)
+ fun getAllByOrderAfterHallInEvent(eventId: Int, hallId: Int): List
+
+ @Modifying
+ @Query("""
+ UPDATE available SET "order" = "order" - 1 WHERE hall_id = ?1 AND event_id = ?2
+ """, nativeQuery = true)
+ fun decreaseOrder(id: Int, eventId: Int)
+
+ @Modifying
+ @Query("""
+ INSERT INTO available(hall_id, event_id, "order") VALUES (?, ?, ?)
+ """, nativeQuery = true)
+ fun associateToEvent(id: Int, eventId: Int, order: Int)
+
+ @Modifying
+ @Query("""
+ DELETE FROM available WHERE hall_id = ? AND event_id = ?
+ """, nativeQuery = true)
+ fun dissociateFromEvent(id: Int, eventId: Int)
+
+ @Modifying
+ @Query("""
+ UPDATE available SET "order" = ?3 WHERE hall_id = ?1 AND event_id = ?2
+ """, nativeQuery = true)
+ fun setOrderInEvent(id: Int, eventId: Int, order: Int)
+
}
\ No newline at end of file
diff --git a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/repos/ManualSessionRepo.kt b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/repos/ManualSessionRepo.kt
new file mode 100644
index 0000000..01c2669
--- /dev/null
+++ b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/repos/ManualSessionRepo.kt
@@ -0,0 +1,30 @@
+package org.breizhcamp.konter.infrastructure.db.repos
+
+import org.breizhcamp.konter.domain.entities.enums.SessionFormatEnum
+import org.breizhcamp.konter.domain.entities.enums.SessionThemeEnum
+import org.breizhcamp.konter.infrastructure.db.model.ManualSessionDB
+import org.springframework.data.jpa.repository.JpaRepository
+import org.springframework.data.jpa.repository.Modifying
+import org.springframework.data.jpa.repository.Query
+
+interface ManualSessionRepo: JpaRepository {
+
+ @Modifying
+ @Query("""
+ INSERT INTO manual_session(event_id, title, description, format, theme)
+ VALUES (?, ?, ?, ?, ?)
+ RETURNING id
+ """, nativeQuery = true)
+ fun create(
+ eventId: Int,
+ title: String,
+ description: String,
+ format: SessionFormatEnum,
+ theme: SessionThemeEnum
+ ): Int
+
+ @Query("""
+ SELECT mSession FROM ManualSessionDB mSession WHERE mSession.event.id = ?1
+ """)
+ fun getAllByEventId(eventId: Int): List
+}
\ No newline at end of file
diff --git a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/repos/SessionRepo.kt b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/repos/SessionRepo.kt
index b01976e..bae91dc 100644
--- a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/repos/SessionRepo.kt
+++ b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/repos/SessionRepo.kt
@@ -2,7 +2,44 @@ package org.breizhcamp.konter.infrastructure.db.repos
import org.breizhcamp.konter.infrastructure.db.model.SessionDB
import org.springframework.data.jpa.repository.JpaRepository
+import org.springframework.data.jpa.repository.Modifying
+import org.springframework.data.jpa.repository.Query
import org.springframework.stereotype.Repository
+import java.util.*
@Repository
-interface SessionRepo: JpaRepository, SessionRepoCustom
\ No newline at end of file
+interface SessionRepo: JpaRepository, SessionRepoCustom {
+
+ @Modifying
+ @Query("""
+ UPDATE SessionDB session SET session.barcode = ?2 WHERE session.id = ?1
+ """)
+ fun addBarcode(id: Int, barcode: String)
+
+ @Query("""
+ SELECT CASE WHEN
+ (SELECT count(*) FROM slot WHERE slot.session_id = ?) = 0
+ THEN FALSE
+ ELSE TRUE
+ END
+ """, nativeQuery = true)
+ fun hasSlotById(id: Int): Boolean
+
+ @Modifying
+ @Query("""
+ UPDATE slot SET session_id = null WHERE session_id = ?
+ """, nativeQuery = true)
+ fun removeSlotById(id: Int)
+
+ @Modifying
+ @Query("""
+ UPDATE slot SET session_id = ? WHERE id = ?
+ """, nativeQuery = true)
+ fun setSlotById(id: Int, slotId: UUID)
+
+ @Query("""
+ SELECT slot.id FROM SlotDB slot WHERE slot.barcode = ?1
+ """)
+ fun getSlotIdByBarcode(barcode: String): UUID
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/repos/SessionRepoCustom.kt b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/repos/SessionRepoCustom.kt
index 1682f77..7f4eb97 100644
--- a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/repos/SessionRepoCustom.kt
+++ b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/repos/SessionRepoCustom.kt
@@ -5,6 +5,6 @@ import org.breizhcamp.konter.infrastructure.db.model.SessionDB
interface SessionRepoCustom {
- fun filter(year: Int,filter: SessionFilter): List
+ fun filter(eventId: Int, filter: SessionFilter, sortByFormat: Boolean): List
}
\ No newline at end of file
diff --git a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/repos/SessionRepoCustomImpl.kt b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/repos/SessionRepoCustomImpl.kt
index 1ba5e30..a418083 100644
--- a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/repos/SessionRepoCustomImpl.kt
+++ b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/repos/SessionRepoCustomImpl.kt
@@ -3,6 +3,7 @@ package org.breizhcamp.konter.infrastructure.db.repos
import mu.KotlinLogging
import org.breizhcamp.konter.domain.entities.SessionFilter
import org.breizhcamp.konter.infrastructure.db.model.QSessionDB
+import org.breizhcamp.konter.infrastructure.db.model.QSlotDB
import org.breizhcamp.konter.infrastructure.db.model.QSpeakerDB
import org.breizhcamp.konter.infrastructure.db.model.SessionDB
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport
@@ -10,15 +11,18 @@ import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport
private val logger = KotlinLogging.logger { }
class SessionRepoCustomImpl: QuerydslRepositorySupport(SessionDB::class.java), SessionRepoCustom {
- override fun filter(year: Int, filter: SessionFilter): List {
+ override fun filter(eventId: Int, filter: SessionFilter, sortByFormat: Boolean): List {
val session = QSessionDB.sessionDB
val speaker = QSpeakerDB.speakerDB
+ val slot = QSlotDB.slotDB
val query = from(session)
- query.where(session.event.year.eq(year))
+ query.leftJoin(session.slot, slot)
+
+ query.where(session.event.id.eq(eventId))
filter.id?.let {
- query.where(session.id.eq(it))
+ query.where(session.id.eq(it).or(session.barcode.eq(it.toString())))
val bypassedFilterValue = query.fetch()
if (bypassedFilterValue.size == 1) {
logger.info { "Session found with id, bypassing the rest of the filter" }
@@ -71,6 +75,10 @@ class SessionRepoCustomImpl: QuerydslRepositorySupport(SessionDB::class.java), S
}
}
+ if (sortByFormat) {
+ query.orderBy(session.format.asc())
+ }
+
query.orderBy(session.rating.desc().nullsLast())
return query.fetch()
diff --git a/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/repos/SlotRepo.kt b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/repos/SlotRepo.kt
new file mode 100644
index 0000000..78747a1
--- /dev/null
+++ b/src/main/kotlin/org/breizhcamp/konter/infrastructure/db/repos/SlotRepo.kt
@@ -0,0 +1,46 @@
+package org.breizhcamp.konter.infrastructure.db.repos
+
+import org.breizhcamp.konter.infrastructure.db.model.SlotDB
+import org.springframework.data.jpa.repository.JpaRepository
+import org.springframework.data.jpa.repository.Modifying
+import org.springframework.data.jpa.repository.Query
+import java.time.LocalTime
+import java.util.*
+
+interface SlotRepo: JpaRepository {
+
+ @Modifying
+ @Query("""
+ WITH s_id as (
+ INSERT INTO slot(day, start, duration, barcode, title, assignable, id)
+ VALUES (?3, CAST(?4 AS TIME), ?5 * INTERVAL '1 second', ?6, ?7, ?8, gen_random_uuid())
+ RETURNING id
+ )
+ INSERT INTO held(hall_id, event_id, slot_id)
+ SELECT ?1, ?2, s_id.id FROM s_id
+ """, nativeQuery = true)
+ fun create(hallId: Int, eventId: Int, day: Int, start: LocalTime, duration: Long, barcode: String?, title: String?, assignable: Boolean)
+
+ fun getAllByEventId(eventId: Int): List
+
+ @Query("""
+ SELECT slot FROM SlotDB slot WHERE slot.event.id = ?2 AND ?1 IN (
+ SELECT hall.id FROM slot.halls hall
+ )
+ """)
+ fun getByHallIdAndEventId(hallId: Int, eventId: Int): List
+
+ fun getByBarcodeAndEventId(barcode: String, eventId: Int): SlotDB
+
+ @Modifying
+ @Query("""
+ INSERT INTO held(slot_id, hall_id, event_id) VALUES (?1, ?2, ?3) ON CONFLICT DO NOTHING
+ """, nativeQuery = true)
+ fun associateToHallAndEvent(slotId: UUID, hallId: Int, eventId: Int)
+
+ @Modifying
+ @Query("""
+ DELETE FROM held WHERE slot_id = ?1 and hall_id = ?2
+ """, nativeQuery = true)
+ fun dissociateFromHall(slotId: UUID, hallId: Int)
+}
\ No newline at end of file
diff --git a/src/main/resources/application-test.yaml b/src/main/resources/application-test.yaml
new file mode 100644
index 0000000..43b947e
--- /dev/null
+++ b/src/main/resources/application-test.yaml
@@ -0,0 +1,5 @@
+konter:
+ kalon:
+ enabled: true
+ url: http://localhost:9999
+ secured: false
\ No newline at end of file
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 6388118..a1a4fa5 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -1,3 +1,7 @@
spring:
liquibase:
change-log: classpath:db/changelog/db.changelog-master.xml
+
+logging:
+ level:
+ com.itextpdf.*: OFF
\ No newline at end of file
diff --git a/src/main/resources/db/changelog/db.changelog-4.0.xml b/src/main/resources/db/changelog/db.changelog-4.0.xml
index 7d7d785..1ad4312 100644
--- a/src/main/resources/db/changelog/db.changelog-4.0.xml
+++ b/src/main/resources/db/changelog/db.changelog-4.0.xml
@@ -19,4 +19,149 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ INSERT INTO held (slot_id, hall_id, event_id)
+ SELECT id, hall_id, event_id FROM slot
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/db/changelog/db.changelog-5.0.xml b/src/main/resources/db/changelog/db.changelog-5.0.xml
new file mode 100644
index 0000000..c8301b7
--- /dev/null
+++ b/src/main/resources/db/changelog/db.changelog-5.0.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/db/changelog/db.changelog-master.xml b/src/main/resources/db/changelog/db.changelog-master.xml
index 2b94d0b..9fa3ea5 100644
--- a/src/main/resources/db/changelog/db.changelog-master.xml
+++ b/src/main/resources/db/changelog/db.changelog-master.xml
@@ -8,4 +8,5 @@
+
\ No newline at end of file
diff --git a/src/test/kotlin/org/breizhcamp/konter/application/rest/HallControllerTest.kt b/src/test/kotlin/org/breizhcamp/konter/application/rest/HallControllerTest.kt
new file mode 100644
index 0000000..a56a9ad
--- /dev/null
+++ b/src/test/kotlin/org/breizhcamp/konter/application/rest/HallControllerTest.kt
@@ -0,0 +1,189 @@
+package org.breizhcamp.konter.application.rest
+
+import com.ninjasquad.springmockk.MockkBean
+import io.mockk.Runs
+import io.mockk.every
+import io.mockk.just
+import io.mockk.verify
+import org.breizhcamp.konter.application.requests.HallCreationReq
+import org.breizhcamp.konter.application.requests.HallPatchReq
+import org.breizhcamp.konter.domain.entities.Hall
+import org.breizhcamp.konter.domain.entities.exceptions.EventNotFoundException
+import org.breizhcamp.konter.domain.use_cases.HallAssociateEvent
+import org.breizhcamp.konter.domain.use_cases.HallCRUD
+import org.breizhcamp.konter.domain.use_cases.HallSetOrder
+import org.breizhcamp.konter.testUtils.HallGen
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Nested
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.boot.test.system.CapturedOutput
+import org.springframework.boot.test.system.OutputCaptureExtension
+import org.springframework.http.HttpStatus
+import org.springframework.http.ResponseEntity
+import org.springframework.test.context.junit.jupiter.SpringExtension
+import kotlin.math.absoluteValue
+import kotlin.random.Random
+
+@ExtendWith(SpringExtension::class)
+@ExtendWith(OutputCaptureExtension::class)
+@WebMvcTest(HallController::class)
+class HallControllerTest {
+
+ @MockkBean
+ private lateinit var hallCRUD: HallCRUD
+
+ @MockkBean
+ private lateinit var hallAssociateEvent: HallAssociateEvent
+
+ @MockkBean
+ private lateinit var hallSetOrder: HallSetOrder
+
+ @Autowired
+ private lateinit var hallController: HallController
+
+ @Nested
+ inner class ListTests {
+ private lateinit var halls: List
+
+ @BeforeEach
+ fun setUp() {
+ halls = HallGen().generateList()
+ }
+
+ @Test
+ fun `listAll should log, call CRUD and return the result as a List of DTOs`(output: CapturedOutput) {
+ every { hallCRUD.listAll() } returns halls
+
+ assertEquals(halls.map(Hall::toDto), hallController.listAll())
+ assert(output.contains("Listing all Halls"))
+
+ verify { hallCRUD.listAll() }
+ }
+
+ @Test
+ fun `listByEvent should log, call CRUD with its input and return the result as a List of DTOs`(output: CapturedOutput) {
+ val eventId = Random.nextInt().absoluteValue
+ every { hallCRUD.listByEvent(eventId) } returns halls
+
+ assertEquals(halls.map(Hall::toDto), hallController.listByEvent(eventId))
+ assert(output.contains("Listing Halls available for Event:$eventId"))
+
+ verify { hallCRUD.listByEvent(eventId) }
+ }
+
+ }
+
+ @Nested
+ inner class CreateAndPatchTests {
+ private lateinit var hall: Hall
+
+ @BeforeEach
+ fun setUp() {
+ hall = HallGen().generateOne()
+ }
+
+ @Test
+ fun `createHall should log, call CRUD with its input and return the result as a DTO`(output: CapturedOutput) {
+ val request = HallCreationReq(
+ name = requireNotNull(hall.name),
+ trackId = hall.trackId
+ )
+ every { hallCRUD.create(request) } returns hall
+
+ assertEquals(hall.toDto(), hallController.createHall(request))
+ assert(output.contains(
+ "Creating a Hall with name ${request.name} and trackId ${request.trackId}"
+ ))
+
+ verify { hallCRUD.create(request) }
+ }
+
+ @Test
+ fun `patchHall should log, call CRUD with its input and return the result as a DTO`(output: CapturedOutput) {
+ val request = HallPatchReq(
+ name = requireNotNull(hall.name),
+ trackId = hall.trackId
+ )
+ every { hallCRUD.update(hall.id, request) } returns hall
+
+ assertEquals(hall.toDto(), hallController.patchHall(hall.id, request))
+ assert(output.contains("Updating Hall:${hall.id}"))
+
+ verify { hallCRUD.update(hall.id, request) }
+ }
+ }
+
+ @Test
+ fun `deleteHall should log and call CRUD with its input`(output: CapturedOutput) {
+ val id = Random.nextInt().absoluteValue
+ every { hallCRUD.delete(id) } just Runs
+
+ hallController.deleteHall(id)
+ assert(output.contains("Deleting Hall:$id"))
+
+ verify { hallCRUD.delete(id) }
+ }
+
+ @Nested
+ inner class EventRelativeTests {
+ private var eventId: Int = 0
+ private lateinit var hall: Hall
+ private var order: Int = 0
+
+ @BeforeEach
+ fun setUp() {
+ eventId = Random.nextInt().absoluteValue
+ hall = HallGen().generateOne()
+ order = Random.nextInt().absoluteValue
+ }
+
+ @Test
+ fun `associateToEvent should log, call AssociateEvent and return a 404 if Event was not found`(output: CapturedOutput) {
+ val exception = EventNotFoundException("No Event with id $eventId found")
+ every { hallAssociateEvent.associate(hall.id, eventId, order) } throws exception
+
+ assertEquals(
+ ResponseEntity.status(HttpStatus.NOT_FOUND).body(exception.message),
+ hallController.associateToEvent(hall.id, eventId, order)
+ )
+ assert(output.contains("Associating Hall:${hall.id} to Event:$eventId"))
+ assert(output.contains(exception.toString()))
+
+ verify { hallAssociateEvent.associate(hall.id, eventId, order) }
+ }
+
+ @Test
+ fun `associateToEvent should log, call AssociateEvent with its inputs and return the result as a DTO`(output: CapturedOutput) {
+ every { hallAssociateEvent.associate(hall.id, eventId, order) } returns hall
+
+ assertEquals(ResponseEntity.ok(hall.toDto()), hallController.associateToEvent(hall.id, eventId, order))
+ assert(output.contains("Associating Hall:${hall.id} to Event:$eventId"))
+
+ verify { hallAssociateEvent.associate(hall.id, eventId, order) }
+ }
+
+ @Test
+ fun `dissociateFromEvent should log, call AssociateEvent with its inputs and return the result as a DTO`(output: CapturedOutput) {
+ every { hallAssociateEvent.dissociate(hall.id, eventId) } returns hall
+
+ assertEquals(hall.toDto(), hallController.dissociateFromEvent(hall.id, eventId))
+ assert(output.contains("Dissociating Hall:${hall.id} from Event:$eventId"))
+
+ verify { hallAssociateEvent.dissociate(hall.id, eventId) }
+ }
+
+ @Test
+ fun `updateOrderForEvent should log, call SetOrderEvent with its inputs and return the result as a DTO`(output: CapturedOutput) {
+ every { hallSetOrder.setOrder(hall.id, eventId, order) } returns hall
+
+ assertEquals(hall.toDto(), hallController.updateOrderForEvent(hall.id, eventId, order))
+ assert(output.contains("Updating order of Hall:${hall.id} for Event:$eventId to $order"))
+
+ verify { hallSetOrder.setOrder(hall.id, eventId, order) }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/org/breizhcamp/konter/application/rest/SessionControllerTest.kt b/src/test/kotlin/org/breizhcamp/konter/application/rest/SessionControllerTest.kt
new file mode 100644
index 0000000..3bb423c
--- /dev/null
+++ b/src/test/kotlin/org/breizhcamp/konter/application/rest/SessionControllerTest.kt
@@ -0,0 +1,254 @@
+package org.breizhcamp.konter.application.rest
+
+import com.ninjasquad.springmockk.MockkBean
+import io.mockk.Runs
+import io.mockk.every
+import io.mockk.just
+import io.mockk.verify
+import org.breizhcamp.konter.application.requests.SessionCreationReq
+import org.breizhcamp.konter.application.requests.SessionPatchReq
+import org.breizhcamp.konter.domain.entities.ManualSession
+import org.breizhcamp.konter.domain.entities.Session
+import org.breizhcamp.konter.domain.entities.SessionFilter
+import org.breizhcamp.konter.domain.use_cases.*
+import org.breizhcamp.konter.testUtils.EventGen
+import org.breizhcamp.konter.testUtils.ManualSessionGen
+import org.breizhcamp.konter.testUtils.SessionGen
+import org.breizhcamp.konter.testUtils.generateRandomHexString
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Nested
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.boot.test.system.CapturedOutput
+import org.springframework.boot.test.system.OutputCaptureExtension
+import org.springframework.http.HttpHeaders
+import org.springframework.http.MediaType
+import org.springframework.mock.web.MockHttpServletResponse
+import org.springframework.mock.web.MockMultipartFile
+import org.springframework.test.context.junit.jupiter.SpringExtension
+import org.springframework.web.multipart.MultipartFile
+import java.util.*
+import kotlin.math.absoluteValue
+import kotlin.random.Random
+
+@ExtendWith(SpringExtension::class)
+@ExtendWith(OutputCaptureExtension::class)
+@WebMvcTest(SessionController::class)
+class SessionControllerTest {
+
+ @MockkBean
+ private lateinit var sessionImport: SessionImport
+
+ @MockkBean
+ private lateinit var sessionGenerateCards: SessionGenerateCards
+
+ @MockkBean
+ private lateinit var eventGet: EventGet
+
+ @MockkBean
+ private lateinit var sessionList: SessionList
+
+ @MockkBean
+ private lateinit var slotSetSession: SlotSetSession
+
+ @MockkBean
+ private lateinit var manualSessionCRUD: ManualSessionCRUD
+
+ @Autowired
+ private lateinit var sessionController: SessionController
+
+ @Nested
+ inner class ListTests {
+ private var eventId: Int = 0
+ private lateinit var sessions: List
+
+ @BeforeEach
+ fun setUp() {
+ eventId = Random.nextInt().absoluteValue
+ sessions = SessionGen().generateList()
+ }
+
+ @Test
+ fun `listSessions should log, call List with its input and return the result as a List of DTOs`(output: CapturedOutput) {
+ every { sessionList.list(eventId) } returns sessions
+
+ assertEquals(sessions.map(Session::toDto), sessionController.listSessions(eventId))
+ assert(output.contains("Listing Sessions from Event:$eventId"))
+
+ verify { sessionList.list(eventId) }
+ }
+
+ @Test
+ fun `filterSessions should log, call List with its inputs and return the result as a List of DTOs`(output: CapturedOutput) {
+ val filter = SessionFilter.empty()
+ every { sessionList.filter(eventId, filter) } returns sessions
+
+ assertEquals(sessions.map(Session::toDto), sessionController.filterSessions(eventId, filter))
+ assert(output.contains("Filtering Sessions from Event:$eventId"))
+
+ verify { sessionList.filter(eventId, filter) }
+ }
+ }
+
+ @Nested
+ inner class ImportTests {
+ private lateinit var file: MultipartFile
+
+ @BeforeEach
+ fun setUp() {
+ file = MockMultipartFile(
+ "values.csv",
+ "values.csv",
+ MediaType.TEXT_PLAIN_VALUE,
+ "VALUES".toByteArray()
+ )
+ }
+
+ @Test
+ fun `importCsv should log and call Import with the input stream of its received file`(output: CapturedOutput) {
+ val eventId = Random.nextInt().absoluteValue
+ every { sessionImport.importCsv(eventId, any()) } just Runs
+
+ sessionController.importCsv(eventId, file)
+ assert(output.contains("Importing Sessions for Event:$eventId"))
+
+ verify { sessionImport.importCsv(eventId, any()) }
+ }
+
+ @Test
+ fun `importEvaluationsCsv should log and call Import with the input stream of its received file`(output: CapturedOutput) {
+ every { sessionImport.importEvaluationCsv(any()) } just Runs
+
+ sessionController.importEvaluationsCsv(file)
+ assert(output.contains("Importing Evaluations"))
+
+ verify { sessionImport.importEvaluationCsv(any()) }
+ }
+ }
+
+ @Test
+ fun `exportCards should log, set the correct headers to the response and call GenerateCards with the response outputStream`(output: CapturedOutput) {
+ val event = EventGen().generateOne()
+ val contentDisposition = "attachment; filename=\"session_cards_${event.name}.pdf\""
+ val response = MockHttpServletResponse()
+ every { eventGet.getById(event.id) } returns event
+ every { sessionGenerateCards.generatePdf(event.id, response.outputStream) } just Runs
+
+ sessionController.exportCards(event.id, response)
+ assertEquals(contentDisposition, response.getHeader(HttpHeaders.CONTENT_DISPOSITION))
+ assertEquals(MediaType.APPLICATION_PDF_VALUE, response.getHeader(HttpHeaders.CONTENT_TYPE))
+ assert(output.contains("Generating Sessions cards for Event:${event.id}"))
+
+ verify { eventGet.getById(event.id) }
+ verify { sessionGenerateCards.generatePdf(event.id, response.outputStream) }
+ }
+
+ @Nested
+ inner class SetSessionTests {
+ private lateinit var session: Session
+
+ @BeforeEach
+ fun setUp() {
+ session = SessionGen().generateOne()
+ }
+
+ @Test
+ fun `setSessionById should log, call slotSetSession with its inputs and return the result as a DTO`(output: CapturedOutput) {
+ val slotId = UUID.randomUUID()
+ every { slotSetSession.setById(session.id, slotId) } returns session
+
+ assertEquals(session.toDto(), sessionController.setSlot(session.id, slotId))
+ assert(output.contains("Setting Session:${session.id} to Slot:$slotId"))
+
+ verify { slotSetSession.setById(session.id, slotId) }
+ }
+
+ @Test
+ fun `setSessionByBarcode should log, call slotSetSession with its inputs and return the result as a DTO`(output: CapturedOutput) {
+ val barcode = generateRandomHexString(3).substring(0..<13)
+ every { slotSetSession.setByBarcode(session.id, barcode) } returns session
+
+ assertEquals(session.toDto(), sessionController.setSlot(session.id, barcode))
+ assert(output.contains("Setting Session:${session.id} to Slot with barcode $barcode"))
+
+ verify { slotSetSession.setByBarcode(session.id, barcode) }
+ }
+ }
+
+ @Nested
+ inner class ManualSessionEntityCruTests {
+ private lateinit var session: ManualSession
+
+ @BeforeEach
+ fun setUp() {
+ session = ManualSessionGen().generateOne()
+ }
+
+ @Test
+ fun `createManualSession should log, call CRUD with its inputs and return the result as a DTO`(output: CapturedOutput) {
+ val request = SessionCreationReq(
+ title = session.title,
+ description = session.description,
+ format = session.format,
+ theme = session.theme
+ )
+ val eventId = Random.nextInt().absoluteValue
+
+ every { manualSessionCRUD.create(request, eventId) } returns session
+
+ assertEquals(session.toDto(), sessionController.createManualSession(eventId, request))
+ assert(output.contains("Creating ManualSession for Event:$eventId with title:${request.title}"))
+
+ verify { manualSessionCRUD.create(request, eventId) }
+ }
+
+ @Test
+ fun `getManualSession should log, call CRUD and return the result as a DTO`(output: CapturedOutput) {
+ every { manualSessionCRUD.get(session.id) } returns session
+
+ assertEquals(session.toDto(), sessionController.getManualSession(session.id))
+ assert(output.contains("Retrieving ManualSession:${session.id}"))
+
+ verify { manualSessionCRUD.get(session.id) }
+ }
+
+ @Test
+ fun `patchManualSession should log, call CRUD with its inputs and return the result as a DTO`(output: CapturedOutput) {
+ val request = SessionPatchReq.empty()
+ every { manualSessionCRUD.update(session.id, request) } returns session
+
+ assertEquals(session.toDto(), sessionController.patchManualSession(session.id, request))
+ assert(output.contains("Updating ManualSession:${session.id}"))
+
+ verify { manualSessionCRUD.update(session.id, request) }
+ }
+
+ }
+
+ @Test
+ fun `listManualSessions should log, call CRUD and return the result as a List of DTOs`(output: CapturedOutput) {
+ val sessions = ManualSessionGen().generateList()
+ val eventId = Random.nextInt().absoluteValue
+
+ every { manualSessionCRUD.list(eventId) } returns sessions
+
+ assertEquals(sessions.map(ManualSession::toDto), sessionController.listManualSessions(eventId))
+ assert(output.contains("Retrieving all ManualSession in Event:$eventId"))
+
+ verify { manualSessionCRUD.list(eventId) }
+ }
+
+ @Test
+ fun `deleteManualSession should log and call CRUD`(output: CapturedOutput) {
+ val id = Random.nextInt().absoluteValue
+ every { manualSessionCRUD.delete(id) } just Runs
+
+ sessionController.deleteManualSession(id)
+ assert(output.contains("Deleting ManualSession:$id"))
+
+ verify { manualSessionCRUD.delete(id) }
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/org/breizhcamp/konter/application/rest/SlotControllerTest.kt b/src/test/kotlin/org/breizhcamp/konter/application/rest/SlotControllerTest.kt
new file mode 100644
index 0000000..646b86d
--- /dev/null
+++ b/src/test/kotlin/org/breizhcamp/konter/application/rest/SlotControllerTest.kt
@@ -0,0 +1,284 @@
+package org.breizhcamp.konter.application.rest
+
+import com.ninjasquad.springmockk.MockkBean
+import io.mockk.Runs
+import io.mockk.every
+import io.mockk.just
+import io.mockk.verify
+import org.breizhcamp.konter.application.requests.SlotCreationReq
+import org.breizhcamp.konter.application.requests.SlotPatchReq
+import org.breizhcamp.konter.domain.entities.Hall
+import org.breizhcamp.konter.domain.entities.Slot
+import org.breizhcamp.konter.domain.entities.exceptions.EventNoBeginException
+import org.breizhcamp.konter.domain.entities.exceptions.HallNotFoundException
+import org.breizhcamp.konter.domain.entities.exceptions.TimeConflictException
+import org.breizhcamp.konter.domain.use_cases.*
+import org.breizhcamp.konter.testUtils.*
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Nested
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.EnumSource
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.boot.test.system.CapturedOutput
+import org.springframework.boot.test.system.OutputCaptureExtension
+import org.springframework.http.HttpHeaders
+import org.springframework.http.HttpStatus
+import org.springframework.http.MediaType
+import org.springframework.http.ResponseEntity
+import org.springframework.mock.web.MockHttpServletResponse
+import org.springframework.test.context.junit.jupiter.SpringExtension
+import java.util.*
+import kotlin.math.absoluteValue
+import kotlin.random.Random
+
+@ExtendWith(SpringExtension::class)
+@ExtendWith(OutputCaptureExtension::class)
+@WebMvcTest(SlotController::class)
+class SlotControllerTest {
+
+ @MockkBean
+ private lateinit var slotCrud: SlotCRUD
+
+ @MockkBean
+ private lateinit var eventGet: EventGet
+
+ @MockkBean
+ private lateinit var slotGenerateProgram: SlotGenerateProgram
+
+ @MockkBean
+ private lateinit var slotAssociateHall: SlotAssociateHall
+
+ @MockkBean
+ private lateinit var getTalks: GetTalks
+
+ @Autowired
+ private lateinit var slotController: SlotController
+
+ @Test
+ fun `listSlotForEvent should log, call CRUD and map the result to change Halls to their Ids and Slots to DTOs`(output: CapturedOutput) {
+ val day = Random.nextInt().absoluteValue
+ val hall = HallGen().generateOne()
+ val slots = SlotGen().generateList()
+ val program: Map>> =
+ mapOf(
+ Pair(day, mapOf(
+ Pair(
+ hall, slots
+ )
+ ))
+ )
+ val eventId = Random.nextInt().absoluteValue
+
+ every { slotCrud.list(eventId) } returns program
+
+ val result = slotController.listSlotForEvent(eventId)
+ assert(output.contains("Listing all slots in Event:$eventId grouping by Hall"))
+
+ assert(result.containsKey(day))
+ assert(requireNotNull(result[day]).containsKey(hall.id))
+ assertEquals(slots.map(Slot::toDto), requireNotNull(result[day])[hall.id])
+
+ verify { slotCrud.list(eventId) }
+ }
+
+ enum class Scenarios {
+ NO_EXCEPTION,
+ TIME_CONFLICT,
+ NOT_FOUND
+ }
+
+ @Nested
+ inner class CRUTests {
+ private lateinit var slot: Slot
+
+ @BeforeEach
+ fun setUp() {
+ slot = SlotGen().generateOne()
+ }
+
+ @ParameterizedTest
+ @EnumSource(Scenarios::class)
+ fun `addSlotToHall should log, call CRUD return a CONFLICT on TimeConflictException, NOT_FOUND on HallNotFoundException and the result as a DTO on no Exception`(scenario: Scenarios, output: CapturedOutput) {
+ val hallId = Random.nextInt().absoluteValue
+ val eventId = Random.nextInt().absoluteValue
+ val request = SlotCreationReq(slot.start, slot.day, slot.duration, slot.title, listOf(hallId), slot.assignable)
+
+ val message = generateRandomHexString()
+
+ when(scenario) {
+ Scenarios.NO_EXCEPTION -> every { slotCrud.create(eventId, request) } returns slot
+ Scenarios.TIME_CONFLICT -> every { slotCrud.create(eventId, request) } throws TimeConflictException(message)
+ Scenarios.NOT_FOUND -> every { slotCrud.create(eventId, request) } throws HallNotFoundException(message)
+ }
+
+ val response = slotController.addSlotToHall(eventId, request)
+ assert(output.contains("Adding slot in Event:$eventId"))
+ when(scenario) {
+ Scenarios.NO_EXCEPTION -> {
+ assertEquals(slot.toDto(), response.body)
+ }
+ Scenarios.TIME_CONFLICT -> {
+ assertEquals(HttpStatus.CONFLICT, response.statusCode)
+ assertEquals(message, response.body)
+ assert(output.contains(message))
+ }
+ Scenarios.NOT_FOUND -> {
+ assertEquals(HttpStatus.NOT_FOUND, response.statusCode)
+ assertEquals(message, response.body)
+ assert(output.contains(message))
+ }
+ }
+
+ verify { slotCrud.create(eventId, request) }
+ }
+
+ @Test
+ fun `getSlot should log, call CRUD and return the result as a DTO`(output: CapturedOutput) {
+ every { slotCrud.get(slot.id) } returns slot
+
+ assertEquals(slot.toDto(), slotController.getSlot(slot.id))
+ assert(output.contains("Retrieving Slot:${slot.id}"))
+
+ verify { slotCrud.get(slot.id) }
+ }
+
+ @Test
+ fun `update should log, call crud and return the result as a DTO`(output: CapturedOutput) {
+ val req = SlotPatchReq(generateRandomHexString(), Random.nextBoolean())
+ val updatedSlot = slot.copy(title = req.title, assignable = req.assignable)
+ every { slotCrud.update(slot.id, req) } returns updatedSlot
+
+ assertEquals(updatedSlot.toDto(), slotController.update(slot.id, req))
+ assert(output.contains("Patching Slot:${slot.id}"))
+
+ verify { slotCrud.update(slot.id, req) }
+ }
+
+ @Nested
+ inner class HallAssignmentTests {
+ private var eventId: Int = 0
+ private var hallId: Int = 0
+
+ @BeforeEach
+ fun setUp() {
+ eventId = Random.nextInt().absoluteValue
+ hallId = Random.nextInt().absoluteValue
+ }
+
+ @Test
+ fun `assignHallToSlot should log, call AssociateHall and return a 409 if a time conflict was found`(
+ output: CapturedOutput
+ ) {
+ val exception = TimeConflictException("Slot overlaps with an existing slot")
+ every { slotAssociateHall.associate(slot.id, eventId, hallId) } throws exception
+
+ assertEquals(
+ ResponseEntity.status(HttpStatus.CONFLICT).body(exception.message),
+ slotController.assignHallToSlot(slot.id, eventId, hallId)
+ )
+ assert(output.contains("Assigning Hall:$hallId to Slot:${slot.id} in Event:$eventId"))
+ assert(output.contains(exception.toString()))
+
+ verify { slotAssociateHall.associate(slot.id, eventId, hallId) }
+ }
+
+ @Test
+ fun `assignHallToSlot should log, call AssociateHall and return the result as a DTO`(output: CapturedOutput) {
+ every { slotAssociateHall.associate(slot.id, eventId, hallId) } returns slot
+
+ assertEquals(
+ ResponseEntity.ok(slot.toDto()),
+ slotController.assignHallToSlot(slot.id, eventId, hallId)
+ )
+ assert(output.contains("Assigning Hall:$hallId to Slot:${slot.id} in Event:$eventId"))
+
+ verify { slotAssociateHall.associate(slot.id, eventId, hallId) }
+ }
+
+ @Test
+ fun `resignHallFromSlot should log, call AssociateHall and return the result as a DTO`(output: CapturedOutput) {
+ every { slotAssociateHall.dissociate(slot.id, hallId) } just Runs
+
+ slotController.resignHallFromSlot(slot.id, hallId)
+ assert(output.contains("Resigning Hall:$hallId from Slot:${slot.id}"))
+
+ verify { slotAssociateHall.dissociate(slot.id, hallId) }
+ }
+ }
+ }
+
+ @Test
+ fun `deleteSlot should log and call CRUD`(output: CapturedOutput) {
+ val id = UUID.randomUUID()
+ every { slotCrud.delete(id) } just Runs
+
+ slotController.deleteSlot(id)
+ assert(output.contains("Deleting Slot:$id"))
+
+ verify { slotCrud.delete(id) }
+ }
+
+ @Test
+ fun `exportProgram should log, call EventGet, set the correct HTTP headers and pass the response to GenerateProgram`(output: CapturedOutput) {
+ val event = EventGen().generateOne()
+ val contentDisposition = "attachment; filename=\"program_${event.name}.pdf\""
+ val response = MockHttpServletResponse()
+
+ every { eventGet.getById(event.id) } returns event
+ every { slotGenerateProgram.generateEmptyProgramPdf(event.id, response.outputStream) } just Runs
+
+ slotController.exportProgram(event.id, response)
+ assert(output.contains("Generating program for Event:${event.id}"))
+
+ assertEquals(contentDisposition, response.getHeader(HttpHeaders.CONTENT_DISPOSITION))
+ assertEquals(MediaType.APPLICATION_PDF_VALUE, response.getHeader(HttpHeaders.CONTENT_TYPE))
+
+ verify { eventGet.getById(event.id) }
+ verify { slotGenerateProgram.generateEmptyProgramPdf(event.id, response.outputStream) }
+ }
+
+ @Nested
+ inner class TalksTests {
+ private var eventId = 0
+
+ @BeforeEach
+ fun setUp() {
+ eventId = Random.nextInt().absoluteValue
+ }
+
+ @Test
+ fun `listTalks should log, call getTalks and return a 404 if the event has no begin date`(
+ output: CapturedOutput
+ ) {
+ val exception = EventNoBeginException("No beginning date found for Event:$eventId")
+ every { getTalks.list(eventId) } throws exception
+
+ assertEquals(
+ ResponseEntity.status(HttpStatus.NOT_FOUND)
+ .body(exception.message),
+ slotController.listTalks(eventId)
+ )
+ assert(output.contains("Retrieving Talks from Event:$eventId"))
+ assert(output.contains(exception.toString()))
+
+ verify { getTalks.list(eventId) }
+ }
+
+ @Test
+ fun `listTalks should log, call getTalks and return the result as a list of DTOs`(
+ output: CapturedOutput
+ ) {
+ val talks = TalkGen().generateList()
+ every { getTalks.list(eventId) } returns talks
+
+ assertEquals(ResponseEntity.ok(talks.map { it.toDto() }), slotController.listTalks(eventId))
+ assert(output.contains("Retrieving Talks from Event:$eventId"))
+
+ verify { getTalks.list(eventId) }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/org/breizhcamp/konter/application/rest/SpeakerControllerTest.kt b/src/test/kotlin/org/breizhcamp/konter/application/rest/SpeakerControllerTest.kt
new file mode 100644
index 0000000..ac35cf6
--- /dev/null
+++ b/src/test/kotlin/org/breizhcamp/konter/application/rest/SpeakerControllerTest.kt
@@ -0,0 +1,47 @@
+package org.breizhcamp.konter.application.rest
+
+import com.ninjasquad.springmockk.MockkBean
+import io.mockk.Runs
+import io.mockk.every
+import io.mockk.just
+import io.mockk.verify
+import org.breizhcamp.konter.domain.use_cases.SpeakerImport
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.boot.test.system.CapturedOutput
+import org.springframework.boot.test.system.OutputCaptureExtension
+import org.springframework.http.MediaType
+import org.springframework.mock.web.MockMultipartFile
+import org.springframework.test.context.junit.jupiter.SpringExtension
+
+
+@ExtendWith(SpringExtension::class)
+@ExtendWith(OutputCaptureExtension::class)
+@WebMvcTest(SpeakerController::class)
+class SpeakerControllerTest {
+
+ @MockkBean
+ private lateinit var speakerImport: SpeakerImport
+
+ @Autowired
+ private lateinit var speakerController: SpeakerController
+
+ @Test
+ fun `importCsv should log and pass the input stream of the provided file to Import`(output: CapturedOutput) {
+ val file = MockMultipartFile(
+ "values.csv",
+ "values.csv",
+ MediaType.TEXT_PLAIN_VALUE,
+ "VALUES".toByteArray()
+ )
+
+ every { speakerImport.importCsv(any()) } just Runs
+
+ speakerController.importCsv(file)
+ assert(output.contains("Importing Speakers"))
+
+ verify { speakerImport.importCsv(any()) }
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/org/breizhcamp/konter/domain/use_cases/EventGetTest.kt b/src/test/kotlin/org/breizhcamp/konter/domain/use_cases/EventGetTest.kt
new file mode 100644
index 0000000..5cefeb4
--- /dev/null
+++ b/src/test/kotlin/org/breizhcamp/konter/domain/use_cases/EventGetTest.kt
@@ -0,0 +1,34 @@
+package org.breizhcamp.konter.domain.use_cases
+
+import com.ninjasquad.springmockk.MockkBean
+import io.mockk.every
+import io.mockk.verify
+import org.breizhcamp.konter.domain.use_cases.ports.EventPort
+import org.breizhcamp.konter.testUtils.EventGen
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.test.context.junit.jupiter.SpringExtension
+
+@ExtendWith(SpringExtension::class)
+@WebMvcTest(EventGet::class)
+class EventGetTest {
+
+ @MockkBean
+ private lateinit var eventPort: EventPort
+
+ @Autowired
+ private lateinit var eventGet: EventGet
+
+ @Test
+ fun `getById should call port with its input and return the result`() {
+ val event = EventGen().generateOne()
+ every { eventPort.getById(event.id) } returns event
+
+ assertEquals(event, eventGet.getById(event.id))
+
+ verify { eventPort.getById(event.id) }
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/org/breizhcamp/konter/domain/use_cases/GetTalksTest.kt b/src/test/kotlin/org/breizhcamp/konter/domain/use_cases/GetTalksTest.kt
new file mode 100644
index 0000000..91cba2e
--- /dev/null
+++ b/src/test/kotlin/org/breizhcamp/konter/domain/use_cases/GetTalksTest.kt
@@ -0,0 +1,100 @@
+package org.breizhcamp.konter.domain.use_cases
+
+import com.ninjasquad.springmockk.MockkBean
+import io.mockk.Runs
+import io.mockk.every
+import io.mockk.just
+import io.mockk.verify
+import org.breizhcamp.konter.domain.entities.Event
+import org.breizhcamp.konter.domain.entities.Talk
+import org.breizhcamp.konter.domain.entities.exceptions.EventNoBeginException
+import org.breizhcamp.konter.domain.use_cases.ports.EventPort
+import org.breizhcamp.konter.domain.use_cases.ports.KalonPort
+import org.breizhcamp.konter.testUtils.EventGen
+import org.breizhcamp.konter.testUtils.TalkGen
+import org.breizhcamp.konter.testUtils.generateRandomLocalDateTime
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+import org.junit.jupiter.api.extension.ExtendWith
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.test.context.junit.jupiter.SpringExtension
+import kotlin.math.absoluteValue
+import kotlin.random.Random
+
+@ExtendWith(SpringExtension::class)
+@WebMvcTest(GetTalks::class)
+class GetTalksTest {
+
+ @MockkBean
+ private lateinit var eventPort: EventPort
+
+ @MockkBean
+ private lateinit var kalonPort: KalonPort
+
+ @Autowired
+ private lateinit var getTalks: GetTalks
+
+ private lateinit var talks: List
+ private var eventId = 0
+
+ @BeforeEach
+ fun setUp() {
+ talks = TalkGen().generateList()
+ eventId = Random.nextInt().absoluteValue
+
+ every { kalonPort.getEvents() } returns emptyList()
+ every { eventPort.save(any>()) } just Runs
+ every { eventPort.exportTalks(eventId) } returns talks
+ }
+
+ @Test
+ fun `list should call kalon if the event has no begin date, and throw if it still has none after reload`() {
+ val event = EventGen().generateOne().copy(id = eventId, begin = null)
+ every { eventPort.getById(eventId) } returns event
+
+ assertThrows { getTalks.list(event.id) }
+
+ verify(exactly = 2) { eventPort.getById(event.id) }
+ verify { eventPort.save(any>()) }
+ verify { kalonPort.getEvents() }
+ verify(exactly = 0) { eventPort.exportTalks(event.id) }
+ }
+
+ @Test
+ fun `list should call kalon if the event has no begin date and proceed if it has one after reload`() {
+ val eventNoBegin = EventGen()
+ .generateOne()
+ .copy(id = eventId, begin = null)
+ val eventSetBegin = eventNoBegin.copy(
+ id = eventId,
+ begin = generateRandomLocalDateTime().toLocalDate()
+ )
+
+ every { eventPort.getById(eventId) } returnsMany listOf(eventNoBegin, eventSetBegin)
+
+ assertEquals(talks, getTalks.list(eventNoBegin.id))
+
+ verify(exactly = 2) { eventPort.getById(eventId) }
+ verify { eventPort.save(any>()) }
+ verify { kalonPort.getEvents() }
+ verify { eventPort.exportTalks(eventId) }
+ }
+
+ @Test
+ fun `list should not call kalon if the event has a begin date, and call eventPort to export the talks`() {
+ val event = EventGen()
+ .generateOne()
+ .copy(id = eventId, begin = generateRandomLocalDateTime().toLocalDate())
+ every { eventPort.getById(eventId) } returns event
+
+ assertEquals(talks, getTalks.list(eventId))
+
+ verify(exactly = 1) { eventPort.getById(eventId) }
+ verify(exactly = 0) { eventPort.save(any>()) }
+ verify(exactly = 0) { kalonPort.getEvents() }
+ verify { eventPort.exportTalks(eventId) }
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/org/breizhcamp/konter/domain/use_cases/HallAssociateEventTest.kt b/src/test/kotlin/org/breizhcamp/konter/domain/use_cases/HallAssociateEventTest.kt
new file mode 100644
index 0000000..37f99f2
--- /dev/null
+++ b/src/test/kotlin/org/breizhcamp/konter/domain/use_cases/HallAssociateEventTest.kt
@@ -0,0 +1,98 @@
+package org.breizhcamp.konter.domain.use_cases
+
+import com.ninjasquad.springmockk.MockkBean
+import io.mockk.*
+import org.breizhcamp.konter.domain.entities.Hall
+import org.breizhcamp.konter.domain.entities.exceptions.HallNotFoundException
+import org.breizhcamp.konter.domain.use_cases.ports.EventPort
+import org.breizhcamp.konter.domain.use_cases.ports.HallPort
+import org.breizhcamp.konter.domain.use_cases.ports.KalonPort
+import org.breizhcamp.konter.testUtils.HallGen
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+import org.junit.jupiter.api.extension.ExtendWith
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.test.context.junit.jupiter.SpringExtension
+import kotlin.math.absoluteValue
+import kotlin.random.Random
+
+@ExtendWith(SpringExtension::class)
+@WebMvcTest(HallAssociateEvent::class)
+class HallAssociateEventTest {
+
+ @MockkBean
+ private lateinit var hallPort: HallPort
+
+ @MockkBean
+ private lateinit var eventPort: EventPort
+
+ @MockkBean
+ private lateinit var kalonPort: KalonPort
+
+
+ @Autowired
+ private lateinit var hallAssociateEvent: HallAssociateEvent
+
+ private lateinit var hall: Hall
+ private var eventId: Int = 0
+ private var order: Int = 0
+
+ @BeforeEach
+ fun setUp() {
+ hall = HallGen().generateOne()
+ eventId = Random.nextInt().absoluteValue
+ order = Random.nextInt().absoluteValue
+
+ every { hallPort.associateToEvent(hall.id, eventId, order) } returns hall
+ }
+
+ @Test
+ fun `associate should call eventPort and throw if the Event is not found after reloading data from Kalon`() {
+ every { eventPort.existsById(eventId) } returns false
+ every { kalonPort.getEvents() } returns emptyList()
+ every { eventPort.save(emptyList()) } just Runs
+
+ assertThrows { hallAssociateEvent.associate(hall.id, eventId, order) }
+
+ verify(exactly = 2) { eventPort.existsById(eventId) }
+ verify { kalonPort.getEvents() }
+ verify { eventPort.save(emptyList()) }
+ verify { hallPort.associateToEvent(hall.id, eventId, order) wasNot Called }
+ }
+
+ @Test
+ fun `associate should call eventPort and continue as expected if the Event was found in Kalon`() {
+ every { eventPort.existsById(eventId) } returnsMany listOf(false, true)
+ every { kalonPort.getEvents() } returns emptyList()
+ every { eventPort.save(emptyList()) } just Runs
+
+ assertEquals(hall, hallAssociateEvent.associate(hall.id, eventId, order))
+
+ verify(exactly = 2) { eventPort.existsById(eventId) }
+ verify { kalonPort.getEvents() }
+ verify { eventPort.save(emptyList()) }
+ verify { hallPort.associateToEvent(hall.id, eventId, order) }
+ }
+
+ @Test
+ fun `associate should call Port with its inputs and return the result`() {
+ every { eventPort.existsById(eventId) } returns true
+
+ assertEquals(hall, hallAssociateEvent.associate(hall.id, eventId, order))
+
+ verify(exactly = 1) { eventPort.existsById(eventId) }
+ verify { hallPort.associateToEvent(hall.id, eventId, order) }
+ }
+
+ @Test
+ fun `dissociate should call Port with its inputs and return the result`() {
+ every { hallPort.dissociateFromEvent(hall.id, eventId) } returns hall
+
+ assertEquals(hall, hallAssociateEvent.dissociate(hall.id, eventId))
+
+ verify { hallPort.dissociateFromEvent(hall.id, eventId) }
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/org/breizhcamp/konter/domain/use_cases/HallCRUDTest.kt b/src/test/kotlin/org/breizhcamp/konter/domain/use_cases/HallCRUDTest.kt
new file mode 100644
index 0000000..e7246a0
--- /dev/null
+++ b/src/test/kotlin/org/breizhcamp/konter/domain/use_cases/HallCRUDTest.kt
@@ -0,0 +1,102 @@
+package org.breizhcamp.konter.domain.use_cases
+
+import com.ninjasquad.springmockk.MockkBean
+import io.mockk.Runs
+import io.mockk.every
+import io.mockk.just
+import io.mockk.verify
+import org.breizhcamp.konter.application.requests.HallCreationReq
+import org.breizhcamp.konter.application.requests.HallPatchReq
+import org.breizhcamp.konter.domain.entities.Hall
+import org.breizhcamp.konter.domain.use_cases.ports.HallPort
+import org.breizhcamp.konter.testUtils.HallGen
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Nested
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.test.context.junit.jupiter.SpringExtension
+import kotlin.math.absoluteValue
+import kotlin.random.Random
+
+@ExtendWith(SpringExtension::class)
+@WebMvcTest(HallCRUD::class)
+class HallCRUDTest {
+
+ @MockkBean
+ private lateinit var hallPort: HallPort
+
+ @Autowired
+ private lateinit var hallCRUD: HallCRUD
+
+ @Nested
+ inner class CUTests {
+ private lateinit var hall: Hall
+
+ @BeforeEach
+ fun setUp() {
+ hall = HallGen().generateOne()
+ }
+
+ @Test
+ fun `create should call Port with its input and return the result`() {
+ val req = HallCreationReq(requireNotNull(hall.name), hall.trackId)
+ every { hallPort.create(req) } returns hall
+
+ assertEquals(hall, hallCRUD.create(req))
+
+ verify { hallPort.create(req) }
+ }
+
+ @Test
+ fun `update should call Port with its inputs and return the result`() {
+ val req = HallPatchReq(requireNotNull(hall.name), hall.trackId)
+ every { hallPort.update(hall.id, req) } returns hall
+
+ assertEquals(hall, hallCRUD.update(hall.id, req))
+
+ verify { hallPort.update(hall.id, req) }
+ }
+ }
+
+ @Test
+ fun `delete should call Port with its input`() {
+ val id = Random.nextInt().absoluteValue
+ every { hallPort.delete(id) } just Runs
+
+ hallCRUD.delete(id)
+
+ verify { hallPort.delete(id) }
+ }
+
+ @Nested
+ inner class ReadListTests {
+ private lateinit var halls: List
+
+ @BeforeEach
+ fun setUp() {
+ halls = HallGen().generateList()
+ }
+
+ @Test
+ fun `listAll should call Port with a null value and return the result`() {
+ every { hallPort.list(null) } returns halls
+
+ assertEquals(halls, hallCRUD.listAll())
+
+ verify { hallPort.list(null) }
+ }
+
+ @Test
+ fun `listByEvent should call Port with its input and return the result`() {
+ val eventId = Random.nextInt().absoluteValue
+ every { hallPort.list(eventId) } returns halls
+
+ assertEquals(halls, hallCRUD.listByEvent(eventId))
+
+ verify { hallPort.list(eventId) }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/org/breizhcamp/konter/domain/use_cases/HallSetOrderTest.kt b/src/test/kotlin/org/breizhcamp/konter/domain/use_cases/HallSetOrderTest.kt
new file mode 100644
index 0000000..e428a48
--- /dev/null
+++ b/src/test/kotlin/org/breizhcamp/konter/domain/use_cases/HallSetOrderTest.kt
@@ -0,0 +1,47 @@
+package org.breizhcamp.konter.domain.use_cases
+
+import com.ninjasquad.springmockk.MockkBean
+import io.mockk.every
+import io.mockk.verify
+import org.breizhcamp.konter.domain.entities.Hall
+import org.breizhcamp.konter.domain.use_cases.ports.HallPort
+import org.breizhcamp.konter.testUtils.HallGen
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.test.context.junit.jupiter.SpringExtension
+import kotlin.math.absoluteValue
+import kotlin.random.Random
+
+@ExtendWith(SpringExtension::class)
+@WebMvcTest(HallSetOrder::class)
+class HallSetOrderTest {
+
+ @MockkBean
+ private lateinit var hallPort: HallPort
+
+ @Autowired
+ private lateinit var hallSetOrder: HallSetOrder
+
+ private lateinit var hall: Hall
+
+ @BeforeEach
+ fun setUp() {
+ hall = HallGen().generateOne()
+ }
+
+ @Test
+ fun `setOrder should call Port with its inputs and return the result`() {
+ val eventId = Random.nextInt().absoluteValue
+ val order = Random.nextInt().absoluteValue
+
+ every { hallPort.setOrderInEvent(hall.id, eventId, order) } returns hall
+
+ assertEquals(hall, hallSetOrder.setOrder(hall.id, eventId, order))
+
+ verify { hallPort.setOrderInEvent(hall.id, eventId, order) }
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/org/breizhcamp/konter/domain/use_cases/ManualSessionCRUDTest.kt b/src/test/kotlin/org/breizhcamp/konter/domain/use_cases/ManualSessionCRUDTest.kt
new file mode 100644
index 0000000..f086d88
--- /dev/null
+++ b/src/test/kotlin/org/breizhcamp/konter/domain/use_cases/ManualSessionCRUDTest.kt
@@ -0,0 +1,105 @@
+package org.breizhcamp.konter.domain.use_cases
+
+import com.ninjasquad.springmockk.MockkBean
+import io.mockk.Runs
+import io.mockk.every
+import io.mockk.just
+import io.mockk.verify
+import org.breizhcamp.konter.application.requests.SessionCreationReq
+import org.breizhcamp.konter.application.requests.SessionPatchReq
+import org.breizhcamp.konter.domain.entities.ManualSession
+import org.breizhcamp.konter.domain.use_cases.ports.ManualSessionPort
+import org.breizhcamp.konter.testUtils.ManualSessionGen
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Nested
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.test.context.junit.jupiter.SpringExtension
+import kotlin.math.absoluteValue
+import kotlin.random.Random
+
+@ExtendWith(SpringExtension::class)
+@WebMvcTest(ManualSessionCRUD::class)
+class ManualSessionCRUDTest {
+
+ @MockkBean
+ private lateinit var manualSessionPort: ManualSessionPort
+
+ @Autowired
+ private lateinit var manualSessionCRUD: ManualSessionCRUD
+
+ @Nested
+ inner class CRUTests {
+ private lateinit var session: ManualSession
+
+ @BeforeEach
+ fun setUp() {
+ session = ManualSessionGen().generateOne()
+ }
+
+ @Test
+ fun `create should call Port with its inputs and return the result`() {
+ val req = SessionCreationReq(
+ title = session.title,
+ description = session.description,
+ format = session.format,
+ theme = session.theme
+ )
+ val eventId = Random.nextInt().absoluteValue
+
+ every { manualSessionPort.create(req, eventId) } returns session
+
+ assertEquals(session, manualSessionCRUD.create(req, eventId))
+
+ verify { manualSessionPort.create(req, eventId) }
+ }
+
+ @Test
+ fun `get should call Port with its input and return the result`() {
+ every { manualSessionPort.getById(session.id) } returns session
+
+ assertEquals(session, manualSessionCRUD.get(session.id))
+
+ verify { manualSessionPort.getById(session.id) }
+ }
+
+ @Test
+ fun `update should call Port with its inputs and return the result`() {
+ val req = SessionPatchReq(
+ title = session.title,
+ description = session.description,
+ format = session.format,
+ theme = session.theme
+ )
+ every { manualSessionPort.update(session.id, req) } returns session
+
+ assertEquals(session, manualSessionCRUD.update(session.id, req))
+
+ verify { manualSessionPort.update(session.id, req) }
+ }
+ }
+
+ @Test
+ fun `list should call Port with its input and return the result`() {
+ val sessions = ManualSessionGen().generateList()
+ val eventId = Random.nextInt().absoluteValue
+ every { manualSessionPort.getAllByEventId(eventId) } returns sessions
+
+ assertEquals(sessions, manualSessionCRUD.list(eventId))
+
+ verify { manualSessionPort.getAllByEventId(eventId) }
+ }
+
+ @Test
+ fun `delete should call Port with its input`() {
+ val id = Random.nextInt().absoluteValue
+ every { manualSessionPort.delete(id) } just Runs
+
+ manualSessionCRUD.delete(id)
+
+ verify { manualSessionPort.delete(id) }
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/org/breizhcamp/konter/domain/use_cases/SessionImportTest.kt b/src/test/kotlin/org/breizhcamp/konter/domain/use_cases/SessionImportTest.kt
new file mode 100644
index 0000000..ceac1b7
--- /dev/null
+++ b/src/test/kotlin/org/breizhcamp/konter/domain/use_cases/SessionImportTest.kt
@@ -0,0 +1,245 @@
+package org.breizhcamp.konter.domain.use_cases
+
+import com.ninjasquad.springmockk.MockkBean
+import io.mockk.Runs
+import io.mockk.every
+import io.mockk.just
+import io.mockk.verify
+import org.breizhcamp.konter.domain.entities.Evaluation
+import org.breizhcamp.konter.domain.entities.Event
+import org.breizhcamp.konter.domain.entities.Session
+import org.breizhcamp.konter.domain.entities.enums.SessionFormatEnum
+import org.breizhcamp.konter.domain.entities.enums.SessionNiveauEnum
+import org.breizhcamp.konter.domain.entities.enums.SessionStatusEnum
+import org.breizhcamp.konter.domain.entities.enums.SessionThemeEnum
+import org.breizhcamp.konter.domain.use_cases.ports.EventPort
+import org.breizhcamp.konter.domain.use_cases.ports.KalonPort
+import org.breizhcamp.konter.domain.use_cases.ports.SessionPort
+import org.breizhcamp.konter.domain.use_cases.ports.SpeakerPort
+import org.breizhcamp.konter.testUtils.EventGen
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.boot.test.system.CapturedOutput
+import org.springframework.boot.test.system.OutputCaptureExtension
+import org.springframework.core.io.ResourceLoader
+import org.springframework.test.context.junit.jupiter.SpringExtension
+import java.io.InputStream
+import java.math.BigDecimal
+import java.time.LocalDateTime
+import java.time.Month
+import java.util.*
+
+@ExtendWith(SpringExtension::class)
+@ExtendWith(OutputCaptureExtension::class)
+@WebMvcTest(SessionImport::class)
+class SessionImportTest {
+
+ @MockkBean
+ private lateinit var sessionPort: SessionPort
+
+ @MockkBean
+ private lateinit var speakerPort: SpeakerPort
+
+ @MockkBean
+ private lateinit var eventPort: EventPort
+
+ @MockkBean
+ private lateinit var kalonPort: KalonPort
+
+ @Autowired
+ private lateinit var sessionImport: SessionImport
+
+ @Autowired
+ private lateinit var loader: ResourceLoader
+
+ private lateinit var event: Event
+ private lateinit var sessions: List
+ private lateinit var evals: List
+
+ private val hammond = SpeakerImportTest.speakers.find { it.id == UUID.fromString("dd41f4a0-d300-4692-977a-14de9aa5db72") }!!
+ private val brown = SpeakerImportTest.speakers.find { it.id == UUID.fromString("7b1cce9c-59e4-4d6e-9ee2-9e20f1224ce4") }!!
+ private val kent = SpeakerImportTest.speakers.find { it.id == UUID.fromString("17ac8d41-3167-455a-ba14-de73b20a4334") }!!
+ private val wayne = SpeakerImportTest.speakers.find { it.id == UUID.fromString("f9840ccd-0c22-4c6d-a712-be2c81847222") }!!
+
+ private lateinit var sessionFile: InputStream
+ private lateinit var evaluationFile: InputStream
+
+ @BeforeEach
+ fun setUp() {
+ sessionFile = loader.getResource("classpath:csv/session-test.csv").inputStream
+ evaluationFile = loader.getResource("classpath:csv/evaluation-test.csv").inputStream
+ event = EventGen().generateOne()
+ val hammondSession = Session(
+ id = 101,
+ title = "PSQL, une technologie de dinosaures ? \uD83D\uDC74\uD83C\uDFFB",
+ description = "PostgreSQL approche ses 30 ans de carrière, et reste un des systèmes de gestion de base de données les plus utilisés à ce jour\n" +
+ "\n" +
+ "À travers l'exemple de notre parc, découvrez pourquoi PSQL reste un système en accord avec notre temps et comment ses extensions permettent d'intégrer tous les types de données utilisés dans notre entreprise, allant de la localisation géographique des enclos aux codes génétiques de nos dinosaures.",
+ owner = hammond,
+ speakers = listOf(hammond),
+ format = SessionFormatEnum.CONFERENCE,
+ theme = SessionThemeEnum.ARCHI,
+ niveau = SessionNiveauEnum.INTRO,
+ status = SessionStatusEnum.NOMINATED,
+ submitted = LocalDateTime.of(2024, Month.APRIL, 14, 17, 32),
+ ownerNotes = "Après un bref rappel de ce qu'est une base de données relationnelle, cette conférence abordera les spécificités de Postgres, puis une présentation de notre cas d'usage. Ce cas d'usage a aussi pour but de présenter des extensions peu utilisées mais qui peuvent convenir à beaucoup de projets modernes, comme la gestion de données de localisation géographiques, de données vectorielles & de fichiers de grandes tailles",
+ event = event,
+ videoURL = null,
+ rating = null,
+ slot = null
+ )
+ val brownSession = Session(
+ id = 203,
+ title = "Le futur de l'IA",
+ description = "Conférence sur les futures formes de l'IA, et comment le monde en sera impacté",
+ owner = brown,
+ speakers = listOf(brown),
+ format = SessionFormatEnum.CONFERENCE,
+ theme = SessionThemeEnum.AI,
+ niveau = SessionNiveauEnum.INTRO,
+ status = SessionStatusEnum.NOMINATED,
+ submitted = LocalDateTime.of(2024, Month.APRIL, 15, 8, 12),
+ ownerNotes = "",
+ event = event,
+ videoURL = null,
+ rating = null,
+ slot = null
+ )
+ val wayneKentSession = Session(
+ id = 207,
+ title = "DĂ©voilons les Secrets de GitHub Actions",
+ description = "Découvrez comment gérer les secrets dans GitHub Actions pour sécuriser les workflows CI/CD.\n" +
+ "\n" +
+ "Clark aborde la création et la gestion des secrets, tandis que Bruce traite des techniques avancées comme la rotation et le partage sécurisé.\n" +
+ "\n" +
+ "Ensemble, nous montrons comment garantir des workflows efficaces et sécurisés dans GitHub Actions.",
+ owner = wayne,
+ speakers = listOf(wayne, kent),
+ format = SessionFormatEnum.TOOL,
+ theme = SessionThemeEnum.SEC,
+ niveau = SessionNiveauEnum.INTRO,
+ status = SessionStatusEnum.NOMINATED,
+ submitted = LocalDateTime.of(2024, Month.APRIL, 15, 8, 48),
+ ownerNotes = "**Résumé**\n" +
+ "\n" +
+ "*Dévoiler les Secrets de GitHub Actions : démonstration*\n" +
+ "\n" +
+ "Découvrez comment gérer les secrets dans GitHub Actions, essentiels pour automatiser les workflows tout en sécurisant des éléments comme des clés d'API, des tokens d'authentification, ...\n" +
+ "\n" +
+ "Clark Kent, journaliste au Daily Bugle présente les bases de la gestion des secrets :\n" +
+ "\n" +
+ "1. **Définition des Secrets :** Création et gestion des secrets dans les paramètres du dépôt.\n" +
+ "2. **Contrôle d'Accès :** Accès réservé aux workflows spécifiques.\n" +
+ "3. **Segmentation par Environnement :** Configuration des secrets pour différents environnements (développement, staging, production).\n" +
+ "\n" +
+ "Bruce Wayne, ex-PDG de Wayne Industries, aborde quant à lui des techniques plus avancées pour la protection de vos secrets :\n" +
+ "\n" +
+ "1. **Rotation des Secrets :** Mise à jour régulière et automatisée.\n" +
+ "2. **Audit et Surveillance :** Suivi de l'utilisation des secrets.\n" +
+ "3. **Partage de Secrets :** Pratiques sécurisées pour partager entre différents dépôts.\n" +
+ "\n" +
+ "Ensemble, nous fournissons un guide complet pour gérer les secrets dans GitHub Actions, en soulignant l'importance de la sécurité dans les pipelines CI/CD. À la fin de cette session, vous saurez comment utiliser GitHub Actions pour des workflows efficaces et sécurisés.",
+ event = event,
+ videoURL = null,
+ rating = null,
+ slot = null
+ )
+ sessions = listOf(hammondSession, brownSession, wayneKentSession)
+ evals = listOf(
+ Evaluation(hammondSession, BigDecimal.valueOf(3.9)),
+ Evaluation(brownSession, BigDecimal.valueOf(2.3)),
+ Evaluation(wayneKentSession, BigDecimal.valueOf(4.2))
+ )
+ every { eventPort.getById(event.id) } returns event
+ }
+
+ @Test
+ fun `importCsv should call kalonPort to update the events if the event was not found, and stop if it is not in kalon either`(
+ output: CapturedOutput
+ ) {
+ every { eventPort.existsById(event.id) } returns false
+ every { kalonPort.getEvents() } returns emptyList()
+ every { eventPort.save(emptyList()) } just Runs
+
+ sessionImport.importCsv(event.id, sessionFile)
+ assert(output.contains("No Event with id=${event.id} found, importing from Kalon"))
+ assert(output.contains("No Event with id=${event.id} found, exiting"))
+
+ verify(exactly = 2) { eventPort.existsById(event.id) }
+ verify(exactly = 0) { eventPort.getById(event.id) }
+ }
+
+ @Test
+ fun `importCsv should call kalonPort to update the events if the event was not found, and continue if it was found in kalon`(
+ output: CapturedOutput
+ ) {
+ every { eventPort.existsById(event.id) } returnsMany listOf(false, true)
+ every { kalonPort.getEvents() } returns listOf(event)
+ every { eventPort.save(listOf(event)) } just Runs
+
+ for (speaker in SpeakerImportTest.speakers) {
+ every { speakerPort.get(speaker.id) } returns speaker
+ every {
+ speakerPort.getByNameAndEmail(
+ "${speaker.firstname} ${speaker.lastname}",
+ speaker.email
+ )
+ } returns speaker
+ }
+
+ every { sessionPort.import(any()) } just Runs
+
+ sessionImport.importCsv(event.id, sessionFile)
+ assert(output.contains("No Event with id=${event.id} found, importing from Kalon"))
+
+ verify(exactly = 2) { eventPort.existsById(event.id) }
+ verify { kalonPort.getEvents() }
+ verify { eventPort.save(listOf(event)) }
+ verify { eventPort.getById(event.id) }
+ }
+
+ @Test
+ fun `importCsv should not kalonPort if the event wad found in the repo, retrieve session owners and speakers and log the number of imported sessions`(
+ output: CapturedOutput
+ ) {
+ every { eventPort.existsById(event.id) } returns true
+ every { kalonPort.getEvents() } returns listOf(event)
+ every { eventPort.save(listOf(event)) } just Runs
+
+ for (speaker in SpeakerImportTest.speakers) {
+ every { speakerPort.get(speaker.id) } returns speaker
+ every {
+ speakerPort.getByNameAndEmail(
+ "${speaker.firstname} ${speaker.lastname}",
+ speaker.email
+ )
+ } returns speaker
+ }
+ every { sessionPort.import(any()) } just Runs
+
+ sessionImport.importCsv(event.id, sessionFile)
+ assert(!output.contains("No Event with id=${event.id} found, importing from Kalon"))
+
+ verify(exactly = 2) { eventPort.existsById(event.id) }
+ verify(exactly = 0) { kalonPort.getEvents() }
+ verify(exactly = 0) { eventPort.save(listOf(event)) }
+ verify { eventPort.getById(event.id) }
+ }
+
+ @Test
+ fun importEvaluationCsv(output: CapturedOutput) {
+ for (session in sessions) {
+ every { sessionPort.getById(session.id) } returns session
+ }
+ for (eval in evals) {
+ every { sessionPort.saveEvaluation(eval) } just Runs
+ }
+
+ sessionImport.importEvaluationCsv(evaluationFile)
+
+ assert(output.contains("Saving [${evals.size}] Evaluations"))
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/org/breizhcamp/konter/domain/use_cases/SessionListTest.kt b/src/test/kotlin/org/breizhcamp/konter/domain/use_cases/SessionListTest.kt
new file mode 100644
index 0000000..19ee861
--- /dev/null
+++ b/src/test/kotlin/org/breizhcamp/konter/domain/use_cases/SessionListTest.kt
@@ -0,0 +1,57 @@
+package org.breizhcamp.konter.domain.use_cases
+
+import com.ninjasquad.springmockk.MockkBean
+import io.mockk.every
+import io.mockk.verify
+import org.breizhcamp.konter.domain.entities.Session
+import org.breizhcamp.konter.domain.entities.SessionFilter
+import org.breizhcamp.konter.domain.use_cases.ports.SessionPort
+import org.breizhcamp.konter.testUtils.SessionGen
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.test.context.junit.jupiter.SpringExtension
+import kotlin.math.absoluteValue
+import kotlin.random.Random
+
+@ExtendWith(SpringExtension::class)
+@WebMvcTest(SessionList::class)
+class SessionListTest {
+
+ @MockkBean
+ private lateinit var sessionPort: SessionPort
+
+ @Autowired
+ private lateinit var sessionList: SessionList
+
+ private lateinit var sessions: List
+
+ @BeforeEach
+ fun setUp() {
+ sessions = SessionGen().generateList()
+ }
+
+ @Test
+ fun `list should call Port with its input and false, and return the result`() {
+ val eventId = Random.nextInt().absoluteValue
+ every { sessionPort.getAllByEventId(eventId, false) } returns sessions
+
+ assertEquals(sessions, sessionList.list(eventId))
+
+ verify { sessionPort.getAllByEventId(eventId, false) }
+ }
+
+ @Test
+ fun `filter should call Port with its inputs and return the result`() {
+ val eventId = Random.nextInt().absoluteValue
+ val filter = SessionFilter.empty()
+ every { sessionPort.filterByEventId(eventId, filter) } returns sessions
+
+ assertEquals(sessions, sessionList.filter(eventId, filter))
+
+ verify { sessionPort.filterByEventId(eventId, filter) }
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/org/breizhcamp/konter/domain/use_cases/SlotAssociateHallTest.kt b/src/test/kotlin/org/breizhcamp/konter/domain/use_cases/SlotAssociateHallTest.kt
new file mode 100644
index 0000000..d05f6bc
--- /dev/null
+++ b/src/test/kotlin/org/breizhcamp/konter/domain/use_cases/SlotAssociateHallTest.kt
@@ -0,0 +1,59 @@
+package org.breizhcamp.konter.domain.use_cases
+
+import com.ninjasquad.springmockk.MockkBean
+import io.mockk.Runs
+import io.mockk.every
+import io.mockk.just
+import io.mockk.verify
+import org.breizhcamp.konter.domain.entities.Slot
+import org.breizhcamp.konter.domain.use_cases.ports.SlotPort
+import org.breizhcamp.konter.testUtils.SlotGen
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.test.context.junit.jupiter.SpringExtension
+import kotlin.math.absoluteValue
+import kotlin.random.Random
+
+@ExtendWith(SpringExtension::class)
+@WebMvcTest(SlotAssociateHall::class)
+class SlotAssociateHallTest {
+
+ @MockkBean
+ private lateinit var slotPort: SlotPort
+
+ @Autowired
+ private lateinit var slotAssociateHall: SlotAssociateHall
+
+ private lateinit var slot: Slot
+ private var eventId: Int = 0
+ private var hallId: Int = 0
+
+ @BeforeEach
+ fun setUp() {
+ slot = SlotGen().generateOne()
+ eventId = Random.nextInt().absoluteValue
+ hallId = Random.nextInt().absoluteValue
+ }
+
+ @Test
+ fun associate() {
+ every { slotPort.associateHall(slot.id, eventId, hallId) } returns slot
+
+ assertEquals(slot, slotAssociateHall.associate(slot.id, eventId, hallId))
+
+ verify { slotPort.associateHall(slot.id, eventId, hallId) }
+ }
+
+ @Test
+ fun dissociate() {
+ every { slotPort.dissociateHall(slot.id, hallId) } just Runs
+
+ slotAssociateHall.dissociate(slot.id, hallId)
+
+ verify { slotPort.dissociateHall(slot.id, hallId) }
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/org/breizhcamp/konter/domain/use_cases/SlotCRUDTest.kt b/src/test/kotlin/org/breizhcamp/konter/domain/use_cases/SlotCRUDTest.kt
new file mode 100644
index 0000000..3dacdc1
--- /dev/null
+++ b/src/test/kotlin/org/breizhcamp/konter/domain/use_cases/SlotCRUDTest.kt
@@ -0,0 +1,106 @@
+package org.breizhcamp.konter.domain.use_cases
+
+import com.ninjasquad.springmockk.MockkBean
+import io.mockk.Runs
+import io.mockk.every
+import io.mockk.just
+import io.mockk.verify
+import org.breizhcamp.konter.application.requests.SlotCreationReq
+import org.breizhcamp.konter.application.requests.SlotPatchReq
+import org.breizhcamp.konter.domain.entities.Slot
+import org.breizhcamp.konter.domain.use_cases.ports.SlotPort
+import org.breizhcamp.konter.testUtils.HallGen
+import org.breizhcamp.konter.testUtils.SlotGen
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Nested
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.test.context.junit.jupiter.SpringExtension
+import java.util.*
+import kotlin.math.absoluteValue
+import kotlin.random.Random
+
+@ExtendWith(SpringExtension::class)
+@WebMvcTest(SlotCRUD::class)
+class SlotCRUDTest {
+
+ @MockkBean
+ private lateinit var slotPort: SlotPort
+
+ @Autowired
+ private lateinit var slotCRUD: SlotCRUD
+
+ @Nested
+ inner class CRUTests {
+ private lateinit var slot: Slot
+
+ @BeforeEach
+ fun setUp() {
+ slot = SlotGen().generateOne()
+ }
+
+ @Test
+ fun `create should call Port with its inputs and return the result`() {
+ val hallId = Random.nextInt().absoluteValue
+ val eventId = Random.nextInt().absoluteValue
+ val req = SlotCreationReq(slot.start, slot.day, slot.duration, slot.title, listOf(hallId), slot.assignable)
+
+ every { slotPort.create(eventId, req) } returns slot
+
+ assertEquals(slot, slotCRUD.create(eventId, req))
+
+ verify { slotPort.create(eventId, req) }
+ }
+
+ @Test
+ fun `get should call Port with its input and return the result`() {
+ every { slotPort.getById(slot.id) } returns slot
+
+ assertEquals(slot, slotCRUD.get(slot.id))
+
+ verify { slotPort.getById(slot.id) }
+ }
+
+ @Test
+ fun `update should call Port with its inputs and return the result`() {
+ val req = SlotPatchReq(slot.title, slot.assignable)
+ every { slotPort.update(slot.id, req) } returns slot
+
+ assertEquals(slot, slotCRUD.update(slot.id, req))
+
+ verify { slotPort.update(slot.id, req) }
+ }
+ }
+
+ @Test
+ fun `list should call Port with its input and return the result`() {
+ val day = Random.nextInt().absoluteValue
+ val hall = HallGen().generateOne()
+ val slots = SlotGen().generateList()
+ val program = mapOf(
+ Pair(day, mapOf(
+ Pair(hall, slots)
+ ))
+ )
+ val eventId = Random.nextInt().absoluteValue
+
+ every { slotPort.getProgram(eventId) } returns program
+
+ assertEquals(program, slotCRUD.list(eventId))
+
+ verify { slotPort.getProgram(eventId) }
+ }
+
+ @Test
+ fun `delete should call Port with its input`() {
+ val id = UUID.randomUUID()
+ every { slotPort.remove(id) } just Runs
+
+ slotCRUD.delete(id)
+
+ verify { slotPort.remove(id) }
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/org/breizhcamp/konter/domain/use_cases/SlotSetSessionTest.kt b/src/test/kotlin/org/breizhcamp/konter/domain/use_cases/SlotSetSessionTest.kt
new file mode 100644
index 0000000..3f0a643
--- /dev/null
+++ b/src/test/kotlin/org/breizhcamp/konter/domain/use_cases/SlotSetSessionTest.kt
@@ -0,0 +1,55 @@
+package org.breizhcamp.konter.domain.use_cases
+
+import com.ninjasquad.springmockk.MockkBean
+import io.mockk.every
+import io.mockk.verify
+import org.breizhcamp.konter.domain.entities.Session
+import org.breizhcamp.konter.domain.use_cases.ports.SessionPort
+import org.breizhcamp.konter.testUtils.SessionGen
+import org.breizhcamp.konter.testUtils.generateRandomHexString
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.test.context.junit.jupiter.SpringExtension
+import java.util.*
+
+@ExtendWith(SpringExtension::class)
+@WebMvcTest(SlotSetSession::class)
+class SlotSetSessionTest {
+
+ @MockkBean
+ private lateinit var sessionPort: SessionPort
+
+ @Autowired
+ private lateinit var slotSetSession: SlotSetSession
+
+ private lateinit var session: Session
+
+ @BeforeEach
+ fun setUp() {
+ session = SessionGen().generateOne()
+ }
+
+ @Test
+ fun `setById should call Port with its inputs and return the result`() {
+ val slotId = UUID.randomUUID()
+ every { sessionPort.setSlotById(session.id, slotId) } returns session
+
+ assertEquals(session, slotSetSession.setById(session.id, slotId))
+
+ verify { sessionPort.setSlotById(session.id, slotId) }
+ }
+
+ @Test
+ fun `setByBarcode should call Port with its inputs and return the result`() {
+ val barcode = generateRandomHexString()
+ every { sessionPort.setSlotByBarcode(session.id, barcode) } returns session
+
+ assertEquals(session, slotSetSession.setByBarcode(session.id, barcode))
+
+ verify { sessionPort.setSlotByBarcode(session.id, barcode) }
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/org/breizhcamp/konter/domain/use_cases/SpeakerImportTest.kt b/src/test/kotlin/org/breizhcamp/konter/domain/use_cases/SpeakerImportTest.kt
new file mode 100644
index 0000000..41ffd49
--- /dev/null
+++ b/src/test/kotlin/org/breizhcamp/konter/domain/use_cases/SpeakerImportTest.kt
@@ -0,0 +1,89 @@
+package org.breizhcamp.konter.domain.use_cases
+
+import com.ninjasquad.springmockk.MockkBean
+import io.mockk.Runs
+import io.mockk.every
+import io.mockk.just
+import io.mockk.verify
+import org.breizhcamp.konter.domain.entities.Speaker
+import org.breizhcamp.konter.domain.use_cases.ports.SpeakerPort
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.boot.test.system.CapturedOutput
+import org.springframework.boot.test.system.OutputCaptureExtension
+import org.springframework.core.io.ResourceLoader
+import org.springframework.test.context.junit.jupiter.SpringExtension
+import java.util.*
+
+@ExtendWith(SpringExtension::class)
+@ExtendWith(OutputCaptureExtension::class)
+@WebMvcTest(SpeakerImport::class)
+class SpeakerImportTest {
+
+ @MockkBean
+ private lateinit var speakerPort: SpeakerPort
+
+ @Autowired
+ private lateinit var speakerImport: SpeakerImport
+
+ @Autowired
+ private lateinit var resourceLoader: ResourceLoader
+
+ @Test
+ fun `importCsv should read all the values excluding the header, log and call the port to save them`(output: CapturedOutput) {
+ val file = resourceLoader.getResource("classpath:csv/speaker-test.csv").inputStream
+ for (speaker in speakers) {
+ every { speakerPort.save(speaker) } just Runs
+ }
+
+ speakerImport.importCsv(file)
+ assert(output.contains("Saving [${speakers.size}] Speakers"))
+
+ for (speaker in speakers) {
+ verify { speakerPort.save(speaker) }
+ }
+ }
+
+ companion object {
+ val speakers = listOf(
+ Speaker(
+ id = UUID.fromString("dd41f4a0-d300-4692-977a-14de9aa5db72"),
+ lastname = "Hammond",
+ firstname = "John",
+ email = "john.hammond@jurassic-park.fr",
+ tagLine = "J'ai dépensé sans compter",
+ bio = "Président Directeur Général chez InGen LLC",
+ profilePicture = "https://static.wikia.nocookie.net/jurassicpark/images/d/d3/Hammond_%281993%29.PNG/revision/latest?cb=20210227191455&path-prefix=fr"
+ ),
+ Speaker(
+ id = UUID.fromString("7b1cce9c-59e4-4d6e-9ee2-9e20f1224ce4"),
+ lastname = "Brown",
+ firstname = "Emmett",
+ email = "doc@back-to-the-future.net",
+ tagLine = "LĂ oĂą on va on n'a pas besoin de route",
+ bio = "Inventeur du voyage dans le temps, mais surtout fan d'horloges :)",
+ profilePicture = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/34/Emmett_Brown_Back_to_the_Future_Universal_Studios_Florida.JPG/800px-Emmett_Brown_Back_to_the_Future_Universal_Studios_Florida.JPG"
+ ),
+ Speaker(
+ id = UUID.fromString("f9840ccd-0c22-4c6d-a712-be2c81847222"),
+ lastname = "Kent",
+ firstname = "Clark",
+ email = "clark.kent@daily-bugle.com",
+ tagLine = "Mais non, je n'ai rien Ă voir avec ce Superman dont vous parlez",
+ bio = "Journaliste au Daily Bugle le jour, et mec normal la nuit",
+ profilePicture = "https://i.pinimg.com/originals/f2/35/78/f235783e1a67ed5bb6de83afd324dcaa.jpg"
+ ),
+ Speaker(
+ id = UUID.fromString("17ac8d41-3167-455a-ba14-de73b20a4334"),
+ lastname = "Wayne",
+ firstname = "Bruce",
+ email = "bruce.wayne@wayne-industries.com",
+ tagLine = "You didn't get the memo?",
+ bio = "Ancien PDG de Wayne Industries, je consacre maintenant ma vie Ă mes oeuvres de bienfaisance",
+ profilePicture = "https://tse1.mm.bing.net/th?id=OIP.lyXmlreDj9JKFtF5gFAPwQHaKF&pid=Api"
+ )
+ )
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/org/breizhcamp/konter/infrastructure/db/EventAdapterTest.kt b/src/test/kotlin/org/breizhcamp/konter/infrastructure/db/EventAdapterTest.kt
new file mode 100644
index 0000000..b1be4c6
--- /dev/null
+++ b/src/test/kotlin/org/breizhcamp/konter/infrastructure/db/EventAdapterTest.kt
@@ -0,0 +1,255 @@
+package org.breizhcamp.konter.infrastructure.db
+
+import com.ninjasquad.springmockk.MockkBean
+import io.mockk.every
+import io.mockk.slot
+import io.mockk.verify
+import org.breizhcamp.konter.domain.entities.exceptions.HallNotFoundException
+import org.breizhcamp.konter.infrastructure.db.mappers.toEvent
+import org.breizhcamp.konter.infrastructure.db.mappers.toHall
+import org.breizhcamp.konter.infrastructure.db.mappers.toSpeaker
+import org.breizhcamp.konter.infrastructure.db.model.EventDB
+import org.breizhcamp.konter.infrastructure.db.model.HallDB
+import org.breizhcamp.konter.infrastructure.db.model.SlotDB
+import org.breizhcamp.konter.infrastructure.db.repos.EventRepo
+import org.breizhcamp.konter.infrastructure.db.repos.HallRepo
+import org.breizhcamp.konter.testUtils.EventDBGen
+import org.breizhcamp.konter.testUtils.HallDBGen
+import org.breizhcamp.konter.testUtils.ImportSlotDBGen
+import org.breizhcamp.konter.testUtils.ManualSlotDBGen
+import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Nested
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+import org.junit.jupiter.api.extension.ExtendWith
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.ValueSource
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.test.context.junit.jupiter.SpringExtension
+import java.time.temporal.ChronoUnit
+import java.util.*
+import kotlin.math.absoluteValue
+import kotlin.random.Random
+
+@ExtendWith(SpringExtension::class)
+@WebMvcTest(EventAdapter::class)
+class EventAdapterTest {
+
+ @MockkBean
+ private lateinit var eventRepo: EventRepo
+
+ @MockkBean
+ private lateinit var hallRepo: HallRepo
+
+ @Autowired
+ private lateinit var eventAdapter: EventAdapter
+
+ @ParameterizedTest
+ @ValueSource(booleans = [false, true])
+ fun `existsById should call repo and return the result`(exists: Boolean) {
+ val id = Random.nextInt().absoluteValue
+ every { eventRepo.existsById(id) } returns exists
+
+ assertEquals(exists, eventAdapter.existsById(id))
+
+ verify { eventRepo.existsById(id) }
+ }
+
+ @Nested
+ inner class CreateReadTests {
+ private lateinit var event: EventDB
+
+ @BeforeEach
+ fun setUp() {
+ event = EventDBGen().generateOne()
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = [false, true])
+ fun `getById should call repo, return the result if the event was found and throw if not`(
+ exists: Boolean
+ ) {
+ every { eventRepo.findById(event.id) } returns
+ if (exists) Optional.of(event)
+ else Optional.empty()
+
+ if (exists) {
+ assertEquals(event.toEvent(), eventAdapter.getById(event.id))
+ } else {
+ assertThrows { eventAdapter.getById(event.id) }
+ }
+
+ verify { eventRepo.findById(event.id) }
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = [false, true])
+ fun `save should call repo`(exists: Boolean) {
+ every { eventRepo.findById(event.id) } returns
+ if (exists) Optional.of(event)
+ else Optional.empty()
+ every { eventRepo.save(event) } returns event
+
+ eventAdapter.save(event.toEvent())
+
+ verify { eventRepo.findById(event.id) }
+ verify { eventRepo.save(event) }
+ }
+ }
+
+ @Test
+ fun `listSave should call repo for each element`() {
+ val events = EventDBGen().generateList()
+ val eventDBSlot = slot()
+ every { eventRepo.save(capture(eventDBSlot)) } answers {
+ eventDBSlot.captured
+ }
+ every { eventRepo.findById(any()) } returns Optional.empty()
+
+ eventAdapter.save(events.map { it.toEvent() })
+
+ verify(exactly = events.size) { eventRepo.save(any()) }
+ verify(exactly = events.size) { eventRepo.findById(any()) }
+ }
+
+ @Test
+ fun `exportTalks should call repos and join manual sessions and sessions as Talks, then return it`() {
+ val halls = HallDBGen().generateList()
+ val event = EventDBGen().generateOne().copy(halls = halls.toSet())
+ val manualSlots = ManualSlotDBGen()
+ .generateList()
+ .map { it.copy(halls = halls.toSet()) }
+ val manualTalks = manualSlots.map { it.toManualTalk(event, halls) }
+ val normalSlots = ImportSlotDBGen()
+ .generateList()
+ .map { it.copy(halls = halls.toSet()) }
+ val normalTalks = normalSlots.map { it.toImportTalk(event, halls) }
+ val slots = manualSlots + normalSlots
+ val talks = manualTalks + normalTalks
+
+ every { eventRepo.findById(event.id) } returns Optional.of(event)
+ every { hallRepo.getAllByAvailableEventId(event.id) } returns halls
+ every { eventRepo.listSlotsHeld(event.id) } returns slots
+
+ assertEquals(talks, eventAdapter.exportTalks(event.id))
+
+ verify { eventRepo.findById(event.id) }
+ verify { hallRepo.getAllByAvailableEventId(event.id) }
+ verify { eventRepo.listSlotsHeld(event.id) }
+ }
+
+ @Nested
+ inner class TalkMapping {
+
+ private lateinit var event: EventDB
+ private lateinit var halls: List
+
+ private lateinit var manualSlot: SlotDB
+ private lateinit var importedSlot: SlotDB
+
+
+ @BeforeEach
+ fun setUp() {
+ event = EventDBGen().generateOne()
+ halls = HallDBGen().generateList()
+
+ manualSlot = ManualSlotDBGen().generateOne().copy(halls = halls.toSet())
+ importedSlot = ImportSlotDBGen().generateOne().copy(halls = halls.toSet())
+ }
+
+ @Test
+ fun `computeDate should throw if event begin is null`() {
+ val nullBeginEvent = event.copy(begin = null)
+ assertThrows { manualSlot.computeDate(nullBeginEvent) }
+ }
+
+ @Test
+ fun `computeDate should return the date of the start of the slot based on the begin date of the event`() {
+ assertNotNull(event.begin)
+ val expectedDate = requireNotNull(event.begin)
+ .plus((manualSlot.day - 1).toLong(), ChronoUnit.DAYS)
+
+ assertEquals(expectedDate, manualSlot.computeDate(event))
+ }
+
+ @Test
+ fun `toManualTalk should throw if the slot has a null manualSession`() {
+ assertThrows { importedSlot.toManualTalk(event, halls) }
+ }
+
+ @Test
+ fun `toManualTalk should throw if slot halls have an empty intersection with availableHalls`() {
+ assertThrows { manualSlot.toManualTalk(event, emptyList()) }
+ }
+
+ @Test
+ fun `toManualTalk should use the manualSession values`() {
+ val date = manualSlot.computeDate(event)
+
+ assertNotNull(manualSlot.manualSession)
+ val manualSession = requireNotNull(manualSlot.manualSession)
+
+ val talk = manualSlot.toManualTalk(event, halls)
+
+ assertEquals(manualSession.id, talk.id)
+ assertEquals(manualSession.title, talk.name)
+ assertEquals(manualSession.theme, talk.eventType)
+ assertEquals(manualSession.format, talk.format)
+ assertEquals(manualSession.description, talk.description)
+
+ assertEquals(manualSlot.start.atDate(date), talk.eventStart)
+ assertEquals(
+ manualSlot.start.plus(manualSlot.duration).atDate(date),
+ talk.eventEnd
+ )
+
+ assertEquals(manualSession.speakers.map { it.toSpeaker() }, talk.speakers)
+ assertEquals(manualSlot.halls.first().toHall(), talk.hall)
+
+ assertNull(talk.videoUrl)
+ assertNull(talk.filesUrl)
+ assertNull(talk.slidesUrl)
+ }
+
+ @Test
+ fun `toImportTalk should throw if the slot has a null session`() {
+ assertThrows { manualSlot.toImportTalk(event, halls) }
+ }
+
+ @Test
+ fun `toImportTalk should throw if slot halls have an empty intersection with availableHalls`() {
+ assertThrows { importedSlot.toImportTalk(event, emptyList()) }
+ }
+
+ @Test
+ fun `toImportTalk should use the session values`() {
+ val date = importedSlot.computeDate(event)
+
+ assertNotNull(importedSlot.session)
+ val session = requireNotNull(importedSlot.session)
+
+ val talk = importedSlot.toImportTalk(event, halls)
+
+ assertEquals(session.id, talk.id)
+ assertEquals(session.title, talk.name)
+ assertEquals(session.theme, talk.eventType)
+ assertEquals(session.format, talk.format)
+ assertEquals(session.description, talk.description)
+
+ assertEquals(importedSlot.start.atDate(date), talk.eventStart)
+ assertEquals(
+ importedSlot.start.plus(importedSlot.duration).atDate(date),
+ talk.eventEnd
+ )
+
+ assertEquals(session.speakers.map { it.toSpeaker() }, talk.speakers)
+ assertEquals(importedSlot.halls.first().toHall(), talk.hall)
+
+ assertNull(talk.videoUrl)
+ assertNull(talk.filesUrl)
+ assertNull(talk.slidesUrl)
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/org/breizhcamp/konter/infrastructure/db/HallAdapterTest.kt b/src/test/kotlin/org/breizhcamp/konter/infrastructure/db/HallAdapterTest.kt
new file mode 100644
index 0000000..7e18ad1
--- /dev/null
+++ b/src/test/kotlin/org/breizhcamp/konter/infrastructure/db/HallAdapterTest.kt
@@ -0,0 +1,216 @@
+package org.breizhcamp.konter.infrastructure.db
+
+import com.ninjasquad.springmockk.MockkBean
+import io.mockk.Runs
+import io.mockk.every
+import io.mockk.just
+import io.mockk.verify
+import org.breizhcamp.konter.application.requests.HallCreationReq
+import org.breizhcamp.konter.application.requests.HallPatchReq
+import org.breizhcamp.konter.infrastructure.db.mappers.toHall
+import org.breizhcamp.konter.infrastructure.db.model.HallDB
+import org.breizhcamp.konter.infrastructure.db.repos.HallRepo
+import org.breizhcamp.konter.testUtils.HallDBGen
+import org.breizhcamp.konter.testUtils.generateRandomHexString
+import org.junit.jupiter.api.*
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.extension.ExtendWith
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.EnumSource
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.test.context.junit.jupiter.SpringExtension
+import java.util.*
+import kotlin.math.absoluteValue
+import kotlin.random.Random
+
+@ExtendWith(SpringExtension::class)
+@WebMvcTest(HallAdapter::class)
+class HallAdapterTest {
+
+ @MockkBean
+ private lateinit var hallRepo: HallRepo
+
+ @Autowired
+ private lateinit var hallAdapter: HallAdapter
+
+ enum class UpdateCases {
+ NoUpdate,
+ UpdateName,
+ UpdateTrackId
+ }
+
+ @Nested
+ inner class ListTests {
+ private lateinit var halls: List
+
+ @BeforeEach
+ fun setUp() {
+ halls = HallDBGen().generateList()
+ }
+
+ @Test
+ fun `list with null eventId should call repo to retrieve all HallDBs regardless of event, and return them as a List of Hall`() {
+ every { hallRepo.findAll() } returns halls
+
+ assertEquals(halls.map { it.toHall() }, hallAdapter.list(null))
+
+ verify { hallRepo.findAll() }
+ }
+
+ @Test
+ fun `list with non null eventId should call repo to retrieve HallDBs that are available in the corresponding event, and return them as a List of Hall`() {
+ val eventId = Random.nextInt().absoluteValue
+ every { hallRepo.getAllByAvailableEventId(eventId) } returns halls
+
+ assertEquals(halls.map { it.toHall() }, hallAdapter.list(eventId))
+
+ verify { hallRepo.getAllByAvailableEventId(eventId) }
+ }
+ }
+
+ @Nested
+ inner class CreateTests {
+ private lateinit var hall: HallDB
+ private lateinit var req: HallCreationReq
+
+ @BeforeEach
+ fun setUp() {
+ hall = HallDBGen().generateOne()
+ req = HallCreationReq(requireNotNull(hall.name), null)
+
+ every { hallRepo.findById(hall.id) } returns Optional.of(hall)
+ every { hallRepo.create(req.name) } returns hall.id
+ }
+
+ @AfterEach
+ fun breakDown() {
+ verify { hallRepo.findById(hall.id) }
+ verify { hallRepo.create(req.name) }
+ }
+
+ @Test
+ fun `create should call repo to create, only retrieve the hall if the trackId is null and return it as a Hall`() {
+ every { hallRepo.addTrackIdToHall(any(), any()) } just Runs
+
+ assertEquals(hall.toHall(), hallAdapter.create(req))
+
+ verify(exactly = 0) { hallRepo.addTrackIdToHall(any(), any()) }
+ }
+
+ @Test
+ fun `create should call repo to create, add the trackId if it is not null, retrieve the hall and return it as a Hall`() {
+ val trackId = Random.nextInt().absoluteValue
+ req = req.copy(trackId = trackId)
+ every { hallRepo.addTrackIdToHall(hall.id, trackId) } just Runs
+
+ assertEquals(hall.toHall(), hallAdapter.create(req))
+
+ verify { hallRepo.addTrackIdToHall(hall.id, trackId) }
+ }
+ }
+
+ @Nested
+ inner class UpdateTests {
+ private lateinit var hall: HallDB
+
+ @BeforeEach
+ fun setUp() {
+ hall = HallDBGen().generateOne()
+
+ every { hallRepo.findById(hall.id) } returns Optional.of(hall)
+ }
+
+ @AfterEach
+ fun breakDown() {
+ verify { hallRepo.findById(hall.id) }
+ }
+
+ @Test
+ fun `associateToEvent should call repo and return the result as a Hall`() {
+ val eventId = Random.nextInt().absoluteValue
+ val order = Random.nextInt().absoluteValue
+ every { hallRepo.associateToEvent(hall.id, eventId, order) } just Runs
+
+ assertEquals(hall.toHall(), hallAdapter.associateToEvent(hall.id, eventId, order))
+
+ verify { hallRepo.associateToEvent(hall.id, eventId, order) }
+ }
+
+ @Test
+ fun `dissociateFromEvent should call repo and return the result as a Hall`() {
+ val eventId = Random.nextInt().absoluteValue
+ every { hallRepo.dissociateFromEvent(hall.id, eventId) } just Runs
+ every { hallRepo.getAllByOrderAfterHallInEvent(eventId, hall.id) } returns emptyList()
+
+ assertEquals(hall.toHall(), hallAdapter.dissociateFromEvent(hall.id, eventId))
+
+ verify { hallRepo.dissociateFromEvent(hall.id, eventId) }
+ verify { hallRepo.getAllByOrderAfterHallInEvent(eventId, hall.id) }
+ }
+
+ @Test
+ fun `setOrderInEvent should call repo and return the result as a Hall`() {
+ val eventId = Random.nextInt().absoluteValue
+ val order = Random.nextInt().absoluteValue
+ every { hallRepo.setOrderInEvent(hall.id, eventId, order) } just Runs
+
+ assertEquals(hall.toHall(), hallAdapter.setOrderInEvent(hall.id, eventId, order))
+
+ verify { hallRepo.setOrderInEvent(hall.id, eventId, order) }
+ }
+
+ @ParameterizedTest
+ @EnumSource(UpdateCases::class)
+ fun `update should call repo and return the result as a Hall`(case: UpdateCases) {
+ val (request, result) = when(case) {
+ UpdateCases.NoUpdate -> {
+ Pair(HallPatchReq(requireNotNull(hall.name), hall.trackId), hall)
+ }
+ UpdateCases.UpdateName -> {
+ val newName = generateRandomHexString()
+ Pair(HallPatchReq(newName, hall.trackId), hall.copy(name = newName))
+ }
+ UpdateCases.UpdateTrackId -> {
+ val newTrackId = Random.nextInt().absoluteValue
+ Pair(HallPatchReq(requireNotNull(hall.name), newTrackId), hall.copy(trackId = newTrackId))
+ }
+ }
+ every { hallRepo.save(result) } returns result
+
+ assertEquals(result.toHall(), hallAdapter.update(hall.id, request))
+
+ verify { hallRepo.save(result) }
+ }
+ }
+
+ @Test
+ fun `delete should call repo`() {
+ val id = Random.nextInt().absoluteValue
+ every { hallRepo.deleteById(id) } just Runs
+
+ hallAdapter.delete(id)
+
+ verify { hallRepo.deleteById(id) }
+ }
+
+ @Test
+ fun `get should throw if the hall was not found`() {
+ val id = Random.nextInt().absoluteValue
+ every { hallRepo.findById(id) } returns Optional.empty()
+
+ assertThrows { hallAdapter.get(id) }
+
+ verify { hallRepo.findById(id) }
+ }
+
+ @Test
+ fun `get should return the result as a Hall if it was found`() {
+ val hall = HallDBGen().generateOne()
+ every { hallRepo.findById(hall.id) } returns Optional.of(hall)
+
+ assertEquals(hall.toHall(), hallAdapter.get(hall.id))
+
+ verify { hallRepo.findById(hall.id) }
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/org/breizhcamp/konter/infrastructure/db/ManualSessionAdapterTest.kt b/src/test/kotlin/org/breizhcamp/konter/infrastructure/db/ManualSessionAdapterTest.kt
new file mode 100644
index 0000000..aace71f
--- /dev/null
+++ b/src/test/kotlin/org/breizhcamp/konter/infrastructure/db/ManualSessionAdapterTest.kt
@@ -0,0 +1,185 @@
+package org.breizhcamp.konter.infrastructure.db
+
+import com.ninjasquad.springmockk.MockkBean
+import io.mockk.Runs
+import io.mockk.every
+import io.mockk.just
+import io.mockk.verify
+import org.breizhcamp.konter.application.requests.SessionCreationReq
+import org.breizhcamp.konter.application.requests.SessionPatchReq
+import org.breizhcamp.konter.domain.entities.enums.SessionFormatEnum
+import org.breizhcamp.konter.domain.entities.enums.SessionThemeEnum
+import org.breizhcamp.konter.infrastructure.db.mappers.toManualSession
+import org.breizhcamp.konter.infrastructure.db.model.ManualSessionDB
+import org.breizhcamp.konter.infrastructure.db.repos.ManualSessionRepo
+import org.breizhcamp.konter.testUtils.ManualSessionDBGen
+import org.breizhcamp.konter.testUtils.generateRandomHexString
+import org.junit.jupiter.api.*
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.extension.ExtendWith
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.EnumSource
+import org.junit.jupiter.params.provider.ValueSource
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.test.context.junit.jupiter.SpringExtension
+import java.util.*
+import kotlin.math.absoluteValue
+import kotlin.random.Random
+
+@ExtendWith(SpringExtension::class)
+@WebMvcTest(ManualSessionAdapter::class)
+class ManualSessionAdapterTest {
+
+ enum class UpdateCases {
+ NoUpdate,
+ UpdateTitle,
+ UpdateDescription,
+ UpdateFormat,
+ UpdateTheme
+ }
+
+ @MockkBean
+ private lateinit var manualSessionRepo: ManualSessionRepo
+
+ @Autowired
+ private lateinit var manualSessionAdapter: ManualSessionAdapter
+
+ @TestInstance(TestInstance.Lifecycle.PER_CLASS)
+ @Nested
+ inner class CreateAndUpdateTests {
+ private lateinit var session: ManualSessionDB
+
+ @BeforeEach
+ fun setUp() {
+ session = ManualSessionDBGen().generateOne()
+ every { manualSessionRepo.findById(session.id) } returns Optional.of(session)
+ }
+
+ @AfterEach
+ fun breakDown() {
+ verify { manualSessionRepo.findById(session.id) }
+ }
+
+ @Test
+ fun `create should call repo to submit the creation request, call it again to retrieve the session and return it as a ManualSession`() {
+ val request = SessionCreationReq(
+ title = session.title,
+ description = session.description,
+ format = session.format,
+ theme = session.theme
+ )
+ val eventId = Random.nextInt().absoluteValue
+
+ every { manualSessionRepo.create(
+ eventId = eventId,
+ title = request.title,
+ description = request.description,
+ format = request.format,
+ theme = request.theme
+ ) } returns session.id
+
+ assertEquals(session.toManualSession(), manualSessionAdapter.create(request, eventId))
+
+ verify { manualSessionRepo.create(
+ eventId = eventId,
+ title = request.title,
+ description = request.description,
+ format = request.format,
+ theme = request.theme
+ ) }
+ }
+
+ @ParameterizedTest
+ @EnumSource(UpdateCases::class)
+ fun `update should call repo to retrieve, only change the fields that are non-null in the request, save the new object and return it as a ManualSession`(case: UpdateCases) {
+ val (request, result) = updateTestValuesProvider(session, case)
+ every { manualSessionRepo.save(result) } returns result
+
+ assertEquals(result.toManualSession(), manualSessionAdapter.update(session.id, request))
+
+ verify { manualSessionRepo.save(result) }
+ }
+
+ private fun updateTestValuesProvider(
+ initialSession: ManualSessionDB,
+ case: UpdateCases
+ ): Pair {
+ return when(case) {
+ UpdateCases.NoUpdate ->
+ Pair(SessionPatchReq.empty(), initialSession.copy())
+ UpdateCases.UpdateTitle -> {
+ val newTitle = generateRandomHexString(2)
+ Pair(
+ SessionPatchReq.empty().copy(title = newTitle),
+ session.copy(title = newTitle))
+ }
+ UpdateCases.UpdateDescription -> {
+ val newDescription = generateRandomHexString(6)
+ Pair(
+ SessionPatchReq.empty().copy(description = newDescription),
+ session.copy(description = newDescription))
+ }
+ UpdateCases.UpdateFormat -> {
+ val newFormat = SessionFormatEnum
+ .entries
+ .filterNot { it == session.format }
+ .random()
+ Pair(
+ SessionPatchReq.empty().copy(format = newFormat),
+ session.copy(format = newFormat))
+ }
+ UpdateCases.UpdateTheme -> {
+ val newTheme = SessionThemeEnum
+ .entries
+ .filterNot { it == session.theme }
+ .random()
+ Pair(
+ SessionPatchReq.empty().copy(theme = newTheme),
+ session.copy(theme = newTheme))
+ }
+ }
+ }
+
+
+
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = [false, true])
+ fun `getById should call repo and return the ManualSession if found, and throw otherwise`(exists: Boolean) {
+ val session = ManualSessionDBGen().generateOne()
+ every { manualSessionRepo.findById(session.id) } returns
+ if (exists) Optional.of(session)
+ else Optional.empty()
+
+ if (exists) {
+ assertEquals(session.toManualSession(), manualSessionAdapter.getById(session.id))
+ } else {
+ assertThrows { manualSessionAdapter.getById(session.id) }
+ }
+
+ verify { manualSessionRepo.findById(session.id) }
+ }
+
+ @Test
+ fun `getAllByEventId should call repo and return the result as a List of ManualSession`() {
+ val sessions = ManualSessionDBGen().generateList()
+ val eventId = Random.nextInt().absoluteValue
+ every { manualSessionRepo.getAllByEventId(eventId) } returns sessions
+
+ assertEquals(sessions.map { it.toManualSession() }, manualSessionAdapter.getAllByEventId(eventId))
+
+ verify { manualSessionRepo.getAllByEventId(eventId) }
+ }
+
+ @Test
+ fun `delete should call repo`() {
+ val id = Random.nextInt().absoluteValue
+ every { manualSessionRepo.deleteById(id) } just Runs
+
+ manualSessionAdapter.delete(id)
+
+ verify { manualSessionRepo.deleteById(id) }
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/org/breizhcamp/konter/infrastructure/db/SessionAdapterTest.kt b/src/test/kotlin/org/breizhcamp/konter/infrastructure/db/SessionAdapterTest.kt
new file mode 100644
index 0000000..c31cb53
--- /dev/null
+++ b/src/test/kotlin/org/breizhcamp/konter/infrastructure/db/SessionAdapterTest.kt
@@ -0,0 +1,225 @@
+package org.breizhcamp.konter.infrastructure.db
+
+import com.ninjasquad.springmockk.MockkBean
+import io.mockk.Runs
+import io.mockk.every
+import io.mockk.just
+import io.mockk.verify
+import org.breizhcamp.konter.domain.entities.Evaluation
+import org.breizhcamp.konter.domain.entities.SessionFilter
+import org.breizhcamp.konter.infrastructure.db.mappers.toDB
+import org.breizhcamp.konter.infrastructure.db.mappers.toSession
+import org.breizhcamp.konter.infrastructure.db.model.SessionDB
+import org.breizhcamp.konter.infrastructure.db.repos.SessionRepo
+import org.breizhcamp.konter.testUtils.SessionDBGen
+import org.breizhcamp.konter.testUtils.SessionFilterGen
+import org.breizhcamp.konter.testUtils.SessionGen
+import org.breizhcamp.konter.testUtils.generateRandomHexString
+import org.junit.jupiter.api.*
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.extension.ExtendWith
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.test.context.junit.jupiter.SpringExtension
+import java.math.BigDecimal
+import java.util.*
+import kotlin.math.absoluteValue
+import kotlin.random.Random
+
+@ExtendWith(SpringExtension::class)
+@WebMvcTest(SessionAdapter::class)
+class SessionAdapterTest {
+
+ @MockkBean
+ private lateinit var sessionRepo: SessionRepo
+
+ @Autowired
+ private lateinit var sessionAdapter: SessionAdapter
+
+ @Test
+ fun `getById should throw if the session does not exist`() {
+ val id = Random.nextInt().absoluteValue
+ every { sessionRepo.findById(id) } returns Optional.empty()
+
+ assertThrows { sessionAdapter.getById(id) }
+
+ verify { sessionRepo.findById(id) }
+ }
+
+ @Test
+ fun `getById should return the result as a session if the session exists`() {
+ val session = SessionDBGen().generateOne()
+ every { sessionRepo.findById(session.id) } returns Optional.of(session)
+
+ assertEquals(session.toSession(), sessionAdapter.getById(session.id))
+
+ verify { sessionRepo.findById(session.id) }
+ }
+
+ @Test
+ fun `import should update existing session if it was found`() {
+ val sessionDB = SessionDBGen().generateOne()
+ val session = SessionGen().generateOne()
+ every { sessionRepo.existsById(session.id) } returns true
+ every { sessionRepo.findById(session.id) } returns Optional.of(sessionDB)
+
+ val sessionAfter = session.toDB().copy(
+ slot = sessionDB.slot,
+ videoURL = sessionDB.videoURL,
+ barcode = sessionDB.barcode
+ )
+ every { sessionRepo.save(sessionAfter) } returns sessionAfter
+
+ sessionAdapter.import(session)
+
+ verify { sessionRepo.existsById(session.id) }
+ verify { sessionRepo.findById(session.id) }
+ verify { sessionRepo.save(sessionAfter) }
+ }
+
+ @Test
+ fun `import should save values if no session with the same id was found`() {
+ val session = SessionGen().generateOne()
+ val sessionDB = session.toDB()
+
+ every { sessionRepo.existsById(session.id) } returns false
+ every { sessionRepo.findById(session.id) } returns Optional.empty()
+ every { sessionRepo.save(sessionDB) } returns sessionDB
+
+ sessionAdapter.import(session)
+
+ verify { sessionRepo.existsById(session.id) }
+ verify(exactly = 0) { sessionRepo.findById(session.id) }
+ verify { sessionRepo.save(sessionDB) }
+ }
+
+ @Test
+ fun `getAllByEventId should call repo with its inputs and an empty filter, and return the result as a List of Session`() {
+ val eventId = Random.nextInt().absoluteValue
+ val sortByFormat = Random.nextBoolean()
+ val result = SessionDBGen().generateList()
+
+ every { sessionRepo.filter(eventId, SessionFilter.empty(), sortByFormat) } returns result
+
+ assertEquals(result.map { it.toSession() }, sessionAdapter.getAllByEventId(eventId, sortByFormat))
+
+ verify { sessionRepo.filter(eventId, SessionFilter.empty(), sortByFormat) }
+ }
+
+ @Test
+ fun `saveEvaluation should set the rating for the session in the evaluation and save it`() {
+ val session = SessionGen().generateOne()
+ val evaluation = Evaluation(
+ session = session,
+ rating = BigDecimal.valueOf(Random.nextDouble(0.0, 5.0))
+ )
+ val sessionDB = session.copy(rating = evaluation.rating).toDB()
+
+ every { sessionRepo.save(sessionDB) } returns sessionDB
+
+ sessionAdapter.saveEvaluation(evaluation)
+
+ verify { sessionRepo.save(sessionDB) }
+ }
+
+ @Test
+ fun `filterByEventId should call repo with its input and sortByFormat set to false, and return the result as a List of Session`() {
+ val filter = SessionFilterGen().generateOne()
+ val eventId = Random.nextInt().absoluteValue
+ val result = SessionDBGen().generateList()
+
+ every { sessionRepo.filter(eventId, filter, false) } returns result
+
+ assertEquals(result.map { it.toSession() }, sessionAdapter.filterByEventId(eventId, filter))
+
+ verify { sessionRepo.filter(eventId, filter, false) }
+ }
+
+ @Test
+ fun `addBarcode should call repo with its inputs`() {
+ val id = Random.nextInt().absoluteValue
+ val barcode = generateRandomHexString()
+ every { sessionRepo.addBarcode(id, barcode) } just Runs
+
+ sessionAdapter.addBarcode(id, barcode)
+
+ verify { sessionRepo.addBarcode(id, barcode) }
+ }
+
+ @Nested
+ inner class SetSlotTests {
+ private lateinit var session: SessionDB
+ private lateinit var slotId: UUID
+
+ @BeforeEach
+ fun setUp() {
+ session = SessionDBGen().generateOne()
+ slotId = UUID.randomUUID()
+
+ every { sessionRepo.removeSlotById(session.id) } just Runs
+ every { sessionRepo.setSlotById(session.id, slotId) } just Runs
+ every { sessionRepo.findById(session.id) } returns Optional.of(session)
+ }
+
+ @AfterEach
+ fun breakDown() {
+ verify { sessionRepo.hasSlotById(session.id) }
+ verify { sessionRepo.setSlotById(session.id, slotId) }
+ verify { sessionRepo.findById(session.id) }
+ }
+
+ @Test
+ fun `setSlotById should reset the slot before assigning one if the session already is part of a slot, and return the result as a Session`() {
+ every { sessionRepo.hasSlotById(session.id) } returns true
+
+ assertEquals(
+ session.toSession(),
+ sessionAdapter.setSlotById(session.id, slotId)
+ )
+
+ verify { sessionRepo.removeSlotById(session.id) }
+ }
+
+ @Test
+ fun `setSlotById should not reset the slot before assigning one if the session is not part of a slot, and return the result as a Session`() {
+ every { sessionRepo.hasSlotById(session.id) } returns false
+
+ assertEquals(
+ session.toSession(),
+ sessionAdapter.setSlotById(session.id, slotId)
+ )
+
+ verify(exactly = 0) { sessionRepo.removeSlotById(session.id) }
+ }
+
+ @Test
+ fun `setSlotByBarcode should reset the slot before assigning one if the session already is part of a slot, and return the result as a Session`() {
+ val barcode = generateRandomHexString()
+ every { sessionRepo.getSlotIdByBarcode(barcode) } returns slotId
+ every { sessionRepo.hasSlotById(session.id) } returns true
+
+ assertEquals(
+ session.toSession(),
+ sessionAdapter.setSlotByBarcode(session.id, barcode)
+ )
+
+ verify { sessionRepo.getSlotIdByBarcode(barcode) }
+ verify { sessionRepo.removeSlotById(session.id) }
+ }
+
+ @Test
+ fun `setSlotByBarcode should not reset the slot before assigning one if the session is not part of a slot, and return the result as a Session`() {
+ val barcode = generateRandomHexString()
+ every { sessionRepo.getSlotIdByBarcode(barcode) } returns slotId
+ every { sessionRepo.hasSlotById(session.id) } returns false
+
+ assertEquals(
+ session.toSession(),
+ sessionAdapter.setSlotByBarcode(session.id, barcode)
+ )
+
+ verify { sessionRepo.getSlotIdByBarcode(barcode) }
+ verify(exactly = 0) { sessionRepo.removeSlotById(session.id) }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/org/breizhcamp/konter/infrastructure/db/SlotAdapterTest.kt b/src/test/kotlin/org/breizhcamp/konter/infrastructure/db/SlotAdapterTest.kt
new file mode 100644
index 0000000..35a6ac2
--- /dev/null
+++ b/src/test/kotlin/org/breizhcamp/konter/infrastructure/db/SlotAdapterTest.kt
@@ -0,0 +1,437 @@
+package org.breizhcamp.konter.infrastructure.db
+
+import com.itextpdf.barcodes.BarcodeEAN
+import com.ninjasquad.springmockk.MockkBean
+import io.mockk.Runs
+import io.mockk.every
+import io.mockk.just
+import io.mockk.verify
+import org.breizhcamp.konter.application.requests.SlotCreationReq
+import org.breizhcamp.konter.application.requests.SlotPatchReq
+import org.breizhcamp.konter.domain.entities.exceptions.HallNotFoundException
+import org.breizhcamp.konter.domain.entities.exceptions.TimeConflictException
+import org.breizhcamp.konter.infrastructure.db.mappers.toHall
+import org.breizhcamp.konter.infrastructure.db.mappers.toSlot
+import org.breizhcamp.konter.infrastructure.db.model.HallDB
+import org.breizhcamp.konter.infrastructure.db.model.SlotDB
+import org.breizhcamp.konter.infrastructure.db.repos.HallRepo
+import org.breizhcamp.konter.infrastructure.db.repos.SlotRepo
+import org.breizhcamp.konter.testUtils.HallDBGen
+import org.breizhcamp.konter.testUtils.ImportSlotDBGen
+import org.breizhcamp.konter.testUtils.ManualSlotDBGen
+import org.breizhcamp.konter.testUtils.generateRandomHexString
+import org.junit.jupiter.api.*
+import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.assertDoesNotThrow
+import org.junit.jupiter.api.extension.ExtendWith
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.EnumSource
+import org.junit.jupiter.params.provider.ValueSource
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.test.context.junit.jupiter.SpringExtension
+import java.time.Duration
+import java.time.LocalTime
+import java.util.*
+import kotlin.math.absoluteValue
+import kotlin.random.Random
+
+@ExtendWith(SpringExtension::class)
+@WebMvcTest(SlotAdapter::class)
+class SlotAdapterTest {
+
+ @MockkBean
+ private lateinit var hallRepo: HallRepo
+
+ @MockkBean
+ private lateinit var slotRepo: SlotRepo
+
+ @Autowired
+ private lateinit var slotAdapter: SlotAdapter
+
+ enum class CreationCases {
+ NoOverlapAfter,
+ NoOverlapBefore,
+ OverlapBefore,
+ OverlapAfter,
+ OverlapIn,
+ OverlapOut
+ }
+
+ enum class HallCases {
+ NotFound,
+ FoundNoTrackId,
+ FoundAndTrackId
+ }
+
+ @Nested
+ inner class CreationTests {
+ private var eventId = 0
+ private lateinit var hall: HallDB
+
+ private var title: String? = null
+ private var assignable: Boolean = false
+
+ private lateinit var existingSlot: SlotDB
+ private lateinit var existingSlots:List
+
+ @BeforeEach
+ fun setUp() {
+ eventId = Random.nextInt(1, 10)
+ hall = HallDBGen().generateOne().copy(trackId = Random.nextInt(1, 10))
+
+ title = generateRandomHexString()
+ assignable = Random.nextBoolean()
+
+ existingSlot = ImportSlotDBGen().generateOne().copy(day = Random.nextInt(1, 10))
+ existingSlots = listOf(existingSlot)
+ }
+
+ @ParameterizedTest
+ @EnumSource(CreationCases::class)
+ fun `throwIfUnavailable should throw if the new slot overlaps in any way`(case: CreationCases) {
+ val slotCreationReq = case.toSlotCreationReq(existingSlot, title, listOf(hall.id), assignable)
+
+ every { slotRepo.getByHallIdAndEventId(hall.id, eventId) } returns existingSlots
+ every { hallRepo.findById(hall.id) } returns Optional.of(hall)
+
+ when(case) {
+ CreationCases.NoOverlapBefore,
+ CreationCases.NoOverlapAfter -> {
+ assertDoesNotThrow {
+ slotAdapter.throwIfOverlapped(hall.id, eventId, slotCreationReq)
+ }
+ verify(exactly = 0) { hallRepo.findById(hall.id) }
+ }
+ CreationCases.OverlapBefore,
+ CreationCases.OverlapAfter,
+ CreationCases.OverlapIn,
+ CreationCases.OverlapOut -> {
+ assertThrows {
+ slotAdapter.throwIfOverlapped(hall.id, eventId, slotCreationReq)
+ }
+ verify { hallRepo.findById(hall.id) }
+ }
+ }
+
+ verify { slotRepo.getByHallIdAndEventId(hall.id, eventId) }
+ }
+
+ @ParameterizedTest
+ @EnumSource(HallCases::class)
+ fun `create should call slotRepo, hallRepo, create a slot if hall is found and has a trackId and return the result, or throw an Exception otherwise`(
+ case: HallCases
+ ) {
+ val slotCreationReq = CreationCases.NoOverlapBefore.toSlotCreationReq(existingSlot, title, listOf(hall.id), assignable)
+
+ every { slotRepo.getByHallIdAndEventId(hall.id, eventId) } returns existingSlots
+
+ every { hallRepo.getAllByAvailableEventId(eventId) } returns when(case) {
+ HallCases.NotFound -> { emptyList() }
+ HallCases.FoundNoTrackId -> {
+ hall = hall.copy(trackId = null)
+ listOf(hall)
+ }
+ HallCases.FoundAndTrackId -> {
+ listOf(hall)
+ }
+ }
+
+ val barcode = getBarcode(slotCreationReq, hall.trackId ?: 0, eventId)
+ every { slotRepo.create(
+ hallId = hall.id,
+ eventId = eventId,
+ day = slotCreationReq.day,
+ start = slotCreationReq.start,
+ duration = slotCreationReq.duration.seconds,
+ barcode = barcode,
+ title = slotCreationReq.title,
+ assignable = slotCreationReq.assignable
+ ) } just Runs
+ val returnedSlot = ImportSlotDBGen()
+ .generateOne()
+ .copy(
+ day = slotCreationReq.day,
+ start = slotCreationReq.start,
+ duration = slotCreationReq.duration,
+ barcode = barcode
+ )
+ every { slotRepo.getByBarcodeAndEventId(barcode, eventId) } returns returnedSlot
+
+ when(case) {
+ HallCases.FoundAndTrackId -> {
+ assertEquals(returnedSlot.toSlot(), slotAdapter.create(eventId, slotCreationReq))
+ verify { slotRepo.create(
+ hall.id,
+ eventId,
+ slotCreationReq.day,
+ slotCreationReq.start,
+ slotCreationReq.duration.seconds,
+ barcode,
+ slotCreationReq.title,
+ slotCreationReq.assignable
+ ) }
+ verify { slotRepo.getByBarcodeAndEventId(barcode, eventId) }
+ }
+ else -> {
+ assertThrows { slotAdapter.create(eventId, slotCreationReq) }
+ verify(exactly = 0) { slotRepo.create(
+ hall.id,
+ eventId,
+ slotCreationReq.day,
+ slotCreationReq.start,
+ slotCreationReq.duration.seconds,
+ barcode,
+ slotCreationReq.title,
+ slotCreationReq.assignable
+ ) }
+ verify(exactly = 0) { slotRepo.getByBarcodeAndEventId(barcode, eventId) }
+ }
+ }
+ verify { slotRepo.getByHallIdAndEventId(hall.id, eventId) }
+ verify { hallRepo.getAllByAvailableEventId(eventId) }
+ }
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = [false, true])
+ fun `getById should call repo and return the result as a Slot if it exists, or throw an Exception otherwise`(
+ exists: Boolean
+ ) {
+ val slotDB = ImportSlotDBGen().generateOne()
+ every { slotRepo.findById(slotDB.id) } returns
+ if (exists) Optional.of(slotDB)
+ else Optional.empty()
+
+ if (exists) {
+ assertEquals(slotDB.toSlot(), slotAdapter.getById(slotDB.id))
+ } else {
+ assertThrows { slotAdapter.getById(slotDB.id) }
+ }
+
+ verify { slotRepo.findById(slotDB.id) }
+ }
+
+ @Test
+ fun `getProgram should call repos and generate the expected data structure to be used when generating the PDF`() {
+ val eventId = Random.nextInt(1, 10)
+ val (slots, halls) = slotsAndHallsForProgram()
+
+ every { slotRepo.getAllByEventId(eventId) } returns slots
+ every { hallRepo.getAllByAvailableEventId(eventId) } returns halls
+
+ val result = slotAdapter.getProgram(eventId)
+ assertEquals(2, result.size)
+
+ val tracks = requireNotNull(result[result.keys.first()])
+ { "Precedent assert should have assured that there is at one entry in the map" }
+ assertEquals(halls.size, tracks.size)
+ assertEquals(halls.map { it.toHall() }, tracks.keys.toList())
+ assert(tracks.size == 2)
+
+ val firstTrack = requireNotNull(tracks[tracks.keys.first()])
+ { "Should contain two tracks, so one is expected" }
+ val secondTrack = requireNotNull(tracks[tracks.keys.last()])
+ { "Should contain two tracks, so a second one is expected" }
+ assertNotEquals(firstTrack, secondTrack, "The two tracks should be different")
+
+ assertNotNull(firstTrack.find { it.span > 1 }, "The plenum slot is stored only in the first track and has a span of 2")
+ assertNull(secondTrack.find { it.span > 1 }, "All slots in the second track have a span of 1")
+
+ verify { slotRepo.getAllByEventId(eventId) }
+ verify { hallRepo.getAllByAvailableEventId(eventId) }
+ }
+
+ @Test
+ fun `remove should call Repo`() {
+ val id = UUID.randomUUID()
+ every { slotRepo.deleteById(id) } just Runs
+ slotAdapter.remove(id)
+ verify { slotRepo.deleteById(id) }
+ }
+
+ @Nested
+ inner class AssociationTests {
+ private lateinit var slot: SlotDB
+ private var hallId: Int = 0
+
+ @BeforeEach
+ fun setUp() {
+ slot = ImportSlotDBGen().generateOne()
+ hallId = Random.nextInt().absoluteValue
+ every { slotRepo.findById(slot.id) } returns Optional.of(slot)
+ }
+
+ @Test
+ fun `associateHall should call repo and return the slot`() {
+ val eventId = Random.nextInt().absoluteValue
+ every { slotRepo.associateToHallAndEvent(slot.id, hallId, eventId) } just Runs
+ every { slotRepo.getByHallIdAndEventId(hallId, eventId) } returns emptyList()
+
+ slotAdapter.associateHall(slot.id, eventId, hallId)
+
+ verify { slotRepo.associateToHallAndEvent(slot.id, hallId, eventId) }
+ }
+
+ @Test
+ fun `dissociateHall should call repo and return the slot`() {
+ every { slotRepo.dissociateFromHall(slot.id, hallId) } just Runs
+
+ slotAdapter.dissociateHall(slot.id, hallId)
+
+ verify { slotRepo.dissociateFromHall(slot.id, hallId) }
+ }
+
+ @Test
+ fun `dissociateHall should delete the slot if the halls are empty after call`() {
+ every { slotRepo.dissociateFromHall(slot.id, hallId) } just Runs
+ val newSlot = slot.copy(halls = emptySet())
+ every { slotRepo.findById(slot.id) } returns Optional.of(newSlot)
+ every { slotRepo.deleteById(slot.id) } just Runs
+
+ slotAdapter.dissociateHall(slot.id, hallId)
+
+ verify { slotRepo.dissociateFromHall(slot.id, hallId) }
+ verify { slotRepo.deleteById(slot.id) }
+ }
+
+ @AfterEach
+ fun breakDown() {
+ verify { slotRepo.findById(slot.id) }
+ }
+ }
+
+ private fun CreationCases.toSlotCreationReq(slot: SlotDB, title: String?, hallIds: List, assignable: Boolean): SlotCreationReq =
+ when (this) {
+ CreationCases.NoOverlapBefore -> {
+ val start = slot.start - Duration.ofMinutes(Random.nextLong(10, 20))
+ val duration = Duration.ofMinutes(Random.nextLong(0, 10))
+
+ SlotCreationReq(start, slot.day, duration, title, hallIds, assignable)
+ }
+ CreationCases.NoOverlapAfter -> {
+ val start = slot.start + slot.duration + Duration.ofMinutes(Random.nextLong(10, 20))
+ val duration = Duration.ofMinutes(Random.nextLong(10, 20))
+
+ SlotCreationReq(start, slot.day, duration, title, hallIds, assignable)
+ }
+ CreationCases.OverlapBefore -> {
+ val timeBeforeOther = Duration.ofMinutes(Random.nextLong(10, 20))
+ val start = slot.start - timeBeforeOther
+ val duration = timeBeforeOther + slot.duration.dividedBy(2)
+
+ SlotCreationReq(start, slot.day, duration, title, hallIds, assignable)
+ }
+ CreationCases.OverlapAfter -> {
+ val start = slot.start + slot.duration.dividedBy(2)
+ val duration = slot.duration.dividedBy(2) + Duration.ofMinutes(Random.nextLong(10, 20))
+
+ SlotCreationReq(start, slot.day, duration, title, hallIds, assignable)
+ }
+ CreationCases.OverlapIn -> {
+ val start = slot.start + slot.duration.dividedBy(4)
+ val duration = slot.duration.dividedBy(2)
+
+ SlotCreationReq(start, slot.day, duration, title, hallIds, assignable)
+ }
+ CreationCases.OverlapOut -> {
+ val timeBeforeOther = Duration.ofMinutes(Random.nextLong(10, 20))
+ val start = slot.start - timeBeforeOther
+ val duration = slot.duration.plus(timeBeforeOther.multipliedBy(2))
+
+ SlotCreationReq(start, slot.day, duration, title, hallIds, assignable)
+ }
+ }
+
+ private fun getBarcode(req: SlotCreationReq, trackId: Int, eventId: Int): String {
+ var barcode = "${req.day}$eventId$trackId" +
+ req.start.hour.toString().padStart(2, '0') +
+ req.start.minute.toString().padStart(2, '0')
+ barcode += "0".repeat(12 - barcode.length)
+ barcode += BarcodeEAN.calculateEANParity(barcode)
+
+ return barcode
+ }
+
+ private fun slotsAndHallsForProgram(): Pair, List> {
+ val day = Random.nextInt(1, 10)
+ val hallGen = HallDBGen()
+ val firstHall = hallGen.generateOne()
+ val secondHall = hallGen.generateOne()
+ val halls = listOf(firstHall, secondHall)
+ val slotGen = ImportSlotDBGen()
+ val plenumSlotStart = LocalTime.of(9, 30)
+ val plenumSlotDuration = Duration.ofHours(1)
+
+ val plenumSlot = ManualSlotDBGen()
+ .generateOne()
+ .copy(
+ halls = halls.toSet(),
+ day = day,
+ start = plenumSlotStart,
+ duration = plenumSlotDuration
+ )
+ val twoHourSlot = slotGen
+ .generateOne()
+ .copy(
+ day = day,
+ halls = setOf(firstHall),
+ start = plenumSlotStart + plenumSlotDuration,
+ duration = Duration.ofHours(2)
+ )
+ val firstOneHourSlot = slotGen
+ .generateOne()
+ .copy(
+ day = day,
+ halls = setOf(secondHall),
+ start = plenumSlotStart + plenumSlotDuration,
+ duration = Duration.ofHours(1)
+ )
+ val secondOneHourSlot = slotGen
+ .generateOne()
+ .copy(
+ day = day,
+ halls = setOf(secondHall),
+ start = plenumSlotStart + plenumSlotDuration + Duration.ofHours(1),
+ duration = Duration.ofHours(1)
+ )
+ val followingDayPlenumSlot = ManualSlotDBGen()
+ .generateOne()
+ .copy(
+ day = day + 1,
+ halls = halls.toSet(),
+ start = plenumSlotStart,
+ duration = plenumSlotDuration
+ )
+
+ val allSlots = listOf(
+ plenumSlot,
+ twoHourSlot,
+ firstOneHourSlot,
+ secondOneHourSlot,
+ followingDayPlenumSlot
+ )
+
+ return Pair(allSlots, halls)
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = [false, true])
+ fun update(nonNullTitle: Boolean) {
+ val oldSlot = ImportSlotDBGen().generateOne()
+ val newTitle = if (nonNullTitle) generateRandomHexString() else null
+ val assignable = Random.nextBoolean()
+
+ val req = SlotPatchReq(title = newTitle, assignable = assignable)
+ val newSlot = if (nonNullTitle) oldSlot.copy(title = newTitle, assignable = assignable)
+ else oldSlot.copy(assignable = assignable)
+
+ every { slotRepo.findById(oldSlot.id) } returns Optional.of(oldSlot)
+ every { slotRepo.save(newSlot) } returns newSlot
+
+ assertEquals(newSlot.toSlot(), slotAdapter.update(oldSlot.id, req))
+
+ verify { slotRepo.findById(oldSlot.id) }
+ verify { slotRepo.save(newSlot) }
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/org/breizhcamp/konter/infrastructure/db/SpeakerAdapterTest.kt b/src/test/kotlin/org/breizhcamp/konter/infrastructure/db/SpeakerAdapterTest.kt
new file mode 100644
index 0000000..73b7179
--- /dev/null
+++ b/src/test/kotlin/org/breizhcamp/konter/infrastructure/db/SpeakerAdapterTest.kt
@@ -0,0 +1,86 @@
+package org.breizhcamp.konter.infrastructure.db
+
+import com.ninjasquad.springmockk.MockkBean
+import io.mockk.every
+import io.mockk.verify
+import org.breizhcamp.konter.infrastructure.db.mappers.toSpeaker
+import org.breizhcamp.konter.infrastructure.db.model.SpeakerDB
+import org.breizhcamp.konter.infrastructure.db.repos.SpeakerRepo
+import org.breizhcamp.konter.testUtils.SpeakerDBGen
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Nested
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+import org.junit.jupiter.api.extension.ExtendWith
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.ValueSource
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.test.context.junit.jupiter.SpringExtension
+import java.util.*
+
+@ExtendWith(SpringExtension::class)
+@WebMvcTest(SpeakerAdapter::class)
+class SpeakerAdapterTest {
+
+ @MockkBean
+ private lateinit var speakerRepo: SpeakerRepo
+
+ @Autowired
+ private lateinit var speakerAdapter: SpeakerAdapter
+
+ @Test
+ fun `list should call repo and return the result as a List of Speaker`() {
+ val speakers = SpeakerDBGen().generateList()
+ every { speakerRepo.findAll() } returns speakers
+
+ assertEquals(speakers.map { it.toSpeaker() }, speakerAdapter.list())
+
+ verify { speakerRepo.findAll() }
+ }
+
+ @Nested
+ inner class CRTests {
+ private lateinit var speaker: SpeakerDB
+
+ @BeforeEach
+ fun setUp() {
+ speaker = SpeakerDBGen().generateOne()
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = [false, true])
+ fun `get should call repo, return the result as a Speaker if it was found and throw if not`(exists: Boolean) {
+ every { speakerRepo.findById(speaker.id) } returns
+ if (exists) Optional.of(speaker) else Optional.empty()
+
+ if (exists) {
+ assertEquals(speaker.toSpeaker(), speakerAdapter.get(speaker.id))
+ } else {
+ assertThrows { speakerAdapter.get(speaker.id) }
+ }
+
+ verify { speakerRepo.findById(speaker.id) }
+ }
+
+ @Test
+ fun `getByNameAndEmail should call repo and return the result as a Speaker`() {
+ val name = "${speaker.lastname} ${speaker.firstname}"
+ every { speakerRepo.findByNameAndEmail(name, speaker.email) } returns speaker
+
+ assertEquals(speaker.toSpeaker(), speakerAdapter.getByNameAndEmail(name, speaker.email))
+
+ verify { speakerRepo.findByNameAndEmail(name, speaker.email) }
+ }
+
+ @Test
+ fun `save should call repo`() {
+ every { speakerRepo.save(speaker) } returns speaker
+
+ speakerAdapter.save(speaker.toSpeaker())
+
+ verify { speakerRepo.save(speaker) }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/org/breizhcamp/konter/infrastructure/db/mappers/EventMapperTest.kt b/src/test/kotlin/org/breizhcamp/konter/infrastructure/db/mappers/EventMapperTest.kt
new file mode 100644
index 0000000..e1ea0dc
--- /dev/null
+++ b/src/test/kotlin/org/breizhcamp/konter/infrastructure/db/mappers/EventMapperTest.kt
@@ -0,0 +1,32 @@
+package org.breizhcamp.konter.infrastructure.db.mappers
+
+import org.breizhcamp.konter.infrastructure.db.model.HallDB
+import org.breizhcamp.konter.testUtils.EventDBGen
+import org.breizhcamp.konter.testUtils.EventGen
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+
+class EventMapperTest {
+
+ @Test
+ fun `toEvent should convert all fields one to one except the halls`() {
+ val eventDB = EventDBGen().generateOne()
+ val event = eventDB.toEvent()
+
+ assertEquals(eventDB.id, event.id)
+ assertEquals(eventDB.year, event.year)
+ assertEquals(eventDB.name, event.name)
+ }
+
+ @Test
+ fun `toDB should convert all fields one to one and set the halls to an empty Set`() {
+ val event = EventGen().generateOne()
+ val eventDB = event.toDB()
+ val emptyHallSet = emptySet()
+
+ assertEquals(event.id, eventDB.id)
+ assertEquals(event.year, eventDB.year)
+ assertEquals(event.name, eventDB.name)
+ assertEquals(emptyHallSet, eventDB.halls)
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/org/breizhcamp/konter/infrastructure/db/mappers/HallMapperTest.kt b/src/test/kotlin/org/breizhcamp/konter/infrastructure/db/mappers/HallMapperTest.kt
new file mode 100644
index 0000000..5ef5de4
--- /dev/null
+++ b/src/test/kotlin/org/breizhcamp/konter/infrastructure/db/mappers/HallMapperTest.kt
@@ -0,0 +1,29 @@
+package org.breizhcamp.konter.infrastructure.db.mappers
+
+import org.breizhcamp.konter.testUtils.HallDBGen
+import org.breizhcamp.konter.testUtils.HallGen
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+
+class HallMapperTest {
+
+ @Test
+ fun `toHall should convert all fields one to one`() {
+ val hallDB = HallDBGen().generateOne()
+ val hall = hallDB.toHall()
+
+ assertEquals(hallDB.id, hall.id)
+ assertEquals(hallDB.name, hall.name)
+ assertEquals(hallDB.trackId, hall.trackId)
+ }
+
+ @Test
+ fun `toDB should convert all fields one to one`() {
+ val hall = HallGen().generateOne()
+ val hallDB = hall.toDB()
+
+ assertEquals(hall.id, hallDB.id)
+ assertEquals(hall.name, hallDB.name)
+ assertEquals(hall.trackId, hallDB.trackId)
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/org/breizhcamp/konter/infrastructure/db/mappers/SessionMapperTest.kt b/src/test/kotlin/org/breizhcamp/konter/infrastructure/db/mappers/SessionMapperTest.kt
new file mode 100644
index 0000000..1f90786
--- /dev/null
+++ b/src/test/kotlin/org/breizhcamp/konter/infrastructure/db/mappers/SessionMapperTest.kt
@@ -0,0 +1,150 @@
+package org.breizhcamp.konter.infrastructure.db.mappers
+
+import org.breizhcamp.konter.domain.entities.Speaker
+import org.breizhcamp.konter.infrastructure.db.model.SpeakerDB
+import org.breizhcamp.konter.testUtils.*
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.ValueSource
+
+class SessionMapperTest {
+
+ @ParameterizedTest
+ @ValueSource(booleans = [false, true])
+ fun `toSession should map all fields one to one, except owner, speakers, event that are fully mapped and slot that is mapped to a limited version`(
+ hasNotNullSlot: Boolean
+ ) {
+ var sessionDB = SessionDBGen().generateOne()
+ if (hasNotNullSlot) {
+ sessionDB = sessionDB.copy(slot = ImportSlotDBGen().generateOne())
+ }
+ val session = sessionDB.toSession()
+
+ assertEquals(sessionDB.id, session.id)
+ assertEquals(sessionDB.title, session.title)
+ assertEquals(sessionDB.description, session.description)
+
+ // The SpeakerDB.toSpeaker() -> Speaker mapping is tested in the SpeakerMapperTest.kt file
+ assertEquals(sessionDB.owner.toSpeaker(), session.owner)
+ assertEquals(sessionDB.speakers.map(SpeakerDB::toSpeaker),
+ session.speakers)
+
+ assertEquals(sessionDB.format, session.format)
+ assertEquals(sessionDB.theme, session.theme)
+ assertEquals(sessionDB.niveau, session.niveau)
+ assertEquals(sessionDB.status, session.status)
+ assertEquals(sessionDB.submitted, session.submitted)
+ assertEquals(sessionDB.ownerNotes, session.ownerNotes)
+ assertEquals(sessionDB.videoURL, session.videoURL)
+
+ // The EventDB.toEvent() -> Event mapping is tested in the EventMapperTest.kt file
+ assertEquals(sessionDB.event.toEvent(), session.event)
+
+ // The SlotDB.toLimitedSlot() -> Slot (limited) mapping is tested in the SlotMapperTest.kt file
+ assertEquals(sessionDB.slot?.toLimitedSlot(), session.slot)
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = [false, true])
+ fun `toLimitedSession should map all fields one to one, except owner, speakers, event that are fully mapped and slot that is set to null`(
+ hasNotNullSlot: Boolean
+ ) {
+ var sessionDB = SessionDBGen().generateOne()
+ if (hasNotNullSlot) {
+ sessionDB = sessionDB.copy(slot = ImportSlotDBGen().generateOne())
+ }
+ val session = sessionDB.toLimitedSession()
+
+ assertEquals(sessionDB.id, session.id)
+ assertEquals(sessionDB.title, session.title)
+ assertEquals(sessionDB.description, session.description)
+
+ // The SpeakerDB.toSpeaker() -> Speaker mapping is tested in the SpeakerMapperTest.kt file
+ assertEquals(sessionDB.owner.toSpeaker(), session.owner)
+ assertEquals(sessionDB.speakers.map(SpeakerDB::toSpeaker),
+ session.speakers)
+
+ assertEquals(sessionDB.format, session.format)
+ assertEquals(sessionDB.theme, session.theme)
+ assertEquals(sessionDB.niveau, session.niveau)
+ assertEquals(sessionDB.status, session.status)
+ assertEquals(sessionDB.submitted, session.submitted)
+ assertEquals(sessionDB.ownerNotes, session.ownerNotes)
+ assertEquals(sessionDB.videoURL, session.videoURL)
+
+ // The EventDB.toEvent() -> Event mapping is tested in the EventMapperTest.kt file
+ assertEquals(sessionDB.event.toEvent(), session.event)
+
+ // In the limited mapping, the slot is always set to null in order to avoid circular dependant calls
+ assertEquals(null, session.slot)
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = [false, true])
+ fun `Session toDB should map all fields one to one, except owner, speakers, event and slot`(hasNotNullSlot: Boolean) {
+ var session = SessionGen().generateOne()
+ if (hasNotNullSlot) {
+ val emptySlot = SlotGen().generateOne().copy(session = null)
+ session = session.copy(slot = emptySlot)
+ }
+ val sessionDB = session.toDB()
+
+ assertEquals(session.id, sessionDB.id)
+ assertEquals(session.title, sessionDB.title)
+ assertEquals(session.description, sessionDB.description)
+
+ // The Speaker.toDB -> SpeakerDB mapping is tested in the SpeakerMapperTest.kt file
+ assertEquals(session.owner.toDB(), sessionDB.owner)
+ assertEquals(session.speakers.map(Speaker::toDB).toSet(),
+ sessionDB.speakers)
+
+ assertEquals(session.format, sessionDB.format)
+ assertEquals(session.theme, sessionDB.theme)
+ assertEquals(session.niveau, sessionDB.niveau)
+ assertEquals(session.status, sessionDB.status)
+ assertEquals(session.submitted, sessionDB.submitted)
+ assertEquals(session.ownerNotes, sessionDB.ownerNotes)
+ assertEquals(session.videoURL, sessionDB.videoURL)
+
+ // The Event.toDB() -> EventDB mapping is tested in the EventMapperTest.kt file
+ assertEquals(session.event.toDB(), sessionDB.event)
+
+ // The Slot.toDB() -> SlotDB mapping is tested in the SlotMapperTest.kt file
+ // We don't use a limited mapping, as object sent to the API are finite
+ // and should then not have infinite recursive objects
+ assertEquals(session.slot?.toDB(), sessionDB.slot)
+ }
+
+ @Test
+ fun `toManualSession should map all fields one to one except event that is fully mapped`() {
+ val manualSessionDB = ManualSessionDBGen().generateOne()
+ val manualSession = manualSessionDB.toManualSession()
+
+ assertEquals(manualSessionDB.id, manualSession.id)
+ assertEquals(manualSessionDB.title, manualSession.title)
+ assertEquals(manualSessionDB.description, manualSession.description)
+
+ // The EventDB.toEvent() -> Event mapping is tested in the EventMapperTest.kt file
+ assertEquals(manualSessionDB.event.toEvent(), manualSession.event)
+
+ assertEquals(manualSessionDB.format, manualSession.format)
+ assertEquals(manualSessionDB.theme, manualSession.theme)
+ }
+
+ @Test
+ fun `ManualSession toDB should map all fields one to one except event that is fully mapped`() {
+ val manualSession = ManualSessionGen().generateOne()
+ val manualSessionDB = manualSession.toDB()
+
+ assertEquals(manualSession.id, manualSessionDB.id)
+ assertEquals(manualSession.title, manualSessionDB.title)
+ assertEquals(manualSession.description, manualSessionDB.description)
+
+ // The Event.toDB() -> EventDB mapping is tested in the EventMapperTest.kt file
+ assertEquals(manualSession.event.toDB(), manualSessionDB.event)
+
+ assertEquals(manualSession.format, manualSessionDB.format)
+ assertEquals(manualSession.theme, manualSessionDB.theme)
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/org/breizhcamp/konter/infrastructure/db/mappers/SlotMapperTest.kt b/src/test/kotlin/org/breizhcamp/konter/infrastructure/db/mappers/SlotMapperTest.kt
new file mode 100644
index 0000000..86c08e3
--- /dev/null
+++ b/src/test/kotlin/org/breizhcamp/konter/infrastructure/db/mappers/SlotMapperTest.kt
@@ -0,0 +1,177 @@
+package org.breizhcamp.konter.infrastructure.db.mappers
+
+import org.breizhcamp.konter.domain.entities.Hall
+import org.breizhcamp.konter.infrastructure.db.model.HallDB
+import org.breizhcamp.konter.testUtils.*
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.EnumSource
+
+class SlotMapperTest {
+
+ enum class SlotContentCases {
+ NoSessionNorManualSession,
+ NoSessionButManualSession,
+ SessionButNoManualSession,
+ SessionAndManualSession
+ }
+
+ @ParameterizedTest
+ @EnumSource(SlotContentCases::class)
+ fun toSlot(case: SlotContentCases) {
+ var slotDB = ImportSlotDBGen().generateOne()
+
+ slotDB = when(case) {
+ SlotContentCases.SessionAndManualSession,
+ SlotContentCases.SessionButNoManualSession -> {
+ val sessionDB = SessionDBGen().generateOne().copy(slot = null)
+ slotDB.copy(session = sessionDB)
+ }
+ SlotContentCases.NoSessionNorManualSession,
+ SlotContentCases.NoSessionButManualSession -> {
+ slotDB.copy(session = null)
+ }
+ }
+
+ slotDB = when(case) {
+ SlotContentCases.SessionAndManualSession,
+ SlotContentCases.NoSessionButManualSession -> {
+ val manualSessionDB = ManualSessionDBGen().generateOne()
+ slotDB.copy(manualSession = manualSessionDB)
+ }
+ SlotContentCases.SessionButNoManualSession,
+ SlotContentCases.NoSessionNorManualSession -> {
+ slotDB.copy(manualSession = null)
+ }
+ }
+
+ val slot = slotDB.toSlot()
+
+ assertEquals(slotDB.id, slot.id)
+ assertEquals(slotDB.day, slot.day)
+ assertEquals(slotDB.start, slot.start)
+ assertEquals(slotDB.duration, slot.duration)
+
+ // The HallDB.toHall() -> Hall mapping is tested in the HallMapperTest.kt file
+ assertEquals(slotDB.halls.map(HallDB::toHall), slot.halls)
+
+ // The SessionDB.toLimitedSession() -> Session limited mapping
+ // and the ManualSessionDB.toManualSession() -> ManualSession mapping
+ // are tested in the SessionMapper.kt file
+ assertEquals(slotDB.session?.toLimitedSession(), slot.session)
+ assertEquals(slotDB.manualSession?.toManualSession(), slot.manualSession)
+
+ // The EventDB.toEvent() -> Event mapping is tested in the EventMapperTest.kt file
+ assertEquals(slotDB.event.toEvent(), slot.event)
+
+ assertEquals(slotDB.barcode, slot.barcode)
+ assertEquals(1, slot.span)
+ assertEquals(slotDB.title, slot.title)
+ }
+
+ @ParameterizedTest
+ @EnumSource(SlotContentCases::class)
+ fun toLimitedSlot(case: SlotContentCases) {
+ var slotDB = ImportSlotDBGen().generateOne()
+
+ slotDB = when(case) {
+ SlotContentCases.SessionAndManualSession,
+ SlotContentCases.SessionButNoManualSession -> {
+ val sessionDB = SessionDBGen().generateOne().copy(slot = null)
+ slotDB.copy(session = sessionDB)
+ }
+ SlotContentCases.NoSessionNorManualSession,
+ SlotContentCases.NoSessionButManualSession -> {
+ slotDB.copy(session = null)
+ }
+ }
+
+ slotDB = when(case) {
+ SlotContentCases.SessionAndManualSession,
+ SlotContentCases.NoSessionButManualSession -> {
+ val manualSessionDB = ManualSessionDBGen().generateOne()
+ slotDB.copy(manualSession = manualSessionDB)
+ }
+ SlotContentCases.SessionButNoManualSession,
+ SlotContentCases.NoSessionNorManualSession -> {
+ slotDB.copy(manualSession = null)
+ }
+ }
+
+ val slot = slotDB.toLimitedSlot()
+
+ assertEquals(slotDB.id, slot.id)
+ assertEquals(slotDB.day, slot.day)
+ assertEquals(slotDB.start, slot.start)
+ assertEquals(slotDB.duration, slot.duration)
+
+ // The HallDB.toHall() -> Hall mapping is tested in the HallMapperTest.kt file
+ assertEquals(slotDB.halls.map(HallDB::toHall), slot.halls)
+
+ // In the limited mapping, the session and manualSession
+ // are always set to null in order to avoid circular dependant calls
+ assertEquals(null, slot.session)
+ assertEquals(null, slot.manualSession)
+
+ // The EventDB.toEvent() -> Event mapping is tested in the EventMapperTest.kt file
+ assertEquals(slotDB.event.toEvent(), slot.event)
+
+ assertEquals(slotDB.barcode, slot.barcode)
+ assertEquals(1, slot.span)
+ assertEquals(slotDB.title, slot.title)
+ }
+
+ @ParameterizedTest
+ @EnumSource(SlotContentCases::class)
+ fun toDB(case: SlotContentCases) {
+ var slot = SlotGen().generateOne()
+
+ slot = when(case) {
+ SlotContentCases.SessionAndManualSession,
+ SlotContentCases.SessionButNoManualSession -> {
+ val session = SessionGen().generateOne().copy(slot = null)
+ slot.copy(session = session)
+ }
+ SlotContentCases.NoSessionNorManualSession,
+ SlotContentCases.NoSessionButManualSession -> {
+ slot.copy(session = null)
+ }
+ }
+
+ slot = when(case) {
+ SlotContentCases.SessionAndManualSession,
+ SlotContentCases.NoSessionButManualSession -> {
+ val manualSession = ManualSessionGen().generateOne()
+ slot.copy(manualSession = manualSession)
+ }
+ SlotContentCases.SessionButNoManualSession,
+ SlotContentCases.NoSessionNorManualSession -> {
+ slot.copy(manualSession = null)
+ }
+ }
+
+ val slotDB = slot.toDB()
+
+ assertEquals(slot.id, slotDB.id)
+ assertEquals(slot.day, slotDB.day)
+ assertEquals(slot.start, slotDB.start)
+ assertEquals(slot.duration, slotDB.duration)
+
+ // The Hall.toDB() -> HallDB mapping is tested in the HallMapperTest.kt file
+ assertEquals(slot.halls.map(Hall::toDB).toSet(), slotDB.halls)
+
+ // The Session.toDB() -> SessionDB mapping
+ // and the ManualSession.toDB() -> ManualSessionDB mapping
+ // are tested in the SessionMapper.kt file
+ // We don't use a limited mapping, as object sent to the API are finite
+ // and should then not have infinite recursive objects
+ assertEquals(slot.session?.toDB(), slotDB.session)
+ assertEquals(slot.manualSession?.toDB(), slotDB.manualSession)
+
+ // The Event.toDB() -> EventDB mapping is tested in the EventMapperTest.kt file
+ assertEquals(slot.event.toDB(), slotDB.event)
+
+ assertEquals(slot.barcode, slotDB.barcode)
+ assertEquals(slot.title, slotDB.title)
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/org/breizhcamp/konter/infrastructure/db/mappers/SpeakerMapperTest.kt b/src/test/kotlin/org/breizhcamp/konter/infrastructure/db/mappers/SpeakerMapperTest.kt
new file mode 100644
index 0000000..1364baf
--- /dev/null
+++ b/src/test/kotlin/org/breizhcamp/konter/infrastructure/db/mappers/SpeakerMapperTest.kt
@@ -0,0 +1,37 @@
+package org.breizhcamp.konter.infrastructure.db.mappers
+
+import org.breizhcamp.konter.testUtils.SpeakerDBGen
+import org.breizhcamp.konter.testUtils.SpeakerGen
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+
+class SpeakerMapperTest {
+
+ @Test
+ fun `toSpeaker should convert all fields one to one`() {
+ val speakerDB = SpeakerDBGen().generateOne()
+ val speaker = speakerDB.toSpeaker()
+
+ assertEquals(speakerDB.id, speaker.id)
+ assertEquals(speakerDB.lastname, speaker.lastname)
+ assertEquals(speakerDB.firstname, speaker.firstname)
+ assertEquals(speakerDB.email, speaker.email)
+ assertEquals(speakerDB.tagLine, speaker.tagLine)
+ assertEquals(speakerDB.bio, speaker.bio)
+ assertEquals(speakerDB.profilePicture, speaker.profilePicture)
+ }
+
+ @Test
+ fun toDB() {
+ val speaker = SpeakerGen().generateOne()
+ val speakerDB = speaker.toDB()
+
+ assertEquals(speaker.id, speakerDB.id)
+ assertEquals(speaker.lastname, speakerDB.lastname)
+ assertEquals(speaker.firstname, speakerDB.firstname)
+ assertEquals(speaker.email, speakerDB.email)
+ assertEquals(speaker.tagLine, speakerDB.tagLine)
+ assertEquals(speaker.bio, speakerDB.bio)
+ assertEquals(speaker.profilePicture, speakerDB.profilePicture)
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/org/breizhcamp/konter/infrastructure/kalon/KalonAdapterTest.kt b/src/test/kotlin/org/breizhcamp/konter/infrastructure/kalon/KalonAdapterTest.kt
new file mode 100644
index 0000000..edfa1ba
--- /dev/null
+++ b/src/test/kotlin/org/breizhcamp/konter/infrastructure/kalon/KalonAdapterTest.kt
@@ -0,0 +1,110 @@
+package org.breizhcamp.konter.infrastructure.kalon
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import org.apache.http.HttpHeaders
+import org.breizhcamp.konter.domain.entities.Event
+import org.breizhcamp.konter.testUtils.EventGen
+import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.mockserver.client.MockServerClient
+import org.mockserver.configuration.Configuration
+import org.mockserver.integration.ClientAndServer
+import org.mockserver.model.Header
+import org.mockserver.model.HttpRequest.request
+import org.mockserver.model.HttpResponse.response
+import org.mockserver.model.HttpStatusCode
+import org.mockserver.model.MediaType
+import org.mockserver.model.RequestDefinition
+import org.slf4j.event.Level
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.boot.test.system.CapturedOutput
+import org.springframework.boot.test.system.OutputCaptureExtension
+import org.springframework.test.context.junit.jupiter.SpringExtension
+
+@ExtendWith(SpringExtension::class)
+@ExtendWith(OutputCaptureExtension::class)
+@WebMvcTest(KalonAdapter::class)
+class KalonAdapterTest {
+
+ @Autowired
+ private lateinit var kalonAdapter: KalonAdapter
+
+ private lateinit var server: MockServerClient
+
+ private lateinit var getIdsRequestDefinition: RequestDefinition
+
+ private lateinit var getEventRequestDefinitions: List
+
+ private lateinit var events: List
+
+ @BeforeEach
+ fun setUp() {
+ events = EventGen().generateList()
+ val mockServerConfig = Configuration()
+ mockServerConfig.logLevel(Level.WARN)
+ server = ClientAndServer.startClientAndServer(mockServerConfig, 9999)
+ setUpGetEventsIds()
+ setUpGetEventById()
+ }
+
+ @AfterEach
+ fun breakDown() {
+ server.close()
+ }
+
+ private fun setUpGetEventsIds() {
+ val requestDefinition = request()
+ .withMethod("GET")
+ .withPath("/api/events")
+
+ server.`when`(requestDefinition).respond(response()
+ .withStatusCode(HttpStatusCode.OK_200.code())
+ .withHeaders(
+ Header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF_8.toString())
+ )
+ .withBody(ObjectMapper().findAndRegisterModules().writeValueAsString(events.map { it.id }))
+ )
+
+ getIdsRequestDefinition = requestDefinition
+ }
+
+ private fun setUpGetEventById() {
+ val requestDefinitions: MutableList = mutableListOf()
+
+ for (event in events) {
+ val reqDef = request()
+ .withMethod("GET")
+ .withPath("/api/events/" + event.id)
+
+ server.`when`(reqDef).respond(response()
+ .withStatusCode(200)
+ .withHeaders(
+ Header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF_8.toString())
+ )
+ .withBody(ObjectMapper().findAndRegisterModules().writeValueAsString(event))
+ )
+
+ requestDefinitions.add(reqDef)
+ }
+
+ getEventRequestDefinitions = requestDefinitions
+ }
+
+ @Test
+ fun `getEvents should call log, call the kalon server to get the ids, log the number of ids, call the server for each id and return a List of Events`(
+ output: CapturedOutput
+ ) {
+ assertEquals(events, kalonAdapter.getEvents())
+ assert(output.contains("Calling Kalon to get all existing events' ids"))
+ assert(output.contains("Calling Kalon to get [${events.size}] events"))
+
+ server.verify(getIdsRequestDefinition)
+ for (definition in getEventRequestDefinitions) {
+ server.verify(definition)
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/org/breizhcamp/konter/testUtils/Generators.kt b/src/test/kotlin/org/breizhcamp/konter/testUtils/Generators.kt
new file mode 100644
index 0000000..9dc2664
--- /dev/null
+++ b/src/test/kotlin/org/breizhcamp/konter/testUtils/Generators.kt
@@ -0,0 +1,296 @@
+package org.breizhcamp.konter.testUtils
+
+import org.breizhcamp.konter.domain.entities.*
+import org.breizhcamp.konter.domain.entities.enums.SessionFormatEnum
+import org.breizhcamp.konter.domain.entities.enums.SessionNiveauEnum
+import org.breizhcamp.konter.domain.entities.enums.SessionStatusEnum
+import org.breizhcamp.konter.domain.entities.enums.SessionThemeEnum
+import org.breizhcamp.konter.infrastructure.db.model.*
+import java.math.BigDecimal
+import java.time.Duration
+import java.time.LocalDateTime
+import java.time.Month
+import java.util.*
+import kotlin.math.absoluteValue
+import kotlin.random.Random
+
+fun generateRandomHexString(blocks: Int = 1): String {
+ val builder = StringBuilder()
+
+ for (i in 1..blocks) builder.append(
+ Random.nextInt().toString(16)
+ )
+
+ return builder.toString()
+}
+
+fun generateRandomLocalDateTime(year: Int = 2024): LocalDateTime =
+ LocalDateTime.of(
+ year,
+ Month.of(Random.nextInt(1, 12)),
+ Random.nextInt(1, 28),
+ Random.nextInt(0, 23),
+ Random.nextInt(0, 59),
+ Random.nextInt(0, 59)
+ )
+
+interface Generator {
+ fun generateOne(): T
+
+ fun generateList(mSize: Int = 5): List {
+ val maxSize = if (mSize < 5) 5 else mSize
+ return (1..maxSize).map { generateOne() }
+ }
+}
+
+class HallGen: Generator {
+ override fun generateOne(): Hall = Hall(
+ id = Random.nextInt().absoluteValue,
+ name = generateRandomHexString(),
+ trackId = Random.nextInt().absoluteValue
+ )
+}
+
+class HallDBGen: Generator {
+ override fun generateOne(): HallDB = HallDB(
+ id = Random.nextInt().absoluteValue,
+ name = generateRandomHexString(),
+ trackId = Random.nextInt().absoluteValue
+ )
+}
+
+class SpeakerGen: Generator {
+ override fun generateOne(): Speaker = Speaker(
+ id = UUID.randomUUID(),
+ lastname = generateRandomHexString(),
+ firstname = generateRandomHexString(),
+ email = generateRandomHexString(),
+ tagLine = generateRandomHexString(),
+ bio = generateRandomHexString(),
+ profilePicture = generateRandomHexString()
+ )
+}
+
+class SpeakerDBGen: Generator {
+ override fun generateOne(): SpeakerDB = SpeakerDB(
+ id = UUID.randomUUID(),
+ lastname = generateRandomHexString(),
+ firstname = generateRandomHexString(),
+ email = generateRandomHexString(),
+ tagLine = generateRandomHexString(),
+ bio = generateRandomHexString(),
+ profilePicture = generateRandomHexString()
+ )
+}
+
+class EventGen: Generator {
+ override fun generateOne(): Event = Event(
+ id = Random.nextInt().absoluteValue,
+ year = Random.nextInt(2020, 2030),
+ name = generateRandomHexString(),
+ begin = generateRandomLocalDateTime().toLocalDate(),
+ end = generateRandomLocalDateTime().toLocalDate()
+ )
+
+}
+
+class EventDBGen: Generator {
+ override fun generateOne(): EventDB = EventDB(
+ id = Random.nextInt().absoluteValue,
+ year = Random.nextInt(2020, 2030),
+ name = generateRandomHexString(),
+ halls = emptySet(),
+ begin = generateRandomLocalDateTime().toLocalDate(),
+ end = generateRandomLocalDateTime().toLocalDate()
+ )
+}
+
+class SessionGen: Generator {
+ override fun generateOne(): Session {
+ val speakerGen = SpeakerGen()
+
+ val owner = speakerGen.generateOne()
+ val speakers = speakerGen.generateList().toMutableList()
+ speakers.addFirst(owner)
+
+ val format = SessionFormatEnum.entries.random()
+ val theme = SessionThemeEnum .entries.random()
+ val niveau = SessionNiveauEnum.entries.random()
+ val status = SessionStatusEnum.entries.random()
+
+ return Session(
+ id = Random.nextInt().absoluteValue,
+ title = generateRandomHexString(),
+ description = generateRandomHexString(8),
+ owner = owner,
+ speakers = speakers,
+ format = format,
+ theme = theme,
+ niveau = niveau,
+ status = status,
+ submitted = generateRandomLocalDateTime(),
+ ownerNotes = generateRandomHexString(),
+ event = EventGen().generateOne(),
+ videoURL = generateRandomHexString(),
+ rating = BigDecimal.valueOf(Random.nextDouble(0.0, 5.0)),
+ slot = null
+ )
+ }
+}
+
+class SessionDBGen: Generator {
+ override fun generateOne(): SessionDB {
+ val owner = SpeakerDBGen().generateOne()
+ val speakers = SpeakerDBGen().generateList().toSet()
+ speakers.plus(owner)
+
+ val format = SessionFormatEnum.entries.random()
+ val theme = SessionThemeEnum .entries.random()
+ val niveau = SessionNiveauEnum.entries.random()
+ val status = SessionStatusEnum.entries.random()
+
+ return SessionDB(
+ id = Random.nextInt().absoluteValue,
+ title = generateRandomHexString(),
+ description = generateRandomHexString(8),
+ owner = owner,
+ speakers = speakers,
+ format = format,
+ theme = theme,
+ niveau = niveau,
+ status = status,
+ submitted = generateRandomLocalDateTime(),
+ ownerNotes = generateRandomHexString(),
+ event = EventDBGen().generateOne(),
+ videoURL = generateRandomHexString(),
+ rating = BigDecimal.valueOf(Random.nextDouble(0.0, 5.0)),
+ barcode = generateRandomHexString(),
+ slot = null
+ )
+ }
+}
+
+class ManualSessionGen: Generator {
+ override fun generateOne(): ManualSession {
+ val format = SessionFormatEnum.entries.random()
+ val theme = SessionThemeEnum .entries.random()
+
+ return ManualSession(
+ id = Random.nextInt().absoluteValue,
+ title = generateRandomHexString(),
+ description = generateRandomHexString(),
+ event = EventGen().generateOne(),
+ format = format,
+ theme = theme
+ )
+ }
+}
+
+class ManualSessionDBGen: Generator {
+ override fun generateOne(): ManualSessionDB {
+ val format = SessionFormatEnum.entries.random()
+ val theme = SessionThemeEnum .entries.random()
+
+ return ManualSessionDB(
+ id = Random.nextInt().absoluteValue,
+ title = generateRandomHexString(),
+ description = generateRandomHexString(),
+ event = EventDBGen().generateOne(),
+ format = format,
+ theme = theme,
+ speakers = SpeakerDBGen().generateList().toSet()
+ )
+ }
+}
+
+class SlotGen: Generator {
+ override fun generateOne(): Slot = Slot(
+ id = UUID.randomUUID(),
+ day = Random.nextInt(0, 5),
+ session = SessionGen().generateOne(),
+ manualSession = null,
+ event = EventGen().generateOne(),
+ halls = HallGen().generateList(),
+ start = generateRandomLocalDateTime().toLocalTime(),
+ duration = Duration.ofMinutes(Random.nextLong(15, 120)),
+ barcode = generateRandomHexString(),
+ span = Random.nextInt(1, 5),
+ title = generateRandomHexString(),
+ assignable = Random.nextBoolean()
+ )
+}
+
+class ImportSlotDBGen: Generator {
+ override fun generateOne(): SlotDB = SlotDB(
+ id = UUID.randomUUID(),
+ day = Random.nextInt(0, 5),
+ session = SessionDBGen().generateOne(),
+ manualSession = null,
+ event = EventDBGen().generateOne(),
+ halls = HallDBGen().generateList().toSet(),
+ start = generateRandomLocalDateTime().toLocalTime(),
+ duration = Duration.ofMinutes(Random.nextLong(15, 120)),
+ barcode = generateRandomHexString(),
+ title = generateRandomHexString(),
+ assignable = Random.nextBoolean()
+ )
+}
+
+class ManualSlotDBGen: Generator {
+ override fun generateOne(): SlotDB = SlotDB(
+ id = UUID.randomUUID(),
+ day = Random.nextInt(0, 5),
+ session = null,
+ manualSession = ManualSessionDBGen().generateOne(),
+ event = EventDBGen().generateOne(),
+ halls = HallDBGen().generateList().toSet(),
+ start = generateRandomLocalDateTime().toLocalTime(),
+ duration = Duration.ofMinutes(Random.nextLong(15, 120)),
+ barcode = generateRandomHexString(),
+ title = generateRandomHexString(),
+ assignable = Random.nextBoolean()
+ )
+}
+
+class TalkGen: Generator {
+ override fun generateOne(): Talk {
+ val format = SessionFormatEnum.entries.random()
+ val theme = SessionThemeEnum .entries.random()
+
+ return Talk(
+ id = Random.nextInt().absoluteValue,
+ name = generateRandomHexString(),
+ eventStart = generateRandomLocalDateTime(),
+ eventEnd = generateRandomLocalDateTime(),
+ eventType = theme,
+ format = format,
+ hall = HallGen().generateOne(),
+ speakers = SpeakerGen().generateList(),
+ videoUrl = null,
+ filesUrl = null,
+ slidesUrl = null,
+ description = generateRandomHexString()
+ )
+ }
+}
+
+class SessionFilterGen: Generator {
+ override fun generateOne(): SessionFilter {
+ val format = SessionFormatEnum.entries.random()
+ val theme = SessionThemeEnum.entries.random()
+ val niveau = SessionNiveauEnum.entries.random()
+ val status = SessionStatusEnum.entries.random()
+
+ return SessionFilter(
+ id = Random.nextInt().absoluteValue,
+ title = generateRandomHexString(),
+ speakerName = generateRandomHexString(),
+ format = format,
+ theme = theme,
+ niveau = niveau,
+ status = status,
+ rated = Random.nextBoolean()
+ )
+ }
+
+}
diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties
new file mode 100644
index 0000000..b2d2a5a
--- /dev/null
+++ b/src/test/resources/application.properties
@@ -0,0 +1 @@
+spring.profiles.active=test
\ No newline at end of file
diff --git a/src/test/resources/csv/evaluation-test.csv b/src/test/resources/csv/evaluation-test.csv
new file mode 100644
index 0000000..40f5794
--- /dev/null
+++ b/src/test/resources/csv/evaluation-test.csv
@@ -0,0 +1,10 @@
+"Session Id","Title","Description","Owner","Owner Email","Speakers","Format","Thème","Niveau","Evaluation"
+101,"PSQL, une technologie de dinosaures ? 👴🏻","PostgreSQL approche ses 30 ans de carrière, et reste un des systèmes de gestion de base de données les plus utilisés à ce jour
+
+À travers l'exemple de notre parc, découvrez pourquoi PSQL reste un système en accord avec notre temps et comment ses extensions permettent d'intégrer tous les types de données utilisés dans notre entreprise, allant de la localisation géographique des enclos aux codes génétiques de nos dinosaures.","John Hammond","john.hammond@jurassic-park.fr","John Hammond","Conférence (55 min)","Architecture","Introduction",3.9
+203,"Le futur de l'IA","Conférence sur les futures formes de l'IA, et comment le monde en sera impacté","Emmett Brown","doc@back-to-the-future.net","Emmett Brown","Conférence (55 min)","IA","Introduction",2.3
+207,"Dévoilons les Secrets de GitHub Actions","Découvrez comment gérer les secrets dans GitHub Actions pour sécuriser les workflows CI/CD.
+
+Clark aborde la création et la gestion des secrets, tandis que Bruce traite des techniques avancées comme la rotation et le partage sécurisé.
+
+Ensemble, nous montrons comment garantir des workflows efficaces et sécurisés dans GitHub Actions.","Bruce Wayne","bruce.wayne@wayne-industries.com","Bruce Wayne, Clark Kent","Tool in action (25 min)","Sécurité","Introduction",4.2
\ No newline at end of file
diff --git a/src/test/resources/csv/session-test.csv b/src/test/resources/csv/session-test.csv
new file mode 100644
index 0000000..00c2893
--- /dev/null
+++ b/src/test/resources/csv/session-test.csv
@@ -0,0 +1,28 @@
+"Session Id","Title","Description","Owner","Owner Email","Speakers","Format","Thème","Niveau","Status","Date Submitted","Owner Notes","Speaker Ids"
+101,"PSQL, une technologie de dinosaures ? 👴🏻","PostgreSQL approche ses 30 ans de carrière, et reste un des systèmes de gestion de base de données les plus utilisés à ce jour
+
+À travers l'exemple de notre parc, découvrez pourquoi PSQL reste un système en accord avec notre temps et comment ses extensions permettent d'intégrer tous les types de données utilisés dans notre entreprise, allant de la localisation géographique des enclos aux codes génétiques de nos dinosaures.","John Hammond","john.hammond@jurassic-park.fr","John Hammond","Conférence (55 min)","Architecture","Introduction","Nominated",14 avr. 2024 05:32 PM,"Après un bref rappel de ce qu'est une base de données relationnelle, cette conférence abordera les spécificités de Postgres, puis une présentation de notre cas d'usage. Ce cas d'usage a aussi pour but de présenter des extensions peu utilisées mais qui peuvent convenir à beaucoup de projets modernes, comme la gestion de données de localisation géographiques, de données vectorielles & de fichiers de grandes tailles","dd41f4a0-d300-4692-977a-14de9aa5db72"
+203,"Le futur de l'IA","Conférence sur les futures formes de l'IA, et comment le monde en sera impacté","Emmett Brown","doc@back-to-the-future.net","Emmett Brown","Conférence (55 min)","IA","Introduction","Nominated",15 avr. 2024 08:12 AM,,"7b1cce9c-59e4-4d6e-9ee2-9e20f1224ce4"
+207,"Dévoilons les Secrets de GitHub Actions","Découvrez comment gérer les secrets dans GitHub Actions pour sécuriser les workflows CI/CD.
+
+Clark aborde la création et la gestion des secrets, tandis que Bruce traite des techniques avancées comme la rotation et le partage sécurisé.
+
+Ensemble, nous montrons comment garantir des workflows efficaces et sécurisés dans GitHub Actions.","Bruce Wayne","bruce.wayne@wayne-industries.com","Bruce Wayne, Clark Kent","Tool in action (25 min)","Sécurité","Introduction","Nominated",15 avr. 2024 08:48 AM,"**Résumé**
+
+*Dévoiler les Secrets de GitHub Actions : démonstration*
+
+Découvrez comment gérer les secrets dans GitHub Actions, essentiels pour automatiser les workflows tout en sécurisant des éléments comme des clés d'API, des tokens d'authentification, ...
+
+Clark Kent, journaliste au Daily Bugle présente les bases de la gestion des secrets :
+
+1. **Définition des Secrets :** Création et gestion des secrets dans les paramètres du dépôt.
+2. **Contrôle d'Accès :** Accès réservé aux workflows spécifiques.
+3. **Segmentation par Environnement :** Configuration des secrets pour différents environnements (développement, staging, production).
+
+Bruce Wayne, ex-PDG de Wayne Industries, aborde quant à lui des techniques plus avancées pour la protection de vos secrets :
+
+1. **Rotation des Secrets :** Mise à jour régulière et automatisée.
+2. **Audit et Surveillance :** Suivi de l'utilisation des secrets.
+3. **Partage de Secrets :** Pratiques sécurisées pour partager entre différents dépôts.
+
+Ensemble, nous fournissons un guide complet pour gérer les secrets dans GitHub Actions, en soulignant l'importance de la sécurité dans les pipelines CI/CD. À la fin de cette session, vous saurez comment utiliser GitHub Actions pour des workflows efficaces et sécurisés.","17ac8d41-3167-455a-ba14-de73b20a4334, f9840ccd-0c22-4c6d-a712-be2c81847222"
\ No newline at end of file
diff --git a/src/test/resources/csv/speaker-test.csv b/src/test/resources/csv/speaker-test.csv
new file mode 100644
index 0000000..3d5623d
--- /dev/null
+++ b/src/test/resources/csv/speaker-test.csv
@@ -0,0 +1,5 @@
+"Speaker Id","FirstName","LastName","Email","TagLine","Bio","Profile Picture"
+"dd41f4a0-d300-4692-977a-14de9aa5db72","John","Hammond","john.hammond@jurassic-park.fr","J'ai dépensé sans compter","Président Directeur Général chez InGen LLC","https://static.wikia.nocookie.net/jurassicpark/images/d/d3/Hammond_%281993%29.PNG/revision/latest?cb=20210227191455&path-prefix=fr"
+"7b1cce9c-59e4-4d6e-9ee2-9e20f1224ce4","Emmett","Brown","doc@back-to-the-future.net","LĂ oĂą on va on n'a pas besoin de route","Inventeur du voyage dans le temps, mais surtout fan d'horloges :)","https://upload.wikimedia.org/wikipedia/commons/thumb/3/34/Emmett_Brown_Back_to_the_Future_Universal_Studios_Florida.JPG/800px-Emmett_Brown_Back_to_the_Future_Universal_Studios_Florida.JPG"
+"f9840ccd-0c22-4c6d-a712-be2c81847222","Clark","Kent","clark.kent@daily-bugle.com","Mais non, je n'ai rien Ă voir avec ce Superman dont vous parlez","Journaliste au Daily Bugle le jour, et mec normal la nuit","https://i.pinimg.com/originals/f2/35/78/f235783e1a67ed5bb6de83afd324dcaa.jpg"
+"17ac8d41-3167-455a-ba14-de73b20a4334","Bruce","Wayne","bruce.wayne@wayne-industries.com","You didn't get the memo?","Ancien PDG de Wayne Industries, je consacre maintenant ma vie Ă mes oeuvres de bienfaisance","https://tse1.mm.bing.net/th?id=OIP.lyXmlreDj9JKFtF5gFAPwQHaKF&pid=Api"
\ No newline at end of file