diff --git a/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/Entity.kt b/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/Entity.kt index 1855325..6406868 100644 --- a/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/Entity.kt +++ b/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/Entity.kt @@ -120,8 +120,8 @@ data class RatingComponent( @GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long = 0, val rating: Int, - val strengths: String, - val improvements: String + val strengths: String = "", + val improvements: String = "" ) @Entity diff --git a/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/GroupSessionManager.kt b/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/GroupSessionManager.kt index e165084..cab71ce 100644 --- a/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/GroupSessionManager.kt +++ b/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/GroupSessionManager.kt @@ -64,6 +64,10 @@ object GroupSessionManager { } fun dataComplete(): Boolean { + if(observations.isEmpty()){ + logger.warn("Observations is currently empty") + return false + } observations.values.forEach { it.scenarios.forEach { x -> if (!x.ratingsAllValid()) diff --git a/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/controller/GroupSessionController.kt b/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/controller/GroupSessionController.kt index 34d696e..ffe7caf 100644 --- a/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/controller/GroupSessionController.kt +++ b/backend/src/main/kotlin/uk/co/neviyn/observationdatabase/controller/GroupSessionController.kt @@ -125,6 +125,11 @@ class GroupSessionController { */ @PostMapping("/submit") fun addGroupObservation(@Valid @RequestBody observationData: GroupObservation) { + val titles = observationData.scenarios.map { it.title } + if(GroupSessionManager.scenarioTitles!!.size != titles.size || !GroupSessionManager.scenarioTitles!!.containsAll(titles)) { + logger.warn("Received scenario data but titles did not match\nInput:$titles\nRequired:${GroupSessionManager.scenarioTitles}") + throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Submission data contains non-matching title(s)") + } GroupSessionManager.updateObservationData(observationData) websocketMessenger.convertAndSend("/ws/scenarios", mapOf("scenarios" to GroupSessionManager.asScenarioView())) } @@ -132,6 +137,7 @@ class GroupSessionController { @PostMapping("/complete") fun pushObservationsToDatabase(): Map { if (GroupSessionManager.isValid() && GroupSessionManager.dataComplete()) { + logger.info("Completing session ${GroupSessionManager.sessionId}") val tutors = tutorRepository.findAllById(GroupSessionManager.tutors!!).toSet() GroupSessionManager.observations.values.forEach { x -> saveObservation(Observation( @@ -157,6 +163,7 @@ class GroupSessionController { websocketMessenger.convertAndSend("/ws/status", mapOf("status" to "complete")) return mapOf("success" to "The submission was successfully completed.") } else if (!GroupSessionManager.dataComplete()) { + logger.info("Tried to complete a session whilst data was incomplete\n${GroupSessionManager.observations}") throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Data is incomplete") } throw ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "No valid session") @@ -178,11 +185,10 @@ class GroupSessionController { } if (ipv4 != null && this::environment.isInitialized) return mapOf("ip" to ipv4, "port" to environment["local.server.port"]) - else if(ipv4 == null) { + else if (ipv4 == null) { logger.error("IP Address could not be determined") return mapOf("error" to "Could not determine IP Address") - } - else { + } else { logger.error("Port could not be determined, environment not initialised") return mapOf("error" to "Could not determine port") } diff --git a/backend/src/test/kotlin/uk/co/neviyn/observationdatabase/controller/GroupSessionControllerTest.kt b/backend/src/test/kotlin/uk/co/neviyn/observationdatabase/controller/GroupSessionControllerTest.kt index 8d08af9..6ae5952 100644 --- a/backend/src/test/kotlin/uk/co/neviyn/observationdatabase/controller/GroupSessionControllerTest.kt +++ b/backend/src/test/kotlin/uk/co/neviyn/observationdatabase/controller/GroupSessionControllerTest.kt @@ -1,18 +1,29 @@ package uk.co.neviyn.observationdatabase.controller +import org.joda.time.LocalDate import org.junit.After import org.junit.Assert.* import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any import org.mockito.InjectMocks import org.mockito.Mock import org.mockito.Mockito +import org.mockito.Mockito.times +import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnitRunner +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.messaging.simp.SimpMessagingTemplate import org.springframework.web.server.ResponseStatusException +import uk.co.neviyn.observationdatabase.GroupObservation import uk.co.neviyn.observationdatabase.GroupObservationInit import uk.co.neviyn.observationdatabase.GroupSessionManager +import uk.co.neviyn.observationdatabase.Observation import uk.co.neviyn.observationdatabase.ObservationRepository +import uk.co.neviyn.observationdatabase.Person import uk.co.neviyn.observationdatabase.PersonRepository +import uk.co.neviyn.observationdatabase.RatingComponent +import uk.co.neviyn.observationdatabase.Scenario import uk.co.neviyn.observationdatabase.Site import uk.co.neviyn.observationdatabase.SiteRepository import uk.co.neviyn.observationdatabase.TrainingType @@ -26,6 +37,8 @@ class GroupSessionControllerTest { @InjectMocks lateinit var controller: GroupSessionController + @Mock + lateinit var websocketMessenger: SimpMessagingTemplate @Mock lateinit var siteRepository: SiteRepository @Mock @@ -74,4 +87,52 @@ class GroupSessionControllerTest { fun testRecoverSession_NoActiveSession() { controller.reconnectToGroupObservation() } + + @Test + fun testCompleteSession(){ + val site = Site(1, "Test site") + val tutor = Tutor(1, "Mr X", site) + Mockito.doReturn(Optional.of(site)).`when`(siteRepository).findById(1) + Mockito.doReturn(listOf(tutor)).`when`(tutorRepository).findAllById(listOf(1)) + val person = Person(1, "A Student") + Mockito.doReturn(person).`when`(personRepository).save(any()) + val rc = RatingComponent(rating = 5) + val scenario = Scenario(0, "Sample title", rc, rc, rc, rc, rc, rc, rc, rc) + Mockito.doReturn(Observation(1, site, LocalDate.now(), TrainingType.INITIAL, "Sample title", 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, listOf(scenario), setOf(tutor), person)).`when`(observationRepository).save(any()) + controller.startGroupObservation(GroupObservationInit(1, TrainingType.INITIAL, listOf(1), listOf("Sample title"))) + controller.addGroupObservation(GroupObservation("A Student", listOf(scenario))) + controller.pushObservationsToDatabase() + verify(observationRepository, times(1)).save(any()) + assertEquals(1, tutor.observations.size) + } + + @Test(expected = ResponseStatusException::class) + fun testCompleteSession_NoObservationData() { + val site = Site(1, "Test site") + Mockito.doReturn(Optional.of(site)).`when`(siteRepository).findById(1) + Mockito.doReturn(listOf(Tutor(1, "Mr X", site))).`when`(tutorRepository).findAllById(listOf(1)) + controller.startGroupObservation(GroupObservationInit(1, TrainingType.INITIAL, listOf(1), listOf("Sample title"))) + controller.pushObservationsToDatabase() + } + + @Test(expected = ResponseStatusException::class) + fun testCompleteSession_PartialObservationData() { + val site = Site(1, "Test site") + Mockito.doReturn(Optional.of(site)).`when`(siteRepository).findById(1) + Mockito.doReturn(listOf(Tutor(1, "Mr X", site))).`when`(tutorRepository).findAllById(listOf(1)) + controller.startGroupObservation(GroupObservationInit(1, TrainingType.INITIAL, listOf(1), listOf("Sample title"))) + val rc = RatingComponent(rating = 5) + controller.addGroupObservation(GroupObservation("A Student", listOf(Scenario(0, "Sample title", RatingComponent(rating = 0), rc, rc, rc, rc, rc, rc, rc)))) + controller.pushObservationsToDatabase() + } + + @Test(expected = ResponseStatusException::class) + fun testCompleteSession_NonMatchingTitles() { + val site = Site(1, "Test site") + Mockito.doReturn(Optional.of(site)).`when`(siteRepository).findById(1) + Mockito.doReturn(listOf(Tutor(1, "Mr X", site))).`when`(tutorRepository).findAllById(listOf(1)) + controller.startGroupObservation(GroupObservationInit(1, TrainingType.INITIAL, listOf(1), listOf("Sample title"))) + val rc = RatingComponent(rating = 5) + controller.addGroupObservation(GroupObservation("A Student", listOf(Scenario(0, "Different Title", rc, rc, rc, rc, rc, rc, rc, rc)))) + } } \ No newline at end of file