From 5121fd3e4d2357261975d333ceb370171042e09f Mon Sep 17 00:00:00 2001 From: Maria <145002050+marikolafs@users.noreply.github.com> Date: Wed, 22 Apr 2026 15:06:14 +0200 Subject: [PATCH 01/25] update docs --- .../idatt/backend/classroom/domain/ClassroomPupilStatus.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/domain/ClassroomPupilStatus.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/domain/ClassroomPupilStatus.kt index e2b21e42..9d09edc3 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/domain/ClassroomPupilStatus.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/domain/ClassroomPupilStatus.kt @@ -1,10 +1,10 @@ package edu.ntnu.idi.idatt.backend.classroom.domain /** - * Enum representing the possible statuses for a classroom. + * Enum representing the possible statuses for a pupil membership. * - * - [ACTIVE]: The classroom is open and available for users to join and participate. - * - [INACTIVE]: The classroom is closed or archived and cannot be joined or used. + * - [ACTIVE]: The pupil has been approved. + * - [PENDING]: The pupil has not yet been approved. */ enum class ClassroomPupilStatus { ACTIVE, From 0b3a99d7137639edf91823132e3dad40feab1a8f Mon Sep 17 00:00:00 2001 From: Maria <145002050+marikolafs@users.noreply.github.com> Date: Wed, 22 Apr 2026 18:31:46 +0200 Subject: [PATCH 02/25] save classroom in pupilProfile --- .../backend/bootstrap/DevDataBootstrap.kt | 10 +++--- .../classroom/api/ClassroomApiMappings.kt | 2 +- .../classroom/api/ClassroomResponse.kt | 4 +-- .../classroom/application/ClassroomService.kt | 7 ++++ .../iam/application/CurrentUserResolver.kt | 2 +- .../application/CurrentUserProfileService.kt | 4 +-- .../users/profile/domain/PupilProfile.kt | 11 +++--- .../backend/classroom/ClassroomServiceTest.kt | 35 ++++++++++++------- .../backend/iam/CurrentUserResolverTests.kt | 2 +- .../profile/CurrentUserProfileServiceTests.kt | 7 ++-- .../UserProfileControllerIntegrationTests.kt | 4 +-- 11 files changed, 54 insertions(+), 34 deletions(-) diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/bootstrap/DevDataBootstrap.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/bootstrap/DevDataBootstrap.kt index 97186f9e..ac1076b2 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/bootstrap/DevDataBootstrap.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/bootstrap/DevDataBootstrap.kt @@ -104,8 +104,8 @@ class DevDataBootstrap( ) seedClassroomMembership(sampleClassroom, pupilAda, "Ada") seedClassroomMembership(sampleClassroom, pupilNoah, "Noah") - seedPupilProfile(pupilAda, "Ada", sampleClassroom.id) - seedPupilProfile(pupilNoah, "Noah", sampleClassroom.id) + seedPupilProfile(pupilAda, "Ada", sampleClassroom) + seedPupilProfile(pupilNoah, "Noah", sampleClassroom) seedPupilAvatar(pupilAda) seedPupilAvatar(pupilNoah) seedPupilGameProgress( @@ -224,13 +224,13 @@ class DevDataBootstrap( private fun seedPupilProfile( user: User, displayName: String, - currentClassroomId: Long?, + currentClassroom: Classroom?, ) { val userId = requireNotNull(user.id) { "Pupil seed user must have an id" } val existingProfile = pupilProfileRepository.findById(userId).orElse(null) if (existingProfile != null) { existingProfile.displayName = displayName - existingProfile.currentClassroomId = currentClassroomId + existingProfile.currentClassroom = currentClassroom return } @@ -238,7 +238,7 @@ class DevDataBootstrap( PupilProfile( user = user, displayName = displayName, - currentClassroomId = currentClassroomId, + currentClassroom = currentClassroom, ), ) } diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomApiMappings.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomApiMappings.kt index edad66cc..5a9b7d9d 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomApiMappings.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomApiMappings.kt @@ -67,7 +67,7 @@ fun JoinClassroomResult.toJoinClassroomResponse(): JoinClassroomResponse = */ fun Classroom.toResponse(): ClassroomResponse = ClassroomResponse( - id = id, + classroomId = id, title = title, description = description, joinCode = joinCode ?: "", diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomResponse.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomResponse.kt index 6cd92255..399cb5b0 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomResponse.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomResponse.kt @@ -3,14 +3,14 @@ package edu.ntnu.idi.idatt.backend.classroom.api /** * DTO for response to classroom requests. * - * @param id the id of a classroom. + * @param classroomId the id of a classroom. * @param title the title of a classroom. * @param description the optional description of a classroom. * @param joinCode the code to join the classroom. * @param teacherUserIds a list of ids of teahcers belonging to the classroom. */ data class ClassroomResponse( - val id: Long, + val classroomId: Long, val title: String, val description: String?, val joinCode: String, diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/application/ClassroomService.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/application/ClassroomService.kt index c746cad5..262f4889 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/application/ClassroomService.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/application/ClassroomService.kt @@ -12,6 +12,7 @@ import edu.ntnu.idi.idatt.backend.iam.application.CurrentUserResolver import edu.ntnu.idi.idatt.backend.iam.infrastructure.UserRepository import edu.ntnu.idi.idatt.backend.game.tasks.infrastructure.TaskRepository import edu.ntnu.idi.idatt.backend.iam.domain.UserRole +import edu.ntnu.idi.idatt.backend.users.profile.infrastructure.PupilProfileRepository import org.springframework.context.ApplicationEventPublisher import org.springframework.http.HttpStatus import org.springframework.stereotype.Service @@ -38,6 +39,7 @@ class ClassroomService( private val pupilGameProgressRepository: PupilGameProgressRepository, private val taskRepository: TaskRepository, private val eventPublisher: ApplicationEventPublisher, + private val pupilProfileRepository: PupilProfileRepository, ) { /** @@ -127,7 +129,12 @@ class ClassroomService( } classroom.addPupilMembership(user) + val profile = pupilProfileRepository.findById(userId) + .orElseThrow { ResponseStatusException(HttpStatus.NOT_FOUND, "Profile not found") } + profile.currentClassroom = classroom + pupilProfileRepository.save(profile) classroomRepository.save(classroom) + pupilGameProgressRepository.save( PupilGameProgress( id = PupilGameProgressId( diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/iam/application/CurrentUserResolver.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/iam/application/CurrentUserResolver.kt index 96800371..46cd5bb6 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/iam/application/CurrentUserResolver.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/iam/application/CurrentUserResolver.kt @@ -84,7 +84,7 @@ class CurrentUserResolver( .orElseThrow { ResponseStatusException(HttpStatus.NOT_FOUND, "Pupil profile was not found") } - val classroomId = pupilProfile.currentClassroomId + val classroomId = pupilProfile.currentClassroom?.id ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Pupil has no current classroom") classroomRepository.findById(classroomId) .orElseThrow { diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt index d1cda6f3..c9e830ea 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt @@ -39,7 +39,7 @@ class CurrentUserProfileService( .map { PupilProfileDetails( displayName = it.displayName, - currentClassroomId = it.currentClassroomId, + currentClassroomId = it.currentClassroom?.id, ) } .orElseThrow { @@ -94,7 +94,7 @@ class CurrentUserProfileService( return PupilProfileDetails( displayName = savedProfile.displayName, - currentClassroomId = savedProfile.currentClassroomId, + currentClassroomId = savedProfile.currentClassroom?.id, ) } diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/domain/PupilProfile.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/domain/PupilProfile.kt index d52e74ed..5dfd6f9d 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/domain/PupilProfile.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/domain/PupilProfile.kt @@ -1,5 +1,6 @@ package edu.ntnu.idi.idatt.backend.users.profile.domain +import edu.ntnu.idi.idatt.backend.classroom.domain.Classroom import edu.ntnu.idi.idatt.backend.iam.domain.User import jakarta.persistence.Column @@ -7,6 +8,7 @@ import jakarta.persistence.Entity import jakarta.persistence.FetchType import jakarta.persistence.Id import jakarta.persistence.JoinColumn +import jakarta.persistence.ManyToOne import jakarta.persistence.MapsId import jakarta.persistence.OneToOne import jakarta.persistence.Table @@ -17,7 +19,7 @@ import jakarta.persistence.Table * @property userId shared primary key matching the owning user id * @property user owning pupil user account * @property displayName pupil name shown in the product - * @property currentClassroomId current classroom id, or `null` when unassigned + * @property currentClassroom current classroom, or `null` when unassigned */ @Entity @Table(name = "pupil_profiles") @@ -31,8 +33,7 @@ class PupilProfile( var user: User, @Column(name = "display_name", nullable = false, length = 100) var displayName: String, - // TODO: replace with @ManyToOne/@JoinColumn when Classroom table exists - // this column references classrooms.id. - @Column(name = "current_classroom_id") - var currentClassroomId: Long? = null, + @ManyToOne(fetch = FetchType.LAZY, optional = true) + @JoinColumn(name = "current_classroom_id") + var currentClassroom: Classroom? = null, ) diff --git a/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomServiceTest.kt b/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomServiceTest.kt index ad73dd55..25f872dc 100644 --- a/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomServiceTest.kt +++ b/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomServiceTest.kt @@ -19,6 +19,8 @@ import edu.ntnu.idi.idatt.backend.iam.domain.User import edu.ntnu.idi.idatt.backend.iam.domain.UserRole import edu.ntnu.idi.idatt.backend.iam.domain.UserStatus import edu.ntnu.idi.idatt.backend.iam.infrastructure.UserRepository +import edu.ntnu.idi.idatt.backend.users.profile.domain.PupilProfile +import edu.ntnu.idi.idatt.backend.users.profile.infrastructure.PupilProfileRepository import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach @@ -32,6 +34,7 @@ import org.springframework.context.ApplicationEventPublisher import org.springframework.http.HttpStatus import org.springframework.web.server.ResponseStatusException import org.mockito.Mockito.any +import java.util.Optional class ClassroomServiceTest { private lateinit var classroomRepository: ClassroomRepository @@ -42,6 +45,7 @@ class ClassroomServiceTest { private lateinit var classroomService: ClassroomService private lateinit var userRepository: UserRepository private lateinit var eventPublisher: ApplicationEventPublisher + private lateinit var pupilProfileRepository: PupilProfileRepository @BeforeEach fun setup() { @@ -52,6 +56,7 @@ class ClassroomServiceTest { pupilGameProgressRepository = mock(PupilGameProgressRepository::class.java) taskRepository = mock(TaskRepository::class.java) eventPublisher = mock(ApplicationEventPublisher::class.java) + pupilProfileRepository = mock(PupilProfileRepository::class.java) classroomService = ClassroomService( classroomRepository, currentUserResolver, @@ -60,19 +65,13 @@ class ClassroomServiceTest { pupilGameProgressRepository, taskRepository, eventPublisher, + pupilProfileRepository, ) } @Test fun `should create classroom and assign teacher`() { - val teacher = User( - id = 1, - email = "teacher@test.com", - username = "teacher", - passwordHash = "pw", - role = UserRole.TEACHER, - status = UserStatus.ACTIVE, - ) + val teacher = User(id = 1, email = "teacher@test.com", username = "teacher", passwordHash = "pw", role = UserRole.TEACHER, status = UserStatus.ACTIVE,) `when`(currentUserResolver.requireTeacherOrAdmin("1")).thenReturn(teacher) `when`(classroomRepository.save(any())).thenAnswer { it.arguments[0] } @@ -94,11 +93,16 @@ class ClassroomServiceTest { @Test fun `should add user to classroom`() { val classroomCode = "ABC12" + val classroom = Classroom(id = 1, ownerTeacherId = 2, title = "class", description = "description", joinCode = classroomCode, status = ClassroomStatus.ACTIVE) + val user = User(id = 3, email = "test@example.com", username = "user", passwordHash = "password", role = UserRole.PUPIL, status = UserStatus.ACTIVE) + val pupilProfile = PupilProfile(userId = 3, user = user, displayName = "test", currentClassroom = null,) + `when`(classroomRepository.findByJoinCode(classroomCode)).thenReturn(classroom) `when`(currentUserResolver.requirePupil("3")).thenReturn(user) + `when`(pupilProfileRepository.findById(3)).thenReturn(Optional.of(pupilProfile)) val command = JoinClassroomCommand(classroomCode = classroomCode, authenticatedSubject = "3") val result = classroomService.joinClassroom(command) @@ -107,9 +111,11 @@ class ClassroomServiceTest { assertEquals(1L, result.classroomId) assertEquals("class", result.classroomTitle) assertEquals("PENDING", result.membershipStatus) + verify(classroomRepository).save(classroom) verify(pupilGameProgressRepository).save(any(PupilGameProgress::class.java)) - verify(classroomRepository).findByJoinCode(classroomCode) + verify(pupilProfileRepository).save(pupilProfile) + assertEquals(classroom, pupilProfile.currentClassroom) } @Test @@ -233,19 +239,22 @@ class ClassroomServiceTest { @Test fun `should normalize classroom code before lookup`() { val classroom = Classroom(id = 1, ownerTeacherId = 2, title = "class", description = "description", joinCode = "ABC123", status = ClassroomStatus.ACTIVE) + val user = User(id = 3, email = "test@example.com", username = "user", passwordHash = "password", role = UserRole.PUPIL, status = UserStatus.ACTIVE) + val pupilProfile = PupilProfile(userId = 3, user = user, displayName = "test", currentClassroom = null,) + `when`(classroomRepository.findByJoinCode("ABC123")).thenReturn(classroom) `when`(currentUserResolver.requirePupil("3")).thenReturn(user) + `when`(pupilProfileRepository.findById(3)).thenReturn(Optional.of(pupilProfile)) classroomService.joinClassroom( - JoinClassroomCommand( - classroomCode = " abc123 ", - authenticatedSubject = "3", - ), + JoinClassroomCommand(classroomCode = " abc123 ", authenticatedSubject = "3"), ) verify(classroomRepository).findByJoinCode("ABC123") + verify(pupilProfileRepository).save(pupilProfile) + assertEquals(classroom, pupilProfile.currentClassroom) } @Test diff --git a/src/test/kotlin/edu/ntnu/idi/idatt/backend/iam/CurrentUserResolverTests.kt b/src/test/kotlin/edu/ntnu/idi/idatt/backend/iam/CurrentUserResolverTests.kt index df452eb6..a11546b7 100644 --- a/src/test/kotlin/edu/ntnu/idi/idatt/backend/iam/CurrentUserResolverTests.kt +++ b/src/test/kotlin/edu/ntnu/idi/idatt/backend/iam/CurrentUserResolverTests.kt @@ -110,7 +110,7 @@ class CurrentUserResolverTests { userId = 11L, user = user, displayName = "Pupil One", - currentClassroomId = 101L, + currentClassroom = classroom, ) Mockito.`when`(userRepository.findById(11L)).thenReturn(Optional.of(user)) diff --git a/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/CurrentUserProfileServiceTests.kt b/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/CurrentUserProfileServiceTests.kt index be26bee0..2d850c35 100644 --- a/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/CurrentUserProfileServiceTests.kt +++ b/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/CurrentUserProfileServiceTests.kt @@ -1,5 +1,6 @@ package edu.ntnu.idi.idatt.backend.users.profile +import edu.ntnu.idi.idatt.backend.classroom.domain.Classroom import edu.ntnu.idi.idatt.backend.iam.application.CurrentUserResolver import edu.ntnu.idi.idatt.backend.iam.domain.User import edu.ntnu.idi.idatt.backend.iam.domain.UserRole @@ -24,6 +25,7 @@ class CurrentUserProfileServiceTests { val currentUserResolver = Mockito.mock(CurrentUserResolver::class.java) val pupilProfileRepository = Mockito.mock(PupilProfileRepository::class.java) val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) + val classroom = Classroom(id = 101L, ownerTeacherId = 7L, title = "Class A") val service = CurrentUserProfileService( currentUserResolver, pupilProfileRepository, @@ -33,7 +35,7 @@ class CurrentUserProfileServiceTests { val pupilProfile = PupilProfile( user = user, displayName = "Ada", - currentClassroomId = 101L, + currentClassroom = classroom, ).apply { userId = 42L } @@ -176,6 +178,7 @@ class CurrentUserProfileServiceTests { val currentUserResolver = Mockito.mock(CurrentUserResolver::class.java) val pupilProfileRepository = Mockito.mock(PupilProfileRepository::class.java) val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) + val classroom = Classroom(id = 101L, ownerTeacherId = 7L, title = "Class A") val service = CurrentUserProfileService( currentUserResolver, pupilProfileRepository, @@ -185,7 +188,7 @@ class CurrentUserProfileServiceTests { val pupilProfile = PupilProfile( user = user, displayName = "Ada", - currentClassroomId = 101L, + currentClassroom = classroom, ).apply { userId = 42L } diff --git a/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/UserProfileControllerIntegrationTests.kt b/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/UserProfileControllerIntegrationTests.kt index 9c2c30fa..6288f9c6 100644 --- a/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/UserProfileControllerIntegrationTests.kt +++ b/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/UserProfileControllerIntegrationTests.kt @@ -75,7 +75,7 @@ class UserProfileControllerIntegrationTests { PupilProfile( user = pupil, displayName = "Ada", - currentClassroomId = classroom.id, + currentClassroom = classroom, ), ) @@ -145,7 +145,7 @@ class UserProfileControllerIntegrationTests { PupilProfile( user = pupil, displayName = "Ada", - currentClassroomId = classroom.id, + currentClassroom = classroom, ), ) From 4d07726fb3c3faee71ab814eec04c85749758020 Mon Sep 17 00:00:00 2001 From: Maria <145002050+marikolafs@users.noreply.github.com> Date: Wed, 22 Apr 2026 18:42:34 +0200 Subject: [PATCH 03/25] fix --- .../users/profile/application/CurrentUserProfileService.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt index 14fc4ed6..f768e120 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt @@ -40,7 +40,7 @@ class CurrentUserProfileService( val pupilProfile = if (user.role == UserRole.PUPIL) { pupilProfileRepository.findById(userId) .map { - val progress = getCurrentClassroomProgress(userId, it.currentClassroomId) + val progress = getCurrentClassroomProgress(userId, it.currentClassroom?.id) PupilProfileDetails( displayName = it.displayName, @@ -96,7 +96,7 @@ class CurrentUserProfileService( val user = currentUserResolver.requirePupil(authenticatedSubject) val userId = requireNotNull(user.id) { "Authenticated user is missing an id" } val pupilProfile = findPupilProfile(userId) - val progress = getCurrentClassroomProgress(userId, pupilProfile.currentClassroomId) + val progress = getCurrentClassroomProgress(userId, pupilProfile.currentClassroom?.id) pupilProfile.displayName = command.displayName.trim() val savedProfile = pupilProfileRepository.save(pupilProfile) From 71f7a199ecd72513803c343a3f681778a5841ef7 Mon Sep 17 00:00:00 2001 From: Maria <145002050+marikolafs@users.noreply.github.com> Date: Wed, 22 Apr 2026 18:48:50 +0200 Subject: [PATCH 04/25] fix tests --- .../NotebookEntryControllerIntegrationTests.kt | 12 ++++++------ .../users/profile/CurrentUserProfileServiceTests.kt | 7 ++++--- .../profile/UserProfileControllerIntegrationTests.kt | 2 +- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/test/kotlin/edu/ntnu/idi/idatt/backend/notebook/NotebookEntryControllerIntegrationTests.kt b/src/test/kotlin/edu/ntnu/idi/idatt/backend/notebook/NotebookEntryControllerIntegrationTests.kt index 423196d7..b831374c 100644 --- a/src/test/kotlin/edu/ntnu/idi/idatt/backend/notebook/NotebookEntryControllerIntegrationTests.kt +++ b/src/test/kotlin/edu/ntnu/idi/idatt/backend/notebook/NotebookEntryControllerIntegrationTests.kt @@ -83,8 +83,8 @@ class NotebookEntryControllerIntegrationTests { ) val pupil = userRepository.save(activePupil("ada")) val otherPupil = userRepository.save(activePupil("grace")) - pupilProfileRepository.save(PupilProfile(user = pupil, displayName = "Ada", currentClassroomId = classroom.id)) - pupilProfileRepository.save(PupilProfile(user = otherPupil, displayName = "Grace", currentClassroomId = classroom.id)) + pupilProfileRepository.save(PupilProfile(user = pupil, displayName = "Ada", currentClassroom = classroom)) + pupilProfileRepository.save(PupilProfile(user = otherPupil, displayName = "Grace", currentClassroom = classroom)) notebookEntryRepository.save( NotebookEntry( pupil = pupil, @@ -130,7 +130,7 @@ class NotebookEntryControllerIntegrationTests { MapStop(slug = "intro", title = "Intro") ) val pupil = userRepository.save(activePupil("ada")) - pupilProfileRepository.save(PupilProfile(user = pupil, displayName = "Ada", currentClassroomId = classroom.id)) + pupilProfileRepository.save(PupilProfile(user = pupil, displayName = "Ada", currentClassroom = classroom)) val accessToken = pupilAccessToken("ada", "secret123") val body = readJson( @@ -172,7 +172,7 @@ class NotebookEntryControllerIntegrationTests { MapStop(slug = "intro", title = "Intro") ) val pupil = userRepository.save(activePupil("ada")) - pupilProfileRepository.save(PupilProfile(user = pupil, displayName = "Ada", currentClassroomId = classroom.id)) + pupilProfileRepository.save(PupilProfile(user = pupil, displayName = "Ada", currentClassroom = classroom)) val entry = notebookEntryRepository.save( NotebookEntry( pupil = pupil, @@ -212,8 +212,8 @@ class NotebookEntryControllerIntegrationTests { ) val pupil = userRepository.save(activePupil("ada")) val otherPupil = userRepository.save(activePupil("grace")) - pupilProfileRepository.save(PupilProfile(user = pupil, displayName = "Ada", currentClassroomId = classroom.id)) - pupilProfileRepository.save(PupilProfile(user = otherPupil, displayName = "Grace", currentClassroomId = classroom.id)) + pupilProfileRepository.save(PupilProfile(user = pupil, displayName = "Ada", currentClassroom = classroom)) + pupilProfileRepository.save(PupilProfile(user = otherPupil, displayName = "Grace", currentClassroom = classroom)) val entry = notebookEntryRepository.save( NotebookEntry( pupil = otherPupil, diff --git a/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/CurrentUserProfileServiceTests.kt b/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/CurrentUserProfileServiceTests.kt index 21c04de9..39dac74d 100644 --- a/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/CurrentUserProfileServiceTests.kt +++ b/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/CurrentUserProfileServiceTests.kt @@ -78,7 +78,7 @@ class CurrentUserProfileServiceTests { val pupilProfile = PupilProfile( user = user, displayName = "Ada", - currentClassroomId = null, + currentClassroom = null, ).apply { userId = 42L } @@ -99,6 +99,7 @@ class CurrentUserProfileServiceTests { val pupilProfileRepository = Mockito.mock(PupilProfileRepository::class.java) val pupilGameProgressRepository = Mockito.mock(PupilGameProgressRepository::class.java) val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) + val classroom = Classroom(id = 101L, ownerTeacherId = 7L, title = "Class A") val service = CurrentUserProfileService( currentUserResolver, pupilProfileRepository, @@ -109,14 +110,14 @@ class CurrentUserProfileServiceTests { val pupilProfile = PupilProfile( user = user, displayName = "Ada", - currentClassroomId = 101L, + currentClassroom = classroom, ).apply { userId = 42L } Mockito.`when`(currentUserResolver.requireUser("42")).thenReturn(user) Mockito.`when`(pupilProfileRepository.findById(42L)).thenReturn(Optional.of(pupilProfile)) - Mockito.`when`(pupilGameProgressRepository.findByPupilIdAndClassroomId(42L, 101L)).thenReturn(null) + Mockito.`when`(pupilGameProgressRepository.findByPupilIdAndClassroomId(42L, classroom.id)).thenReturn(null) val resolvedProfile = service.getCurrentUserProfile("42") diff --git a/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/UserProfileControllerIntegrationTests.kt b/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/UserProfileControllerIntegrationTests.kt index 4aff649d..1d850d5b 100644 --- a/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/UserProfileControllerIntegrationTests.kt +++ b/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/UserProfileControllerIntegrationTests.kt @@ -127,7 +127,7 @@ class UserProfileControllerIntegrationTests { PupilProfile( user = pupil, displayName = "Ada", - currentClassroomId = classroom.id, + currentClassroom = classroom, ), ) From 68f30ba1610edfa72898eba1a0b2373bc8880953 Mon Sep 17 00:00:00 2001 From: Maria <145002050+marikolafs@users.noreply.github.com> Date: Thu, 23 Apr 2026 09:33:18 +0200 Subject: [PATCH 05/25] fix tests --- .../classroom/ClassroomIntegrationTests.kt | 75 +++++++++++-------- 1 file changed, 42 insertions(+), 33 deletions(-) diff --git a/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomIntegrationTests.kt b/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomIntegrationTests.kt index 66361c21..03f3c4ae 100644 --- a/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomIntegrationTests.kt +++ b/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomIntegrationTests.kt @@ -13,6 +13,7 @@ import edu.ntnu.idi.idatt.backend.iam.domain.UserStatus import edu.ntnu.idi.idatt.backend.iam.infrastructure.RefreshTokenRepository import edu.ntnu.idi.idatt.backend.iam.infrastructure.UserRepository import edu.ntnu.idi.idatt.backend.users.avatar.infrastructure.UserAvatarRepository +import edu.ntnu.idi.idatt.backend.users.profile.domain.PupilProfile import edu.ntnu.idi.idatt.backend.users.profile.infrastructure.PupilProfileRepository import edu.ntnu.idi.idatt.backend.users.profile.infrastructure.TeacherProfileRepository import org.junit.jupiter.api.Assertions.assertEquals @@ -54,9 +55,6 @@ class ClassroomIntegrationTests { @Autowired private lateinit var userAvatarRepository: UserAvatarRepository - @Autowired - private lateinit var pupilProfileRepository: PupilProfileRepository - @Autowired private lateinit var teacherProfileRepository: TeacherProfileRepository @@ -66,6 +64,9 @@ class ClassroomIntegrationTests { @Autowired private lateinit var jdbcTemplate: JdbcTemplate + @Autowired + private lateinit var pupilProfileRepository: PupilProfileRepository + private val jsonMapper = JsonMapper.builder().build() @BeforeEach @@ -77,6 +78,7 @@ class ClassroomIntegrationTests { pupilProfileRepository.deleteAll() teacherProfileRepository.deleteAll() userRepository.deleteAll() + pupilProfileRepository.deleteAll() } @Test @@ -94,8 +96,10 @@ class ClassroomIntegrationTests { fun `authenticated pupil can join classroom`() { val classroom = createClassroom("CLASS42") val pupil = userRepository.save(activePupil(username = "ada")) + pupilProfileRepository.save(PupilProfile(user = pupil, displayName = "ada", currentClassroom = classroom)) val accessToken = pupilAccessToken(username = "ada", password = "secret123") + mockMvc.post("/api/v1/classrooms/join") { with(csrf()) contentType = MediaType.APPLICATION_JSON @@ -109,6 +113,12 @@ class ClassroomIntegrationTests { jsonPath("$.message") { value("Join request submitted") } } + val updatedProfile = pupilProfileRepository + .findById(requireNotNull(pupil.id)) + .orElseThrow() + + assertEquals(classroom.id, updatedProfile.currentClassroom?.id) + val membershipCount = jdbcTemplate.queryForObject( "SELECT COUNT(*) FROM classroom_pupils WHERE classroom_id = ? AND pupil_user_id = ?", Int::class.java, @@ -128,8 +138,9 @@ class ClassroomIntegrationTests { @Test fun `authenticated pupil can join classroom with padded lowercase code`() { - createClassroom("CLASS42") + val classroom = createClassroom("CLASS42") val pupil = userRepository.save(activePupil(username = "padding-check")) + pupilProfileRepository.save(PupilProfile(user = pupil, displayName = "padding", currentClassroom = classroom)) val accessToken = pupilAccessToken(username = "padding-check", password = "secret123") mockMvc.post("/api/v1/classrooms/join") { @@ -143,6 +154,15 @@ class ClassroomIntegrationTests { jsonPath("$.membershipStatus") { value("PENDING") } } + val updatedProfile = pupilProfileRepository + .findById(requireNotNull(pupil.id)) + .orElseThrow() + + assertEquals( + classroomRepository.findByJoinCode("CLASS42")!!.id, + updatedProfile.currentClassroom?.id + ) + val membershipCount = jdbcTemplate.queryForObject( "SELECT COUNT(*) FROM classroom_pupils cp JOIN users u ON u.id = cp.pupil_user_id WHERE cp.classroom_id = (SELECT id FROM classrooms WHERE join_code = ?) AND u.username = ?", Int::class.java, @@ -163,28 +183,8 @@ class ClassroomIntegrationTests { @Test fun `teacher can list owned classrooms`() { val owner = userRepository.save(activeTeacher(email = "teacher-owned@example.com")) - classroomRepository.save( - Classroom( - ownerTeacherId = requireNotNull(owner.id), - title = "Alpha classroom", - description = "Owned by teacher", - joinCode = "OWN01", - status = ClassroomStatus.ACTIVE, - createdAt = Instant.now(), - updatedAt = Instant.now(), - ), - ) - classroomRepository.save( - Classroom( - ownerTeacherId = requireNotNull(owner.id), - title = "Beta classroom", - description = "Owned by teacher", - joinCode = "OWN02", - status = ClassroomStatus.ACTIVE, - createdAt = Instant.now(), - updatedAt = Instant.now(), - ), - ) + classroomRepository.save(Classroom(ownerTeacherId = requireNotNull(owner.id), title = "Alpha classroom", description = "Owned by teacher", joinCode = "OWN01", status = ClassroomStatus.ACTIVE, createdAt = Instant.now(), updatedAt = Instant.now(),),) + classroomRepository.save(Classroom(ownerTeacherId = requireNotNull(owner.id), title = "Beta classroom", description = "Owned by teacher", joinCode = "OWN02", status = ClassroomStatus.ACTIVE, createdAt = Instant.now(), updatedAt = Instant.now()),) createClassroom("OTHER1") val accessToken = teacherPortalAccessToken(email = "teacher-owned@example.com", password = "secret123") @@ -246,31 +246,35 @@ class ClassroomIntegrationTests { } @Test - fun `authenticated user can get all classrooms`() { - val classroom = createClassroom("LIST1") + fun `authenticated user can get classrooms`() { + val classroom = createClassroom("GET1") + val pupil = userRepository.save(activePupil(username = "getuser")) + pupilProfileRepository.save(PupilProfile(user = pupil, displayName = "get", currentClassroom = classroom)) + val token = pupilAccessToken(username = "getuser", password = "secret123") mockMvc.post("/api/v1/classrooms/join") { with(csrf()) contentType = MediaType.APPLICATION_JSON - header(HttpHeaders.AUTHORIZATION, "Bearer ${pupilAccessToken("listuser", "secret123")}") - content = """{"classroomCode":"LIST1"}""" + header(HttpHeaders.AUTHORIZATION, "Bearer $token") + content = """{"classroomCode":"GET1"}""" }.andExpect { status { isOk() } } mockMvc.get("/api/v1/classrooms") { - header(HttpHeaders.AUTHORIZATION, "Bearer ${pupilAccessToken("listuser", "secret123")}") + header(HttpHeaders.AUTHORIZATION, "Bearer $token") }.andExpect { status { isOk() } - jsonPath("$[0].id") { value(classroom.id.toInt()) } + jsonPath("$[0].classroomId") { value(classroom.id.toInt()) } + jsonPath("$[0].title") { value("Class GET1") } } } @Test fun `should get classroom by id`() { val classroom = createClassroom("DETAIL1") - val pupil = userRepository.save(activePupil(username = "detailuser")) + pupilProfileRepository.save(PupilProfile(user = pupil, displayName = "detail", currentClassroom = classroom)) val token = pupilAccessToken(username = "detailuser", password = "secret123") mockMvc.post("/api/v1/classrooms/join") { @@ -349,6 +353,8 @@ class ClassroomIntegrationTests { val pupil = userRepository.save(activePupil("approveuser")) val teacherToken = teacherPortalAccessToken("owner-APPROVE1@example.com", "secret123") + pupilProfileRepository.save(PupilProfile(user = pupil, displayName = "approve", currentClassroom = classroom)) + mockMvc.post("/api/v1/classrooms/join") { with(csrf()) contentType = MediaType.APPLICATION_JSON @@ -370,6 +376,8 @@ class ClassroomIntegrationTests { val pupil = userRepository.save(activePupil("removeuser")) + pupilProfileRepository.save(PupilProfile(user = pupil, displayName = "remove", currentClassroom = classroom)) + mockMvc.post("/api/v1/classrooms/join") { with(csrf()) contentType = MediaType.APPLICATION_JSON @@ -514,6 +522,7 @@ class ClassroomIntegrationTests { role = UserRole.ADMIN, status = UserStatus.ACTIVE, ) + } From 017869dd2d60bc8d4565125ea7946c015c18bd36 Mon Sep 17 00:00:00 2001 From: Maria <145002050+marikolafs@users.noreply.github.com> Date: Thu, 23 Apr 2026 09:49:19 +0200 Subject: [PATCH 06/25] let removepupil remove classroom from pupilprofile --- .../idatt/backend/classroom/application/ClassroomService.kt | 6 +++++- .../idi/idatt/backend/classroom/ClassroomServiceTest.kt | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/application/ClassroomService.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/application/ClassroomService.kt index 262f4889..10b51578 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/application/ClassroomService.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/application/ClassroomService.kt @@ -196,6 +196,11 @@ class ClassroomService( val classroom = classroomRepository.findById(classroomId).orElse(null) ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Classroom not found") + val profile = pupilProfileRepository.findById(pupilUserId) + .orElseThrow { ResponseStatusException(HttpStatus.NOT_FOUND, "Profile not found") } + profile.currentClassroom = null + pupilProfileRepository.save(profile) + try { classroom.removePupil(pupilUserId) } catch (e: IllegalArgumentException) { @@ -310,7 +315,6 @@ class ClassroomService( return normalizedCode } - /** * Generates a random uppercase join code for a classroom. * diff --git a/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomServiceTest.kt b/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomServiceTest.kt index 25f872dc..9084fda7 100644 --- a/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomServiceTest.kt +++ b/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomServiceTest.kt @@ -98,7 +98,7 @@ class ClassroomServiceTest { val user = User(id = 3, email = "test@example.com", username = "user", passwordHash = "password", role = UserRole.PUPIL, status = UserStatus.ACTIVE) - val pupilProfile = PupilProfile(userId = 3, user = user, displayName = "test", currentClassroom = null,) + val pupilProfile = PupilProfile(userId = 3, user = user, displayName = "test", currentClassroom = null) `when`(classroomRepository.findByJoinCode(classroomCode)).thenReturn(classroom) `when`(currentUserResolver.requirePupil("3")).thenReturn(user) @@ -297,12 +297,14 @@ class ClassroomServiceTest { val classroom = Classroom(id = 1, ownerTeacherId = 2, title = "class", description = "description", joinCode = "ABC123", status = ClassroomStatus.ACTIVE) val pupil = User(id = 3, email = null, username = "pupil", passwordHash = "password", role = UserRole.PUPIL, status = UserStatus.ACTIVE) val teacher = User(id = 2, email = "test@test.com", username = "teacher", passwordHash = "password", role = UserRole.TEACHER, status = UserStatus.ACTIVE ) + val pupilProfile = PupilProfile(userId = 3, user = pupil, displayName = "test", currentClassroom = null) classroom.addTeacher(teacher) classroom.addPupilMembership(pupil) `when`(classroomRepository.findById(1)).thenReturn(java.util.Optional.of(classroom)) `when`(currentUserResolver.requireTeacherOrAdmin("teacher")).thenReturn(teacher) + `when`(pupilProfileRepository.findById(3)).thenReturn(Optional.of(pupilProfile)) classroomService.removePupil(1, 3, "teacher") verify(classroomRepository).findById(1) From 1586cfeeafa2b25729b9a51c220f7af544a9a50f Mon Sep 17 00:00:00 2001 From: Maria <145002050+marikolafs@users.noreply.github.com> Date: Thu, 23 Apr 2026 10:32:45 +0200 Subject: [PATCH 07/25] change id to classroomId in response --- .../idi/idatt/backend/classroom/api/ClassroomApiMappings.kt | 2 +- .../idatt/backend/classroom/api/ClassroomDetailResponse.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomApiMappings.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomApiMappings.kt index 5a9b7d9d..92b8b36c 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomApiMappings.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomApiMappings.kt @@ -81,7 +81,7 @@ fun Classroom.toResponse(): ClassroomResponse = */ fun Classroom.toDetailResponse(): ClassroomDetailResponse = ClassroomDetailResponse( - id = id, + clasroomId = id, title = title, description = description, joinCode = joinCode ?: "", diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomDetailResponse.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomDetailResponse.kt index 34e2236f..964a28a4 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomDetailResponse.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomDetailResponse.kt @@ -3,7 +3,7 @@ package edu.ntnu.idi.idatt.backend.classroom.api /** * DTO for response to retrieving classroom details. * - * @param id the id of the classroom. + * @param classroomId the id of the classroom. * @param title the title of the classroom. * @param description the description of the classroom. * @param joinCode the code for joining the classroom. @@ -11,7 +11,7 @@ package edu.ntnu.idi.idatt.backend.classroom.api * @param pupils a list of the pupils that are members of the classroom. */ data class ClassroomDetailResponse( - val id: Long, + val clasroomId: Long, val title: String, val description: String?, val joinCode: String, From ed47243aa4fd9146dc433cb95130b288041e7d8a Mon Sep 17 00:00:00 2001 From: Maria <145002050+marikolafs@users.noreply.github.com> Date: Thu, 23 Apr 2026 10:35:34 +0200 Subject: [PATCH 08/25] spelling --- .../idi/idatt/backend/classroom/api/ClassroomApiMappings.kt | 2 +- .../idi/idatt/backend/classroom/api/ClassroomDetailResponse.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomApiMappings.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomApiMappings.kt index 92b8b36c..ae1086b2 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomApiMappings.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomApiMappings.kt @@ -81,7 +81,7 @@ fun Classroom.toResponse(): ClassroomResponse = */ fun Classroom.toDetailResponse(): ClassroomDetailResponse = ClassroomDetailResponse( - clasroomId = id, + classroomId = id, title = title, description = description, joinCode = joinCode ?: "", diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomDetailResponse.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomDetailResponse.kt index 964a28a4..3327d47b 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomDetailResponse.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomDetailResponse.kt @@ -11,7 +11,7 @@ package edu.ntnu.idi.idatt.backend.classroom.api * @param pupils a list of the pupils that are members of the classroom. */ data class ClassroomDetailResponse( - val clasroomId: Long, + val classroomId: Long, val title: String, val description: String?, val joinCode: String, From 619421f7fde3e694d96a294634d54c6c550af246 Mon Sep 17 00:00:00 2001 From: Maria <145002050+marikolafs@users.noreply.github.com> Date: Thu, 23 Apr 2026 10:50:30 +0200 Subject: [PATCH 09/25] update classroom response to send teacher emails --- .../idi/idatt/backend/classroom/api/ClassroomApiMappings.kt | 2 +- .../ntnu/idi/idatt/backend/classroom/api/ClassroomResponse.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomApiMappings.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomApiMappings.kt index ae1086b2..629af872 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomApiMappings.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomApiMappings.kt @@ -71,7 +71,7 @@ fun Classroom.toResponse(): ClassroomResponse = title = title, description = description, joinCode = joinCode ?: "", - teacherUserIds = teacherMemberships.mapNotNull { it.teacher?.id } + teacherEmails = teacherMemberships.mapNotNull { it.teacher?.email } ) /** diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomResponse.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomResponse.kt index 399cb5b0..43791aa0 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomResponse.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomResponse.kt @@ -7,12 +7,12 @@ package edu.ntnu.idi.idatt.backend.classroom.api * @param title the title of a classroom. * @param description the optional description of a classroom. * @param joinCode the code to join the classroom. - * @param teacherUserIds a list of ids of teahcers belonging to the classroom. + * @param teacherEmails a list of ids of teahcers belonging to the classroom by email. */ data class ClassroomResponse( val classroomId: Long, val title: String, val description: String?, val joinCode: String, - val teacherUserIds: List, + val teacherEmails: List, ) \ No newline at end of file From b4e69888537a119491694c5ad4f49e8ad574ad93 Mon Sep 17 00:00:00 2001 From: Maria <145002050+marikolafs@users.noreply.github.com> Date: Thu, 23 Apr 2026 11:31:09 +0200 Subject: [PATCH 10/25] update classroom detail response to send pupil status --- .../idi/idatt/backend/classroom/api/ClassroomApiMappings.kt | 1 + .../idi/idatt/backend/classroom/api/ClassroomPupilResponse.kt | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomApiMappings.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomApiMappings.kt index 629af872..0bfa93a2 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomApiMappings.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomApiMappings.kt @@ -101,6 +101,7 @@ fun Classroom.toDetailResponse(): ClassroomDetailResponse = ClassroomPupilResponse( userId = pupil.id ?: return@mapNotNull null, displayName = pupil.username ?: "Unknown", + pupilStatus = membership.status, ) } ) diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomPupilResponse.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomPupilResponse.kt index bf32893e..e41bed6c 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomPupilResponse.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomPupilResponse.kt @@ -1,12 +1,16 @@ package edu.ntnu.idi.idatt.backend.classroom.api +import edu.ntnu.idi.idatt.backend.classroom.domain.ClassroomPupilStatus + /** * DTO for response to retrieving pupil information for a classroom. * * @param userId the id of a pupil in the classroom. * @param displayName the name of a pupil in the classroom. + * @param pupilStatus the pupils pending/approved status. */ data class ClassroomPupilResponse( val userId: Long, val displayName: String, + val pupilStatus: ClassroomPupilStatus, ) \ No newline at end of file From 786dc426be44044efd1d55cbe929c2f436caa129 Mon Sep 17 00:00:00 2001 From: Maria <145002050+marikolafs@users.noreply.github.com> Date: Fri, 24 Apr 2026 14:34:01 +0200 Subject: [PATCH 11/25] Add membership status to pupil profile --- .../classroom/api/ClassroomApiMappings.kt | 9 ++++++++- .../classroom/api/ClassroomDetailResponse.kt | 3 +++ .../classroom/api/ClassroomResponse.kt | 3 +++ .../api/CurrentUserProfileApiMappings.kt | 2 ++ .../profile/api/CurrentUserProfileResponse.kt | 1 + .../application/CurrentUserProfileService.kt | 19 +++++++++++++++++++ .../infrastructure/PupilProfileRepository.kt | 1 + 7 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomApiMappings.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomApiMappings.kt index 0bfa93a2..4a9b67a8 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomApiMappings.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomApiMappings.kt @@ -6,6 +6,7 @@ import edu.ntnu.idi.idatt.backend.classroom.application.JoinClassroomResult import edu.ntnu.idi.idatt.backend.classroom.application.UpdateClassroomCommand import edu.ntnu.idi.idatt.backend.classroom.domain.Classroom import edu.ntnu.idi.idatt.backend.classroom.application.OwnedClassroomSummary +import edu.ntnu.idi.idatt.backend.classroom.domain.ClassroomPupilStatus /** * Converts the join classroom request to the application command. @@ -71,7 +72,10 @@ fun Classroom.toResponse(): ClassroomResponse = title = title, description = description, joinCode = joinCode ?: "", - teacherEmails = teacherMemberships.mapNotNull { it.teacher?.email } + teacherEmails = teacherMemberships.mapNotNull { it.teacher?.email }, + pupilCount = pupilMemberships.count { it.status == ClassroomPupilStatus.ACTIVE }, + pendingPupilCount = pupilMemberships.count { it.status == ClassroomPupilStatus.PENDING }, + teacherCount = teacherMemberships.size, ) /** @@ -85,6 +89,9 @@ fun Classroom.toDetailResponse(): ClassroomDetailResponse = title = title, description = description, joinCode = joinCode ?: "", + pupilCount = pupilMemberships.count { it.status == ClassroomPupilStatus.ACTIVE }, + pendingPupilCount = pupilMemberships.count { it.status == ClassroomPupilStatus.PENDING }, + teacherCount = teacherMemberships.size, teachers = teacherMemberships.mapNotNull { membership -> val teacher = membership.teacher ?: return@mapNotNull null diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomDetailResponse.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomDetailResponse.kt index 3327d47b..a4af27d8 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomDetailResponse.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomDetailResponse.kt @@ -15,6 +15,9 @@ data class ClassroomDetailResponse( val title: String, val description: String?, val joinCode: String, + val pupilCount: Int, + val pendingPupilCount: Int, + val teacherCount: Int, val teachers: List, val pupils: List ) \ No newline at end of file diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomResponse.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomResponse.kt index 43791aa0..71b9d902 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomResponse.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomResponse.kt @@ -15,4 +15,7 @@ data class ClassroomResponse( val description: String?, val joinCode: String, val teacherEmails: List, + val pupilCount: Int, + val pendingPupilCount: Int, + val teacherCount: Int, ) \ No newline at end of file diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/api/CurrentUserProfileApiMappings.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/api/CurrentUserProfileApiMappings.kt index d6854a51..f9952f4a 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/api/CurrentUserProfileApiMappings.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/api/CurrentUserProfileApiMappings.kt @@ -19,6 +19,7 @@ fun CurrentUserProfile.toCurrentUserProfileResponse(): CurrentUserProfileRespons PupilProfileResponse( displayName = it.displayName, currentClassroomId = it.currentClassroomId, + membershipStatus = it.membershipStatus, totalXp = it.totalXp, currentLevel = it.currentLevel, ) @@ -40,6 +41,7 @@ fun PupilProfileDetails.toPupilProfileResponse(): PupilProfileResponse = PupilProfileResponse( displayName = displayName, currentClassroomId = currentClassroomId, + membershipStatus = membershipStatus, totalXp = totalXp, currentLevel = currentLevel, ) diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/api/CurrentUserProfileResponse.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/api/CurrentUserProfileResponse.kt index 9d4f908c..6c0beb0c 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/api/CurrentUserProfileResponse.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/api/CurrentUserProfileResponse.kt @@ -32,6 +32,7 @@ data class CurrentUserProfileResponse( data class PupilProfileResponse( val displayName: String, val currentClassroomId: Long?, + val membershipStatus: String? = null, val totalXp: Int, val currentLevel: Int, ) diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt index f768e120..313a892b 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt @@ -11,6 +11,7 @@ import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import org.springframework.web.server.ResponseStatusException + /** * Handles lookup of the authenticated user's profile data. * @@ -41,10 +42,12 @@ class CurrentUserProfileService( pupilProfileRepository.findById(userId) .map { val progress = getCurrentClassroomProgress(userId, it.currentClassroom?.id) + val membership = findMembership(userId, it.currentClassroom?.id) PupilProfileDetails( displayName = it.displayName, currentClassroomId = it.currentClassroom?.id, + membershipStatus = membership?.status?.name, totalXp = progress?.totalXp ?: 0, currentLevel = progress?.currentLevel ?: 1, ) @@ -100,9 +103,14 @@ class CurrentUserProfileService( pupilProfile.displayName = command.displayName.trim() val savedProfile = pupilProfileRepository.save(pupilProfile) + val membership = savedProfile.currentClassroom?.pupilMemberships + ?.firstOrNull { it.pupil?.id == userId } + + return PupilProfileDetails( displayName = savedProfile.displayName, currentClassroomId = savedProfile.currentClassroom?.id, + membershipStatus = membership?.status?.name, totalXp = progress?.totalXp ?: 0, currentLevel = progress?.currentLevel ?: 1, ) @@ -122,6 +130,15 @@ class CurrentUserProfileService( private fun getCurrentClassroomProgress(userId: Long, classroomId: Long?) = classroomId?.let { pupilGameProgressRepository.findByPupilIdAndClassroomId(userId, it) } + + private fun findMembership(userId: Long, classroomId: Long?) = + classroomId?.let { id -> + pupilProfileRepository.findById(userId) + .orElse(null) + ?.currentClassroom + ?.pupilMemberships + ?.firstOrNull { it.pupil?.id == userId } + } } /** @@ -151,6 +168,7 @@ data class PupilProfileDetails( val currentClassroomId: Long?, val totalXp: Int, val currentLevel: Int, + val membershipStatus: String?, ) /** @@ -160,3 +178,4 @@ data class TeacherProfileDetails( val teacherName: String, val schoolName: String, ) + diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/infrastructure/PupilProfileRepository.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/infrastructure/PupilProfileRepository.kt index 9befd76c..cf7aa6ed 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/infrastructure/PupilProfileRepository.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/infrastructure/PupilProfileRepository.kt @@ -7,6 +7,7 @@ import org.springframework.data.jpa.repository.JpaRepository * Repository for pupil profile persistence. */ interface PupilProfileRepository : JpaRepository { + /** * Finds all pupil profiles for the given user ids. */ From a257152db10eb65954f63fc57aeccfa0ff30b8fd Mon Sep 17 00:00:00 2001 From: Maria <145002050+marikolafs@users.noreply.github.com> Date: Mon, 27 Apr 2026 12:15:55 +0200 Subject: [PATCH 12/25] update imports --- .../users/profile/api/CurrentUserProfileApiMappings.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/api/CurrentUserProfileApiMappings.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/api/CurrentUserProfileApiMappings.kt index f9952f4a..3acf68ae 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/api/CurrentUserProfileApiMappings.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/api/CurrentUserProfileApiMappings.kt @@ -1,7 +1,7 @@ package edu.ntnu.idi.idatt.backend.users.profile.api -import edu.ntnu.idi.idatt.backend.users.profile.application.CurrentUserProfile -import edu.ntnu.idi.idatt.backend.users.profile.application.PupilProfileDetails +import edu.ntnu.idi.idatt.backend.users.profile.application.CurrentUserProfileService.CurrentUserProfile +import edu.ntnu.idi.idatt.backend.users.profile.application.CurrentUserProfileService.PupilProfileDetails import edu.ntnu.idi.idatt.backend.users.profile.application.UpdateCurrentUserDisplayNameCommand /** From 4664c4ccca11eabb7764023f25c3e58559cb9700 Mon Sep 17 00:00:00 2001 From: Maria <145002050+marikolafs@users.noreply.github.com> Date: Tue, 28 Apr 2026 10:53:19 +0200 Subject: [PATCH 13/25] move adding classroom to pupil profile to happen only when pupil has been approved --- .../classroom/application/ClassroomService.kt | 11 ++++--- .../infrastructure/ClassroomRepository.kt | 9 ++++++ .../application/CurrentUserProfileService.kt | 30 +++++++++++++------ 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/application/ClassroomService.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/application/ClassroomService.kt index 33af6375..63a49762 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/application/ClassroomService.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/application/ClassroomService.kt @@ -125,10 +125,7 @@ class ClassroomService( } classroom.addPupilMembership(user) - val profile = pupilProfileRepository.findById(userId) - .orElseThrow { ResponseStatusException(HttpStatus.NOT_FOUND, "Profile not found") } - profile.currentClassroom = classroom - pupilProfileRepository.save(profile) + classroomRepository.save(classroom) pupilGameProgressService.createPupilGameProgressEntry( pupilId = userId, @@ -167,6 +164,12 @@ class ClassroomService( throw ResponseStatusException(HttpStatus.NOT_FOUND, "Pupil not found in classroom") } + val profile = pupilProfileRepository.findById(pupilUserId) + .orElseThrow { ResponseStatusException(HttpStatus.NOT_FOUND, "Profile not found") } + + profile.currentClassroom = classroom + pupilProfileRepository.save(profile) + classroomRepository.save(classroom) } diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/infrastructure/ClassroomRepository.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/infrastructure/ClassroomRepository.kt index 9897c162..9783565e 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/infrastructure/ClassroomRepository.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/infrastructure/ClassroomRepository.kt @@ -1,6 +1,7 @@ package edu.ntnu.idi.idatt.backend.classroom.infrastructure import edu.ntnu.idi.idatt.backend.classroom.domain.Classroom +import edu.ntnu.idi.idatt.backend.classroom.domain.ClassroomPupilMembership import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.jpa.repository.Query import org.springframework.data.repository.query.Param @@ -67,4 +68,12 @@ interface ClassroomRepository : JpaRepository { nativeQuery = true, ) fun findAllWithAverageTotalScore(): List + + /** + * Returns classroom memberships for a pupil based on the pupils id. + * + * @param userId the id of the pupil being checked. + * @return a list of classrooms the pupil is a member of. + */ + fun findAllByPupilMemberships_Pupil_Id(userId: Long): List } diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt index b63330ec..d5f5bfb9 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt @@ -1,5 +1,8 @@ package edu.ntnu.idi.idatt.backend.users.profile.application +import edu.ntnu.idi.idatt.backend.classroom.domain.ClassroomPupilMembership +import edu.ntnu.idi.idatt.backend.classroom.domain.ClassroomPupilStatus +import edu.ntnu.idi.idatt.backend.classroom.infrastructure.ClassroomRepository import edu.ntnu.idi.idatt.backend.game.gameProgress.application.PupilGameProgressService import edu.ntnu.idi.idatt.backend.iam.application.CurrentUserResolver import edu.ntnu.idi.idatt.backend.iam.domain.UserRole @@ -25,6 +28,7 @@ class CurrentUserProfileService( private val pupilProfileRepository: PupilProfileRepository, private val pupilGameProgressService: PupilGameProgressService, private val teacherProfileRepository: TeacherProfileRepository, + private val classroomRepository: ClassroomRepository ) { /** * Returns the current authenticated user's profile. @@ -41,7 +45,7 @@ class CurrentUserProfileService( pupilProfileRepository.findById(userId) .map { val progress = getCurrentClassroomProgress(userId, it.currentClassroom?.id) - val membership = findMembership(userId, it.currentClassroom?.id) + val membership = findMembership(userId) PupilProfileDetails( displayName = it.displayName, @@ -137,14 +141,22 @@ class CurrentUserProfileService( private fun getCurrentClassroomProgress(userId: Long, classroomId: Long?) = classroomId?.let { pupilGameProgressService.getPupilProgress(userId, it) } - private fun findMembership(userId: Long, classroomId: Long?) = - classroomId?.let { id -> - pupilProfileRepository.findById(userId) - .orElse(null) - ?.currentClassroom - ?.pupilMemberships - ?.firstOrNull { it.pupil?.id == userId } - } + /** + * Finds pupil memberships based on the pupils id. + * Memberships are prioritized by membership status so that if a pupil has an active membership in a classroom that membership will be prioritized over one where the pupil is still pending. + * + * @param userId persisted pupil user id + * @return pupil membership, or `null`when no membership exists + */ + private fun findMembership(userId: Long): ClassroomPupilMembership? { + val memberships = classroomRepository + .findAllByPupilMemberships_Pupil_Id(userId) + .flatMap { it.pupilMemberships } + .filter { it.pupil?.id == userId } + + return memberships.firstOrNull { it.status == ClassroomPupilStatus.ACTIVE } + ?: memberships.firstOrNull { it.status == ClassroomPupilStatus.PENDING } + } /** * Internal representation of the authenticated user's profile. From 1bf4a2d546327276f203995a6f5b6c97b30b66b1 Mon Sep 17 00:00:00 2001 From: Maria <145002050+marikolafs@users.noreply.github.com> Date: Tue, 28 Apr 2026 13:21:29 +0200 Subject: [PATCH 14/25] update function name --- .../backend/classroom/infrastructure/ClassroomRepository.kt | 3 +-- .../users/profile/application/CurrentUserProfileService.kt | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/infrastructure/ClassroomRepository.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/infrastructure/ClassroomRepository.kt index a84c58c0..4e44ad14 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/infrastructure/ClassroomRepository.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/infrastructure/ClassroomRepository.kt @@ -1,7 +1,6 @@ package edu.ntnu.idi.idatt.backend.classroom.infrastructure import edu.ntnu.idi.idatt.backend.classroom.domain.Classroom -import edu.ntnu.idi.idatt.backend.classroom.domain.ClassroomPupilMembership import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.jpa.repository.Query import org.springframework.data.repository.query.Param @@ -76,5 +75,5 @@ interface ClassroomRepository : JpaRepository { * @param userId the id of the pupil being checked. * @return a list of classrooms the pupil is a member of. */ - fun findAllByPupilMemberships_Pupil_Id(userId: Long): List + fun findAllByPupilMembershipsPupilId(userId: Long): List } diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt index 3bdeaed1..744aa25f 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt @@ -181,7 +181,7 @@ class CurrentUserProfileService( */ private fun findMembership(userId: Long): ClassroomPupilMembership? { val memberships = classroomRepository - .findAllByPupilMemberships_Pupil_Id(userId) + .findAllByPupilMembershipsPupilId(userId) .flatMap { it.pupilMemberships } .filter { it.pupil?.id == userId } return memberships.firstOrNull { it.status == ClassroomPupilStatus.ACTIVE } From d8e41a1cb92de1d31db6987e693886e19bb6d73d Mon Sep 17 00:00:00 2001 From: Maria <145002050+marikolafs@users.noreply.github.com> Date: Tue, 28 Apr 2026 13:31:06 +0200 Subject: [PATCH 15/25] fix merge breaks --- .../idi/idatt/backend/classroom/api/ClassroomApiMappings.kt | 1 - .../users/profile/api/CurrentUserProfileApiMappings.kt | 1 + .../users/profile/application/CurrentUserProfileService.kt | 4 +++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomApiMappings.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomApiMappings.kt index 839308ee..a494aae6 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomApiMappings.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomApiMappings.kt @@ -6,7 +6,6 @@ import edu.ntnu.idi.idatt.backend.classroom.application.JoinClassroomResult import edu.ntnu.idi.idatt.backend.classroom.application.OwnedClassroomSummary import edu.ntnu.idi.idatt.backend.classroom.application.UpdateClassroomCommand import edu.ntnu.idi.idatt.backend.classroom.domain.Classroom -import edu.ntnu.idi.idatt.backend.classroom.application.OwnedClassroomSummary import edu.ntnu.idi.idatt.backend.classroom.domain.ClassroomPupilStatus /** diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/api/CurrentUserProfileApiMappings.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/api/CurrentUserProfileApiMappings.kt index 15c19731..be3a9121 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/api/CurrentUserProfileApiMappings.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/api/CurrentUserProfileApiMappings.kt @@ -22,6 +22,7 @@ fun CurrentUserProfile.toCurrentUserProfileResponse(): CurrentUserProfileRespons membershipStatus = it.membershipStatus, totalXp = it.totalXp, currentLevel = it.currentLevel, + hasCompletedOnboarding = it.hasCompletedOnboarding, ) }, teacherProfile = teacherProfile?.let { diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt index 744aa25f..ebf5f7a7 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt @@ -4,6 +4,7 @@ import edu.ntnu.idi.idatt.backend.classroom.domain.ClassroomPupilMembership import edu.ntnu.idi.idatt.backend.classroom.domain.ClassroomPupilStatus import edu.ntnu.idi.idatt.backend.classroom.infrastructure.ClassroomRepository import edu.ntnu.idi.idatt.backend.game.gameProgress.application.PupilGameProgressService +import edu.ntnu.idi.idatt.backend.game.gameProgress.domain.PupilGameProgress import edu.ntnu.idi.idatt.backend.iam.application.CurrentUserResolver import edu.ntnu.idi.idatt.backend.iam.domain.UserRole import edu.ntnu.idi.idatt.backend.users.profile.domain.PupilProfile @@ -134,6 +135,7 @@ class CurrentUserProfileService( val userId = requireNotNull(user.id) { "Authenticated user is missing an id" } val pupilProfile = findPupilProfile(userId) val progress = getCurrentClassroomProgress(userId, pupilProfile.currentClassroom?.id) + val membership = findMembership(userId) pupilProfile.hasCompletedOnboarding = true val savedProfile = pupilProfileRepository.save(pupilProfile) @@ -142,6 +144,7 @@ class CurrentUserProfileService( currentClassroomId = savedProfile.currentClassroom?.id, totalXp = progress?.totalXp ?: 0, currentLevel = progress?.currentLevel ?: 1, + membershipStatus = membership?.status?.name, hasCompletedOnboarding = savedProfile.hasCompletedOnboarding, ) } @@ -170,7 +173,6 @@ class CurrentUserProfileService( userId: Long, classroomId: Long?, ): PupilGameProgress? = classroomId?.let { pupilGameProgressService.getPupilProgress(userId, it) } -} /** * Finds pupil memberships based on the pupils id. From 02b22ce7b2fd120455c2676b97390cd523e24f51 Mon Sep 17 00:00:00 2001 From: Maria <145002050+marikolafs@users.noreply.github.com> Date: Tue, 28 Apr 2026 13:44:34 +0200 Subject: [PATCH 16/25] update tests --- .../classroom/ClassroomIntegrationTests.kt | 33 ++++++++++--------- .../backend/classroom/ClassroomServiceTest.kt | 10 ++---- .../profile/CurrentUserProfileServiceTests.kt | 25 ++++++++++++++ 3 files changed, 45 insertions(+), 23 deletions(-) diff --git a/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomIntegrationTests.kt b/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomIntegrationTests.kt index aa1f966f..db917875 100644 --- a/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomIntegrationTests.kt +++ b/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomIntegrationTests.kt @@ -285,24 +285,25 @@ class ClassroomIntegrationTests { } @Test - fun `should get classroom by id`() { - val classroom = createClassroom("DETAIL1") - val pupil = userRepository.save(activePupil(username = "detailuser")) - pupilProfileRepository.save(PupilProfile(user = pupil, displayName = "detail", currentClassroom = classroom)) - val token = pupilAccessToken(username = "detailuser", password = "secret123") - - mockMvc - .post("/api/v1/classrooms/join") { - with(csrf()) - contentType = MediaType.APPLICATION_JSON - header(HttpHeaders.AUTHORIZATION, "Bearer $token") - content = """{"classroomCode":"DETAIL1"}""" - }.andExpect { - status { isOk() } - } + fun `teacher should get owned classroom by id`() { + val teacher = userRepository.save(activeTeacher(email = "teacher-detail@example.com")) + val token = teacherPortalAccessToken("teacher-detail@example.com", "secret123") + + val classroom = + classroomRepository.save( + Classroom( + ownerTeacherId = requireNotNull(teacher.id), + title = "Class DETAIL1", + description = "Integration test classroom", + joinCode = "DETAIL1", + status = ClassroomStatus.ACTIVE, + createdAt = Instant.now(), + updatedAt = Instant.now(), + ), + ) mockMvc - .get("/api/v1/classrooms/${classroom.id}") { + .get("/api/v1/classrooms/owned/${classroom.id}") { header(HttpHeaders.AUTHORIZATION, "Bearer $token") }.andExpect { status { isOk() } diff --git a/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomServiceTest.kt b/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomServiceTest.kt index 61ad21f2..6769bded 100644 --- a/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomServiceTest.kt +++ b/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomServiceTest.kt @@ -114,8 +114,6 @@ class ClassroomServiceTest { assertEquals("PENDING", result.membershipStatus) verify(classroomRepository).save(classroom) - verify(pupilProfileRepository).save(pupilProfile) - assertEquals(classroom, pupilProfile.currentClassroom) verify(pupilGameProgressService).createPupilGameProgressEntry(3L, 1L) verify(classroomRepository).findByJoinCode(classroomCode) } @@ -250,19 +248,14 @@ class ClassroomServiceTest { val user = User(id = 3, email = "test@example.com", username = "user", passwordHash = "password", role = UserRole.PUPIL, status = UserStatus.ACTIVE) - val pupilProfile = PupilProfile(userId = 3, user = user, displayName = "test", currentClassroom = null) - `when`(classroomRepository.findByJoinCode("ABC123")).thenReturn(classroom) `when`(currentUserResolver.requirePupil("3")).thenReturn(user) - `when`(pupilProfileRepository.findById(3)).thenReturn(Optional.of(pupilProfile)) classroomService.joinClassroom( JoinClassroomCommand(classroomCode = " abc123 ", authenticatedSubject = "3"), ) verify(classroomRepository).findByJoinCode("ABC123") - verify(pupilProfileRepository).save(pupilProfile) - assertEquals(classroom, pupilProfile.currentClassroom) } @Test @@ -270,12 +263,15 @@ class ClassroomServiceTest { val classroom = Classroom(id = 1, ownerTeacherId = 2, title = "class", description = "description", joinCode = "ABC123", status = ClassroomStatus.ACTIVE) val pupil = User(id = 3, email = null, username = "pupil", passwordHash = "password", role = UserRole.PUPIL, status = UserStatus.ACTIVE) val teacher = User(id = 2, email = "test@test.com", username = "teacher", passwordHash = "password", role = UserRole.TEACHER, status = UserStatus.ACTIVE) + val pupilProfile = PupilProfile(userId = 3, user = pupil, displayName = "test", currentClassroom = null) classroom.addTeacher(teacher) classroom.addPupilMembership(pupil) `when`(classroomRepository.findById(1)).thenReturn(Optional.of(classroom)) + `when`(pupilProfileRepository.findById(3)).thenReturn(Optional.of(pupilProfile)) + `when`(currentUserResolver.requireTeacherOrAdmin("teacher")).thenReturn(teacher) classroomService.approvePupil(1, 3, "teacher") diff --git a/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/CurrentUserProfileServiceTests.kt b/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/CurrentUserProfileServiceTests.kt index a43eb161..5a06d413 100644 --- a/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/CurrentUserProfileServiceTests.kt +++ b/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/CurrentUserProfileServiceTests.kt @@ -13,6 +13,7 @@ import edu.ntnu.idi.idatt.backend.users.profile.domain.PupilProfile import edu.ntnu.idi.idatt.backend.users.profile.domain.TeacherProfile import edu.ntnu.idi.idatt.backend.users.profile.infrastructure.PupilProfileRepository import edu.ntnu.idi.idatt.backend.users.profile.infrastructure.TeacherProfileRepository +import edu.ntnu.idi.idatt.backend.classroom.infrastructure.ClassroomRepository import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Test @@ -28,6 +29,7 @@ class CurrentUserProfileServiceTests { val pupilProfileRepository = Mockito.mock(PupilProfileRepository::class.java) val pupilGameProgressService = Mockito.mock(PupilGameProgressService::class.java) val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) + val classroomRepository = Mockito.mock(ClassroomRepository::class.java) val classroom = Classroom(id = 101L, ownerTeacherId = 7L, title = "Class A") val service = CurrentUserProfileService( @@ -35,6 +37,7 @@ class CurrentUserProfileServiceTests { pupilProfileRepository, pupilGameProgressService, teacherProfileRepository, + classroomRepository, ) val user = pupilUser(42L) val pupilProfile = @@ -72,12 +75,14 @@ class CurrentUserProfileServiceTests { val pupilProfileRepository = Mockito.mock(PupilProfileRepository::class.java) val pupilGameProgressService = Mockito.mock(PupilGameProgressService::class.java) val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) + val classroomRepository = Mockito.mock(ClassroomRepository::class.java) val service = CurrentUserProfileService( currentUserResolver, pupilProfileRepository, pupilGameProgressService, teacherProfileRepository, + classroomRepository, ) val user = pupilUser(42L) val pupilProfile = @@ -105,6 +110,7 @@ class CurrentUserProfileServiceTests { val pupilProfileRepository = Mockito.mock(PupilProfileRepository::class.java) val pupilGameProgressService = Mockito.mock(PupilGameProgressService::class.java) val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) + val classroomRepository = Mockito.mock(ClassroomRepository::class.java) val classroom = Classroom(id = 101L, ownerTeacherId = 7L, title = "Class A") val service = CurrentUserProfileService( @@ -112,6 +118,7 @@ class CurrentUserProfileServiceTests { pupilProfileRepository, pupilGameProgressService, teacherProfileRepository, + classroomRepository, ) val user = pupilUser(42L) val pupilProfile = @@ -139,12 +146,14 @@ class CurrentUserProfileServiceTests { val pupilProfileRepository = Mockito.mock(PupilProfileRepository::class.java) val pupilGameProgressService = Mockito.mock(PupilGameProgressService::class.java) val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) + val classroomRepository = Mockito.mock(ClassroomRepository::class.java) val service = CurrentUserProfileService( currentUserResolver, pupilProfileRepository, pupilGameProgressService, teacherProfileRepository, + classroomRepository, ) val user = teacherUser(7L) val teacherProfile = @@ -176,12 +185,14 @@ class CurrentUserProfileServiceTests { val pupilProfileRepository = Mockito.mock(PupilProfileRepository::class.java) val pupilGameProgressService = Mockito.mock(PupilGameProgressService::class.java) val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) + val classroomRepository = Mockito.mock(ClassroomRepository::class.java) val service = CurrentUserProfileService( currentUserResolver, pupilProfileRepository, pupilGameProgressService, teacherProfileRepository, + classroomRepository, ) val exception = ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid authenticated user subject") @@ -201,12 +212,14 @@ class CurrentUserProfileServiceTests { val pupilProfileRepository = Mockito.mock(PupilProfileRepository::class.java) val pupilGameProgressService = Mockito.mock(PupilGameProgressService::class.java) val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) + val classroomRepository = Mockito.mock(ClassroomRepository::class.java) val service = CurrentUserProfileService( currentUserResolver, pupilProfileRepository, pupilGameProgressService, teacherProfileRepository, + classroomRepository, ) val exception = ResponseStatusException(HttpStatus.UNAUTHORIZED, "Authenticated user was not found") @@ -226,12 +239,14 @@ class CurrentUserProfileServiceTests { val pupilProfileRepository = Mockito.mock(PupilProfileRepository::class.java) val pupilGameProgressService = Mockito.mock(PupilGameProgressService::class.java) val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) + val classroomRepository = Mockito.mock(ClassroomRepository::class.java) val service = CurrentUserProfileService( currentUserResolver, pupilProfileRepository, pupilGameProgressService, teacherProfileRepository, + classroomRepository ) val user = pupilUser(42L) @@ -252,12 +267,14 @@ class CurrentUserProfileServiceTests { val pupilProfileRepository = Mockito.mock(PupilProfileRepository::class.java) val pupilGameProgressService = Mockito.mock(PupilGameProgressService::class.java) val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) + val classroomRepository = Mockito.mock(ClassroomRepository::class.java) val service = CurrentUserProfileService( currentUserResolver, pupilProfileRepository, pupilGameProgressService, teacherProfileRepository, + classroomRepository ) val user = teacherUser(7L) @@ -278,6 +295,7 @@ class CurrentUserProfileServiceTests { val pupilProfileRepository = Mockito.mock(PupilProfileRepository::class.java) val pupilGameProgressService = Mockito.mock(PupilGameProgressService::class.java) val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) + val classroomRepository = Mockito.mock(ClassroomRepository::class.java) val classroom = Classroom(id = 101L, ownerTeacherId = 7L, title = "Class A") val service = CurrentUserProfileService( @@ -285,6 +303,7 @@ class CurrentUserProfileServiceTests { pupilProfileRepository, pupilGameProgressService, teacherProfileRepository, + classroomRepository, ) val user = pupilUser(42L) val pupilProfile = @@ -325,6 +344,7 @@ class CurrentUserProfileServiceTests { val pupilProfileRepository = Mockito.mock(PupilProfileRepository::class.java) val pupilGameProgressService = Mockito.mock(PupilGameProgressService::class.java) val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) + val classroomRepository = Mockito.mock(ClassroomRepository::class.java) val classroom = Classroom(id = 101L, ownerTeacherId = 7L, title = "Class A") val service = CurrentUserProfileService( @@ -332,6 +352,7 @@ class CurrentUserProfileServiceTests { pupilProfileRepository, pupilGameProgressService, teacherProfileRepository, + classroomRepository, ) val user = pupilUser(42L) val pupilProfile = @@ -368,12 +389,14 @@ class CurrentUserProfileServiceTests { val pupilProfileRepository = Mockito.mock(PupilProfileRepository::class.java) val pupilGameProgressService = Mockito.mock(PupilGameProgressService::class.java) val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) + val classroomRepository = Mockito.mock(ClassroomRepository::class.java) val service = CurrentUserProfileService( currentUserResolver, pupilProfileRepository, pupilGameProgressService, teacherProfileRepository, + classroomRepository ) val exception = ResponseStatusException(HttpStatus.FORBIDDEN, "Only pupils are allowed") @@ -396,12 +419,14 @@ class CurrentUserProfileServiceTests { val pupilProfileRepository = Mockito.mock(PupilProfileRepository::class.java) val pupilGameProgressService = Mockito.mock(PupilGameProgressService::class.java) val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) + val classroomRepository = Mockito.mock(ClassroomRepository::class.java) val service = CurrentUserProfileService( currentUserResolver, pupilProfileRepository, pupilGameProgressService, teacherProfileRepository, + classroomRepository ) val user = pupilUser(42L) From 160a380b4a64cfe64b7c39fd8cdaa19096efba69 Mon Sep 17 00:00:00 2001 From: Maria <145002050+marikolafs@users.noreply.github.com> Date: Tue, 28 Apr 2026 13:54:02 +0200 Subject: [PATCH 17/25] spotlessApply --- .../classroom/api/ClassroomApiMappings.kt | 12 ++-- .../classroom/application/ClassroomService.kt | 12 ++-- .../api/CurrentUserProfileApiMappings.kt | 34 ++++++----- .../profile/api/CurrentUserProfileResponse.kt | 2 +- .../application/CurrentUserProfileService.kt | 61 ++++++++++--------- .../infrastructure/PupilProfileRepository.kt | 1 - .../backend/classroom/ClassroomServiceTest.kt | 4 +- ...NotebookEntryControllerIntegrationTests.kt | 42 +++++++------ .../profile/CurrentUserProfileServiceTests.kt | 14 ++--- 9 files changed, 96 insertions(+), 86 deletions(-) diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomApiMappings.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomApiMappings.kt index a494aae6..0777098a 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomApiMappings.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomApiMappings.kt @@ -106,12 +106,12 @@ fun Classroom.toDetailResponse(pupilDisplayNames: Map = emptyMap() val pupil = membership.pupil ?: return@mapNotNull null val pupilId = pupil.id ?: return@mapNotNull null - ClassroomPupilResponse( - userId = pupilId, - displayName = pupilDisplayNames[pupilId] ?: pupil.username ?: "Unknown", - pupilStatus = membership.status, - ) - } + ClassroomPupilResponse( + userId = pupilId, + displayName = pupilDisplayNames[pupilId] ?: pupil.username ?: "Unknown", + pupilStatus = membership.status, + ) + }, ) fun OwnedClassroomSummary.toOwnedClassroomResponse(): OwnedClassroomResponse = diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/application/ClassroomService.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/application/ClassroomService.kt index d59e0c17..8bdfad9b 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/application/ClassroomService.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/application/ClassroomService.kt @@ -171,8 +171,10 @@ class ClassroomService( throw ResponseStatusException(HttpStatus.NOT_FOUND, "Pupil not found in classroom") } - val profile = pupilProfileRepository.findById(pupilUserId) - .orElseThrow { ResponseStatusException(HttpStatus.NOT_FOUND, "Profile not found") } + val profile = + pupilProfileRepository + .findById(pupilUserId) + .orElseThrow { ResponseStatusException(HttpStatus.NOT_FOUND, "Profile not found") } profile.currentClassroom = classroom pupilProfileRepository.save(profile) @@ -201,8 +203,10 @@ class ClassroomService( classroomRepository.findById(classroomId).orElse(null) ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Classroom not found") - val profile = pupilProfileRepository.findById(pupilUserId) - .orElseThrow { ResponseStatusException(HttpStatus.NOT_FOUND, "Profile not found") } + val profile = + pupilProfileRepository + .findById(pupilUserId) + .orElseThrow { ResponseStatusException(HttpStatus.NOT_FOUND, "Profile not found") } profile.currentClassroom = null pupilProfileRepository.save(profile) diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/api/CurrentUserProfileApiMappings.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/api/CurrentUserProfileApiMappings.kt index be3a9121..331b6c05 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/api/CurrentUserProfileApiMappings.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/api/CurrentUserProfileApiMappings.kt @@ -15,22 +15,24 @@ fun CurrentUserProfile.toCurrentUserProfileResponse(): CurrentUserProfileRespons role = role, email = email, username = username, - pupilProfile = pupilProfile?.let { - PupilProfileResponse( - displayName = it.displayName, - currentClassroomId = it.currentClassroomId, - membershipStatus = it.membershipStatus, - totalXp = it.totalXp, - currentLevel = it.currentLevel, - hasCompletedOnboarding = it.hasCompletedOnboarding, - ) - }, - teacherProfile = teacherProfile?.let { - TeacherProfileResponse( - teacherName = it.teacherName, - schoolName = it.schoolName, - ) - }, + pupilProfile = + pupilProfile?.let { + PupilProfileResponse( + displayName = it.displayName, + currentClassroomId = it.currentClassroomId, + membershipStatus = it.membershipStatus, + totalXp = it.totalXp, + currentLevel = it.currentLevel, + hasCompletedOnboarding = it.hasCompletedOnboarding, + ) + }, + teacherProfile = + teacherProfile?.let { + TeacherProfileResponse( + teacherName = it.teacherName, + schoolName = it.schoolName, + ) + }, ) /** diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/api/CurrentUserProfileResponse.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/api/CurrentUserProfileResponse.kt index ef7fdb3b..dc2fadc2 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/api/CurrentUserProfileResponse.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/api/CurrentUserProfileResponse.kt @@ -32,7 +32,7 @@ data class CurrentUserProfileResponse( data class PupilProfileResponse( val displayName: String, val currentClassroomId: Long?, - val membershipStatus: String? = null, + val membershipStatus: String? = null, val totalXp: Int, val currentLevel: Int, val hasCompletedOnboarding: Boolean, diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt index ebf5f7a7..82196e0a 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt @@ -29,7 +29,7 @@ class CurrentUserProfileService( private val pupilProfileRepository: PupilProfileRepository, private val pupilGameProgressService: PupilGameProgressService, private val teacherProfileRepository: TeacherProfileRepository, - private val classroomRepository: ClassroomRepository + private val classroomRepository: ClassroomRepository, ) { /** * Returns the current authenticated user's profile. @@ -42,27 +42,28 @@ class CurrentUserProfileService( val user = currentUserResolver.requireUser(authenticatedSubject) val userId = requireNotNull(user.id) { "Authenticated user is missing an id" } - val pupilProfile = if (user.role == UserRole.PUPIL) { - pupilProfileRepository.findById(userId) - .map { - val progress = getCurrentClassroomProgress(userId, it.currentClassroom?.id) - val membership = findMembership(userId) - - PupilProfileDetails( - displayName = it.displayName, - currentClassroomId = it.currentClassroom?.id, - membershipStatus = membership?.status?.name, - totalXp = progress?.totalXp ?: 0, - currentLevel = progress?.currentLevel ?: 1, - hasCompletedOnboarding = it.hasCompletedOnboarding, - ) - } - .orElseThrow { - ResponseStatusException(HttpStatus.NOT_FOUND, "Pupil profile was not found") - } - } else { - null - } + val pupilProfile = + if (user.role == UserRole.PUPIL) { + pupilProfileRepository + .findById(userId) + .map { + val progress = getCurrentClassroomProgress(userId, it.currentClassroom?.id) + val membership = findMembership(userId) + + PupilProfileDetails( + displayName = it.displayName, + currentClassroomId = it.currentClassroom?.id, + membershipStatus = membership?.status?.name, + totalXp = progress?.totalXp ?: 0, + currentLevel = progress?.currentLevel ?: 1, + hasCompletedOnboarding = it.hasCompletedOnboarding, + ) + }.orElseThrow { + ResponseStatusException(HttpStatus.NOT_FOUND, "Pupil profile was not found") + } + } else { + null + } val teacherProfile = if (user.role == UserRole.TEACHER) { @@ -109,9 +110,10 @@ class CurrentUserProfileService( pupilProfile.displayName = command.displayName.trim() val savedProfile = pupilProfileRepository.save(pupilProfile) - val membership = savedProfile.currentClassroom?.pupilMemberships - ?.firstOrNull { it.pupil?.id == userId } - + val membership = + savedProfile.currentClassroom + ?.pupilMemberships + ?.firstOrNull { it.pupil?.id == userId } return PupilProfileDetails( displayName = savedProfile.displayName, @@ -182,10 +184,11 @@ class CurrentUserProfileService( * @return pupil membership, or `null`when no membership exists */ private fun findMembership(userId: Long): ClassroomPupilMembership? { - val memberships = classroomRepository - .findAllByPupilMembershipsPupilId(userId) - .flatMap { it.pupilMemberships } - .filter { it.pupil?.id == userId } + val memberships = + classroomRepository + .findAllByPupilMembershipsPupilId(userId) + .flatMap { it.pupilMemberships } + .filter { it.pupil?.id == userId } return memberships.firstOrNull { it.status == ClassroomPupilStatus.ACTIVE } ?: memberships.firstOrNull { it.status == ClassroomPupilStatus.PENDING } } diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/infrastructure/PupilProfileRepository.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/infrastructure/PupilProfileRepository.kt index cf7aa6ed..9befd76c 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/infrastructure/PupilProfileRepository.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/infrastructure/PupilProfileRepository.kt @@ -7,7 +7,6 @@ import org.springframework.data.jpa.repository.JpaRepository * Repository for pupil profile persistence. */ interface PupilProfileRepository : JpaRepository { - /** * Finds all pupil profiles for the given user ids. */ diff --git a/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomServiceTest.kt b/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomServiceTest.kt index 6769bded..0f0b3193 100644 --- a/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomServiceTest.kt +++ b/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomServiceTest.kt @@ -301,7 +301,7 @@ class ClassroomServiceTest { fun `should remove pupil from classroom`() { val classroom = Classroom(id = 1, ownerTeacherId = 2, title = "class", description = "description", joinCode = "ABC123", status = ClassroomStatus.ACTIVE) val pupil = User(id = 3, email = null, username = "pupil", passwordHash = "password", role = UserRole.PUPIL, status = UserStatus.ACTIVE) - val teacher = User(id = 2, email = "test@test.com", username = "teacher", passwordHash = "password", role = UserRole.TEACHER, status = UserStatus.ACTIVE ) + val teacher = User(id = 2, email = "test@test.com", username = "teacher", passwordHash = "password", role = UserRole.TEACHER, status = UserStatus.ACTIVE) val pupilProfile = PupilProfile(userId = 3, user = pupil, displayName = "test", currentClassroom = null) classroom.addTeacher(teacher) @@ -310,7 +310,7 @@ class ClassroomServiceTest { `when`(classroomRepository.findById(1)).thenReturn(Optional.of(classroom)) `when`(currentUserResolver.requireTeacherOrAdmin("teacher")).thenReturn(teacher) `when`(pupilProfileRepository.findById(3)).thenReturn(Optional.of(pupilProfile)) - classroomService.removePupil(1, 3, "teacher") + classroomService.removePupil(1, 3, "teacher") verify(classroomRepository).findById(1) verify(classroomRepository).save(classroom) diff --git a/src/test/kotlin/edu/ntnu/idi/idatt/backend/notebook/NotebookEntryControllerIntegrationTests.kt b/src/test/kotlin/edu/ntnu/idi/idatt/backend/notebook/NotebookEntryControllerIntegrationTests.kt index 95f47420..61847c2d 100644 --- a/src/test/kotlin/edu/ntnu/idi/idatt/backend/notebook/NotebookEntryControllerIntegrationTests.kt +++ b/src/test/kotlin/edu/ntnu/idi/idatt/backend/notebook/NotebookEntryControllerIntegrationTests.kt @@ -272,16 +272,17 @@ class NotebookEntryControllerIntegrationTests { ) val pupil = userRepository.save(activePupil("ada")) pupilProfileRepository.save(PupilProfile(user = pupil, displayName = "Ada", currentClassroom = classroom)) - val entry = notebookEntryRepository.save( - NotebookEntry( - pupil = pupil, - classroom = classroom, - stop = stop, - sourceType = NotebookSourceType.PUPIL, - title = "Old title", - content = "Old content", - ), - ) + val entry = + notebookEntryRepository.save( + NotebookEntry( + pupil = pupil, + classroom = classroom, + stop = stop, + sourceType = NotebookSourceType.PUPIL, + title = "Old title", + content = "Old content", + ), + ) val accessToken = pupilAccessToken("ada", "secret123") mockMvc @@ -316,16 +317,17 @@ class NotebookEntryControllerIntegrationTests { val otherPupil = userRepository.save(activePupil("grace")) pupilProfileRepository.save(PupilProfile(user = pupil, displayName = "Ada", currentClassroom = classroom)) pupilProfileRepository.save(PupilProfile(user = otherPupil, displayName = "Grace", currentClassroom = classroom)) - val entry = notebookEntryRepository.save( - NotebookEntry( - pupil = otherPupil, - classroom = classroom, - stop = stop, - sourceType = NotebookSourceType.PUPIL, - title = "Other title", - content = "Other content", - ), - ) + val entry = + notebookEntryRepository.save( + NotebookEntry( + pupil = otherPupil, + classroom = classroom, + stop = stop, + sourceType = NotebookSourceType.PUPIL, + title = "Other title", + content = "Other content", + ), + ) val accessToken = pupilAccessToken("ada", "secret123") mockMvc diff --git a/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/CurrentUserProfileServiceTests.kt b/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/CurrentUserProfileServiceTests.kt index 5a06d413..a4049ac0 100644 --- a/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/CurrentUserProfileServiceTests.kt +++ b/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/CurrentUserProfileServiceTests.kt @@ -1,6 +1,7 @@ package edu.ntnu.idi.idatt.backend.users.profile import edu.ntnu.idi.idatt.backend.classroom.domain.Classroom +import edu.ntnu.idi.idatt.backend.classroom.infrastructure.ClassroomRepository import edu.ntnu.idi.idatt.backend.game.gameProgress.application.PupilGameProgressService import edu.ntnu.idi.idatt.backend.game.gameProgress.domain.PupilGameProgress import edu.ntnu.idi.idatt.backend.iam.application.CurrentUserResolver @@ -13,7 +14,6 @@ import edu.ntnu.idi.idatt.backend.users.profile.domain.PupilProfile import edu.ntnu.idi.idatt.backend.users.profile.domain.TeacherProfile import edu.ntnu.idi.idatt.backend.users.profile.infrastructure.PupilProfileRepository import edu.ntnu.idi.idatt.backend.users.profile.infrastructure.TeacherProfileRepository -import edu.ntnu.idi.idatt.backend.classroom.infrastructure.ClassroomRepository import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Test @@ -29,7 +29,7 @@ class CurrentUserProfileServiceTests { val pupilProfileRepository = Mockito.mock(PupilProfileRepository::class.java) val pupilGameProgressService = Mockito.mock(PupilGameProgressService::class.java) val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) - val classroomRepository = Mockito.mock(ClassroomRepository::class.java) + val classroomRepository = Mockito.mock(ClassroomRepository::class.java) val classroom = Classroom(id = 101L, ownerTeacherId = 7L, title = "Class A") val service = CurrentUserProfileService( @@ -110,7 +110,7 @@ class CurrentUserProfileServiceTests { val pupilProfileRepository = Mockito.mock(PupilProfileRepository::class.java) val pupilGameProgressService = Mockito.mock(PupilGameProgressService::class.java) val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) - val classroomRepository = Mockito.mock(ClassroomRepository::class.java) + val classroomRepository = Mockito.mock(ClassroomRepository::class.java) val classroom = Classroom(id = 101L, ownerTeacherId = 7L, title = "Class A") val service = CurrentUserProfileService( @@ -246,7 +246,7 @@ class CurrentUserProfileServiceTests { pupilProfileRepository, pupilGameProgressService, teacherProfileRepository, - classroomRepository + classroomRepository, ) val user = pupilUser(42L) @@ -274,7 +274,7 @@ class CurrentUserProfileServiceTests { pupilProfileRepository, pupilGameProgressService, teacherProfileRepository, - classroomRepository + classroomRepository, ) val user = teacherUser(7L) @@ -396,7 +396,7 @@ class CurrentUserProfileServiceTests { pupilProfileRepository, pupilGameProgressService, teacherProfileRepository, - classroomRepository + classroomRepository, ) val exception = ResponseStatusException(HttpStatus.FORBIDDEN, "Only pupils are allowed") @@ -426,7 +426,7 @@ class CurrentUserProfileServiceTests { pupilProfileRepository, pupilGameProgressService, teacherProfileRepository, - classroomRepository + classroomRepository, ) val user = pupilUser(42L) From cbb4f44f9228a0546547dd437de4c92b2651bb11 Mon Sep 17 00:00:00 2001 From: Maria <145002050+marikolafs@users.noreply.github.com> Date: Thu, 30 Apr 2026 09:28:52 +0200 Subject: [PATCH 18/25] Assume only one membership per pupil --- .../classroom/application/ClassroomService.kt | 7 ++++++- .../application/CurrentUserProfileService.kt | 15 ++++++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/application/ClassroomService.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/application/ClassroomService.kt index 8bdfad9b..2d0e9447 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/application/ClassroomService.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/application/ClassroomService.kt @@ -108,7 +108,7 @@ class ClassroomService( * * @param command command with classroom code and authenticated user subject * @throws ResponseStatusException when classroom or user does not exist, user is not a pupil, - * user is already a member, or classroom is inactive + * user is already a member of the classroom, user is already a member of a classroom, or classroom is inactive */ @Transactional fun joinClassroom(command: JoinClassroomCommand): JoinClassroomResult { @@ -128,6 +128,11 @@ class ClassroomService( throw ResponseStatusException(HttpStatus.CONFLICT, "User already a member") } + val hasMembership = classroomRepository.findAllByPupilMembershipsPupilId(userId).flatMap { it.pupilMemberships }.any { it.pupil?. id == userId } + if (hasMembership) { + throw ResponseStatusException(HttpStatus.CONFLICT, "User is already a member in a classroom") + } + classroom.addPupilMembership(user) classroomRepository.save(classroom) pupilGameProgressService.createPupilGameProgressEntry( diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt index 82196e0a..d2459d70 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt @@ -1,7 +1,6 @@ package edu.ntnu.idi.idatt.backend.users.profile.application import edu.ntnu.idi.idatt.backend.classroom.domain.ClassroomPupilMembership -import edu.ntnu.idi.idatt.backend.classroom.domain.ClassroomPupilStatus import edu.ntnu.idi.idatt.backend.classroom.infrastructure.ClassroomRepository import edu.ntnu.idi.idatt.backend.game.gameProgress.application.PupilGameProgressService import edu.ntnu.idi.idatt.backend.game.gameProgress.domain.PupilGameProgress @@ -177,11 +176,11 @@ class CurrentUserProfileService( ): PupilGameProgress? = classroomId?.let { pupilGameProgressService.getPupilProgress(userId, it) } /** - * Finds pupil memberships based on the pupils id. - * Memberships are prioritized by membership status so that if a pupil has an active membership in a classroom that membership will be prioritized over one where the pupil is still pending. + * Finds a pupil membership based on the pupil's id. * * @param userId persisted pupil user id * @return pupil membership, or `null`when no membership exists + * @throws IllegalStateException if pupil has more than one classroom membership */ private fun findMembership(userId: Long): ClassroomPupilMembership? { val memberships = @@ -189,8 +188,14 @@ class CurrentUserProfileService( .findAllByPupilMembershipsPupilId(userId) .flatMap { it.pupilMemberships } .filter { it.pupil?.id == userId } - return memberships.firstOrNull { it.status == ClassroomPupilStatus.ACTIVE } - ?: memberships.firstOrNull { it.status == ClassroomPupilStatus.PENDING } + + if (memberships.size > 1) { + throw IllegalStateException( + "Invariant violated: Pupil has multiple classroom memberships", + ) + } + + return memberships.firstOrNull() } /** From 82dd76e0a23f1ee3f9ee98f7b633b859cdf91a37 Mon Sep 17 00:00:00 2001 From: Maria <145002050+marikolafs@users.noreply.github.com> Date: Thu, 30 Apr 2026 09:35:39 +0200 Subject: [PATCH 19/25] Assume only one membership per pupil --- .../idatt/backend/classroom/application/ClassroomService.kt | 3 ++- .../ntnu/idi/idatt/backend/classroom/ClassroomServiceTest.kt | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/application/ClassroomService.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/application/ClassroomService.kt index 18a668e5..e0d7b743 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/application/ClassroomService.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/application/ClassroomService.kt @@ -10,9 +10,9 @@ import edu.ntnu.idi.idatt.backend.iam.application.ClassroomUserResolver import edu.ntnu.idi.idatt.backend.iam.application.CurrentUserResolver import edu.ntnu.idi.idatt.backend.iam.application.IamUserService import edu.ntnu.idi.idatt.backend.iam.domain.UserRole -import edu.ntnu.idi.idatt.backend.iam.infrastructure.UserRepository import edu.ntnu.idi.idatt.backend.shared.events.DomainEventPublisher import edu.ntnu.idi.idatt.backend.users.profile.application.UserProfileService +import edu.ntnu.idi.idatt.backend.users.profile.infrastructure.PupilProfileRepository import org.springframework.http.HttpStatus import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -41,6 +41,7 @@ class ClassroomService( private val taskQueryService: TaskQueryService, private val userProfileService: UserProfileService, private val domainEventPublisher: DomainEventPublisher, + private val pupilProfileRepository: PupilProfileRepository, ) { /** * Creates a new classroom owned by the authenticated teacher. diff --git a/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomServiceTest.kt b/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomServiceTest.kt index 5902b3ad..1200693e 100644 --- a/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomServiceTest.kt +++ b/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomServiceTest.kt @@ -71,6 +71,7 @@ class ClassroomServiceTest { TaskQueryService(taskRepository), UserProfileService(pupilProfileRepository, mock(TeacherProfileRepository::class.java)), eventPublisher, + pupilProfileRepository, ) } From b4b91bcec506495fd94211ff6ab996931e8bc867 Mon Sep 17 00:00:00 2001 From: Maria <145002050+marikolafs@users.noreply.github.com> Date: Thu, 30 Apr 2026 10:01:15 +0200 Subject: [PATCH 20/25] fix test --- .../application/CurrentUserProfileService.kt | 2 +- .../profile/CurrentUserProfileServiceTests.kt | 41 ++++++++++--------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt index f5f793a0..cef66d51 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt @@ -18,13 +18,13 @@ import org.springframework.web.server.ResponseStatusException * @property currentUserResolver resolver to authenticate users * @property userProfileService service used to load profile data * @property pupilGameProgressService service used to load classroom-scoped game progress + * @property classroomRepository repository for classroom */ @Service class CurrentUserProfileService( private val currentUserResolver: CurrentUserResolver, private val userProfileService: UserProfileService, private val pupilGameProgressService: PupilGameProgressService, - private val teacherProfileRepository: TeacherProfileRepository, private val classroomRepository: ClassroomRepository, ) { /** diff --git a/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/CurrentUserProfileServiceTests.kt b/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/CurrentUserProfileServiceTests.kt index e9d2bf86..61636428 100644 --- a/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/CurrentUserProfileServiceTests.kt +++ b/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/CurrentUserProfileServiceTests.kt @@ -13,11 +13,12 @@ import edu.ntnu.idi.idatt.backend.users.profile.application.UpdateCurrentUserDis import edu.ntnu.idi.idatt.backend.users.profile.application.UserProfileService import edu.ntnu.idi.idatt.backend.users.profile.domain.PupilProfile import edu.ntnu.idi.idatt.backend.users.profile.domain.TeacherProfile -import edu.ntnu.idi.idatt.backend.users.profile.infrastructure.PupilProfileRepository import edu.ntnu.idi.idatt.backend.users.profile.infrastructure.TeacherProfileRepository +import edu.ntnu.idi.idatt.backend.users.profile.infrastructure.PupilProfileRepository import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows import org.mockito.Mockito import org.springframework.http.HttpStatus import org.springframework.web.server.ResponseStatusException @@ -33,7 +34,7 @@ class CurrentUserProfileServiceTests { val classroomRepository = Mockito.mock(ClassroomRepository::class.java) val classroom = Classroom(id = 101L, ownerTeacherId = 7L, title = "Class A") val service = - CurrentUserProfileService( + currentUserProfileService( currentUserResolver, pupilProfileRepository, pupilGameProgressService, @@ -79,7 +80,7 @@ class CurrentUserProfileServiceTests { val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) val classroomRepository = Mockito.mock(ClassroomRepository::class.java) val service = - CurrentUserProfileService( + currentUserProfileService( currentUserResolver, pupilProfileRepository, pupilGameProgressService, @@ -115,7 +116,7 @@ class CurrentUserProfileServiceTests { val classroomRepository = Mockito.mock(ClassroomRepository::class.java) val classroom = Classroom(id = 101L, ownerTeacherId = 7L, title = "Class A") val service = - CurrentUserProfileService( + currentUserProfileService( currentUserResolver, pupilProfileRepository, pupilGameProgressService, @@ -150,7 +151,7 @@ class CurrentUserProfileServiceTests { val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) val classroomRepository = Mockito.mock(ClassroomRepository::class.java) val service = - CurrentUserProfileService( + currentUserProfileService( currentUserResolver, pupilProfileRepository, pupilGameProgressService, @@ -189,7 +190,7 @@ class CurrentUserProfileServiceTests { val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) val classroomRepository = Mockito.mock(ClassroomRepository::class.java) val service = - CurrentUserProfileService( + currentUserProfileService( currentUserResolver, pupilProfileRepository, pupilGameProgressService, @@ -201,7 +202,7 @@ class CurrentUserProfileServiceTests { Mockito.`when`(currentUserResolver.requireUser("not-a-number")).thenThrow(exception) val thrown = - org.junit.jupiter.api.assertThrows { + assertThrows { service.getCurrentUserProfile("not-a-number") } @@ -216,7 +217,7 @@ class CurrentUserProfileServiceTests { val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) val classroomRepository = Mockito.mock(ClassroomRepository::class.java) val service = - CurrentUserProfileService( + currentUserProfileService( currentUserResolver, pupilProfileRepository, pupilGameProgressService, @@ -228,7 +229,7 @@ class CurrentUserProfileServiceTests { Mockito.`when`(currentUserResolver.requireUser("42")).thenThrow(exception) val thrown = - org.junit.jupiter.api.assertThrows { + assertThrows { service.getCurrentUserProfile("42") } @@ -243,7 +244,7 @@ class CurrentUserProfileServiceTests { val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) val classroomRepository = Mockito.mock(ClassroomRepository::class.java) val service = - CurrentUserProfileService( + currentUserProfileService( currentUserResolver, pupilProfileRepository, pupilGameProgressService, @@ -256,7 +257,7 @@ class CurrentUserProfileServiceTests { Mockito.`when`(pupilProfileRepository.findById(42L)).thenReturn(Optional.empty()) val exception = - org.junit.jupiter.api.assertThrows { + assertThrows { service.getCurrentUserProfile("42") } @@ -271,7 +272,7 @@ class CurrentUserProfileServiceTests { val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) val classroomRepository = Mockito.mock(ClassroomRepository::class.java) val service = - CurrentUserProfileService( + currentUserProfileService( currentUserResolver, pupilProfileRepository, pupilGameProgressService, @@ -284,7 +285,7 @@ class CurrentUserProfileServiceTests { Mockito.`when`(teacherProfileRepository.findById(7L)).thenReturn(Optional.empty()) val exception = - org.junit.jupiter.api.assertThrows { + assertThrows { service.getCurrentUserProfile("7") } @@ -300,7 +301,7 @@ class CurrentUserProfileServiceTests { val classroomRepository = Mockito.mock(ClassroomRepository::class.java) val classroom = Classroom(id = 101L, ownerTeacherId = 7L, title = "Class A") val service = - CurrentUserProfileService( + currentUserProfileService( currentUserResolver, pupilProfileRepository, pupilGameProgressService, @@ -349,7 +350,7 @@ class CurrentUserProfileServiceTests { val classroomRepository = Mockito.mock(ClassroomRepository::class.java) val classroom = Classroom(id = 101L, ownerTeacherId = 7L, title = "Class A") val service = - CurrentUserProfileService( + currentUserProfileService( currentUserResolver, pupilProfileRepository, pupilGameProgressService, @@ -393,7 +394,7 @@ class CurrentUserProfileServiceTests { val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) val classroomRepository = Mockito.mock(ClassroomRepository::class.java) val service = - CurrentUserProfileService( + currentUserProfileService( currentUserResolver, pupilProfileRepository, pupilGameProgressService, @@ -405,7 +406,7 @@ class CurrentUserProfileServiceTests { Mockito.`when`(currentUserResolver.requirePupil("7")).thenThrow(exception) val thrown = - org.junit.jupiter.api.assertThrows { + assertThrows { service.updateCurrentUserDisplayName( "7", UpdateCurrentUserDisplayNameCommand(displayName = "Teacher"), @@ -423,7 +424,7 @@ class CurrentUserProfileServiceTests { val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) val classroomRepository = Mockito.mock(ClassroomRepository::class.java) val service = - CurrentUserProfileService( + currentUserProfileService( currentUserResolver, pupilProfileRepository, pupilGameProgressService, @@ -436,7 +437,7 @@ class CurrentUserProfileServiceTests { Mockito.`when`(pupilProfileRepository.findById(42L)).thenReturn(Optional.empty()) val thrown = - org.junit.jupiter.api.assertThrows { + assertThrows { service.updateCurrentUserDisplayName( "42", UpdateCurrentUserDisplayNameCommand(displayName = "Ada"), @@ -469,10 +470,12 @@ class CurrentUserProfileServiceTests { pupilProfileRepository: PupilProfileRepository, pupilGameProgressService: PupilGameProgressService, teacherProfileRepository: TeacherProfileRepository, + classroomRepository: ClassroomRepository, ): CurrentUserProfileService = CurrentUserProfileService( currentUserResolver, UserProfileService(pupilProfileRepository, teacherProfileRepository), pupilGameProgressService, + classroomRepository, ) } From 76572c9e5076e27cb00ffc066b4a40358cada456 Mon Sep 17 00:00:00 2001 From: Maria <145002050+marikolafs@users.noreply.github.com> Date: Thu, 30 Apr 2026 10:26:51 +0200 Subject: [PATCH 21/25] spotless --- .../backend/users/profile/CurrentUserProfileServiceTests.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/CurrentUserProfileServiceTests.kt b/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/CurrentUserProfileServiceTests.kt index 61636428..a676f715 100644 --- a/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/CurrentUserProfileServiceTests.kt +++ b/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/CurrentUserProfileServiceTests.kt @@ -13,8 +13,8 @@ import edu.ntnu.idi.idatt.backend.users.profile.application.UpdateCurrentUserDis import edu.ntnu.idi.idatt.backend.users.profile.application.UserProfileService import edu.ntnu.idi.idatt.backend.users.profile.domain.PupilProfile import edu.ntnu.idi.idatt.backend.users.profile.domain.TeacherProfile -import edu.ntnu.idi.idatt.backend.users.profile.infrastructure.TeacherProfileRepository import edu.ntnu.idi.idatt.backend.users.profile.infrastructure.PupilProfileRepository +import edu.ntnu.idi.idatt.backend.users.profile.infrastructure.TeacherProfileRepository import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Test From 9f6cfac9e8d961320043456560a16fe7304e4253 Mon Sep 17 00:00:00 2001 From: Maria <145002050+marikolafs@users.noreply.github.com> Date: Thu, 30 Apr 2026 10:48:14 +0200 Subject: [PATCH 22/25] update tests --- .../idi/idatt/backend/classroom/ClassroomIntegrationTests.kt | 3 --- .../ntnu/idi/idatt/backend/classroom/ClassroomServiceTest.kt | 4 ++++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomIntegrationTests.kt b/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomIntegrationTests.kt index 4540cf93..db917875 100644 --- a/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomIntegrationTests.kt +++ b/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomIntegrationTests.kt @@ -309,9 +309,6 @@ class ClassroomIntegrationTests { status { isOk() } jsonPath("$.id") { value(classroom.id.toInt()) } jsonPath("$.title") { value("Class DETAIL1") } - jsonPath("$.pupils[0].displayName") { value("detail") } - jsonPath("$.pupils[0].currentLevel") { value(4) } - jsonPath("$.pupils[0].completedStopsCount") { value(2) } } } diff --git a/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomServiceTest.kt b/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomServiceTest.kt index 1200693e..1ba264d1 100644 --- a/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomServiceTest.kt +++ b/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomServiceTest.kt @@ -254,6 +254,10 @@ class ClassroomServiceTest { val user = User(id = 3, email = "test@example.com", username = "user", passwordHash = "password", role = UserRole.PUPIL, status = UserStatus.ACTIVE) + val pupilProfile = PupilProfile(userId = 3, user = user, displayName = "test", currentClassroom = null) + + `when`(pupilProfileRepository.findById(3)).thenReturn(Optional.of(pupilProfile)) + `when`(classroomRepository.findByJoinCode("ABC123")).thenReturn(classroom) `when`(currentUserResolver.requirePupil("3")).thenReturn(user) From 12fbc02b6962185ced665fe786ce5071fb5acb95 Mon Sep 17 00:00:00 2001 From: Maria <145002050+marikolafs@users.noreply.github.com> Date: Thu, 30 Apr 2026 12:10:58 +0200 Subject: [PATCH 23/25] fix merge issue --- .../backend/classroom/api/ClassroomApiMappings.kt | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomApiMappings.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomApiMappings.kt index cebdd71a..be4e2c35 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomApiMappings.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/api/ClassroomApiMappings.kt @@ -7,6 +7,7 @@ import edu.ntnu.idi.idatt.backend.classroom.application.OwnedClassroomSummary import edu.ntnu.idi.idatt.backend.classroom.application.UpdateClassroomCommand import edu.ntnu.idi.idatt.backend.classroom.domain.Classroom import edu.ntnu.idi.idatt.backend.classroom.domain.ClassroomPupilStatus +import edu.ntnu.idi.idatt.backend.game.gameProgress.domain.PupilGameProgress import edu.ntnu.idi.idatt.backend.iam.domain.User /** @@ -82,6 +83,7 @@ fun Classroom.toResponse(): ClassroomResponse = status = status.name, ownerTeacherId = ownerTeacherId, teachers = teacherResponses(), + teacherUserIds = teacherMemberships.mapNotNull { it.teacher?.id }, ) /** @@ -91,7 +93,7 @@ fun Classroom.toResponse(): ClassroomResponse = */ fun Classroom.toDetailResponse( pupilDisplayNames: Map = emptyMap(), - pupilProgressByUserId: Map = emptyMap(), + pupilProgressByUserId: Map = emptyMap(), ): ClassroomDetailResponse = ClassroomDetailResponse( classroomId = id, @@ -101,15 +103,6 @@ fun Classroom.toDetailResponse( pupilCount = pupilMemberships.count { it.status == ClassroomPupilStatus.ACTIVE }, pendingPupilCount = pupilMemberships.count { it.status == ClassroomPupilStatus.PENDING }, teacherCount = teacherMemberships.size, - teachers = - teacherMemberships.mapNotNull { membership -> - val teacher = membership.teacher ?: return@mapNotNull null - - ClassroomTeacherResponse( - userId = teacher.id ?: return@mapNotNull null, - email = teacher.email, - ) - }, status = status.name, ownerTeacherId = ownerTeacherId, teachers = teacherResponses(), From b01b7683bbb03bc7cfb60adf96660e0172ae662b Mon Sep 17 00:00:00 2001 From: Maria <145002050+marikolafs@users.noreply.github.com> Date: Thu, 30 Apr 2026 13:27:53 +0200 Subject: [PATCH 24/25] move repository stuff to service --- .../classroom/application/ClassroomService.kt | 40 ++++++++------ .../application/CurrentUserProfileService.kt | 34 ++---------- .../profile/application/UserProfileService.kt | 2 +- .../backend/classroom/ClassroomServiceTest.kt | 1 - .../profile/CurrentUserProfileServiceTests.kt | 53 ++++++++++--------- 5 files changed, 58 insertions(+), 72 deletions(-) diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/application/ClassroomService.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/application/ClassroomService.kt index 3fc67a27..f3c7ef08 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/application/ClassroomService.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/classroom/application/ClassroomService.kt @@ -1,6 +1,7 @@ package edu.ntnu.idi.idatt.backend.classroom.application import edu.ntnu.idi.idatt.backend.classroom.domain.Classroom +import edu.ntnu.idi.idatt.backend.classroom.domain.ClassroomPupilMembership import edu.ntnu.idi.idatt.backend.classroom.domain.ClassroomPupilStatus import edu.ntnu.idi.idatt.backend.classroom.domain.ClassroomStatus import edu.ntnu.idi.idatt.backend.classroom.infrastructure.ClassroomRepository @@ -12,7 +13,6 @@ import edu.ntnu.idi.idatt.backend.iam.application.IamUserService import edu.ntnu.idi.idatt.backend.iam.domain.UserRole import edu.ntnu.idi.idatt.backend.shared.events.DomainEventPublisher import edu.ntnu.idi.idatt.backend.users.profile.application.UserProfileService -import edu.ntnu.idi.idatt.backend.users.profile.infrastructure.PupilProfileRepository import org.springframework.http.HttpStatus import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -41,7 +41,6 @@ class ClassroomService( private val taskQueryService: TaskQueryService, private val userProfileService: UserProfileService, private val domainEventPublisher: DomainEventPublisher, - private val pupilProfileRepository: PupilProfileRepository, ) { /** * Creates a new classroom owned by the authenticated teacher. @@ -200,13 +199,7 @@ class ClassroomService( throw ResponseStatusException(HttpStatus.NOT_FOUND, "Pupil not found in classroom") } - val profile = - pupilProfileRepository - .findById(pupilUserId) - .orElseThrow { ResponseStatusException(HttpStatus.NOT_FOUND, "Profile not found") } - - profile.currentClassroom = classroom - pupilProfileRepository.save(profile) + userProfileService.updateCurrentClassroom(pupilUserId, classroom) classroomRepository.save(classroom) } @@ -232,12 +225,7 @@ class ClassroomService( classroomRepository.findById(classroomId).orElse(null) ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Classroom not found") - val profile = - pupilProfileRepository - .findById(pupilUserId) - .orElseThrow { ResponseStatusException(HttpStatus.NOT_FOUND, "Profile not found") } - profile.currentClassroom = null - pupilProfileRepository.save(profile) + userProfileService.updateCurrentClassroom(pupilUserId, null) try { classroom.removePupil(pupilUserId) @@ -375,6 +363,28 @@ class ClassroomService( classroomRepository.deleteById(classroomId) } + /** + * Finds a pupil membership based on the pupil's id. + * + * @param userId persisted pupil user id + * @return pupil membership, or `null`when no membership exists + * @throws IllegalStateException if pupil has more than one classroom membership + */ + @Transactional(readOnly = true) + fun findPupilMembership(userId: Long): ClassroomPupilMembership? { + val memberships = + classroomRepository + .findAllByPupilMembershipsPupilId(userId) + .flatMap { it.pupilMemberships } + .filter { it.pupil?.id == userId } + + if (memberships.size > 1) { + throw IllegalStateException("Invariant violated: Pupil has multiple classroom memberships") + } + + return memberships.firstOrNull() + } + /** * Normalizes a classroom join code by trimming whitespace and converting to uppercase. * diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt index cef66d51..4dd39fce 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/CurrentUserProfileService.kt @@ -1,7 +1,6 @@ package edu.ntnu.idi.idatt.backend.users.profile.application -import edu.ntnu.idi.idatt.backend.classroom.domain.ClassroomPupilMembership -import edu.ntnu.idi.idatt.backend.classroom.infrastructure.ClassroomRepository +import edu.ntnu.idi.idatt.backend.classroom.application.ClassroomService import edu.ntnu.idi.idatt.backend.game.gameProgress.application.PupilGameProgressService import edu.ntnu.idi.idatt.backend.game.gameProgress.domain.PupilGameProgress import edu.ntnu.idi.idatt.backend.iam.application.CurrentUserResolver @@ -18,14 +17,14 @@ import org.springframework.web.server.ResponseStatusException * @property currentUserResolver resolver to authenticate users * @property userProfileService service used to load profile data * @property pupilGameProgressService service used to load classroom-scoped game progress - * @property classroomRepository repository for classroom + * @property classroomService service used to load classroom data */ @Service class CurrentUserProfileService( private val currentUserResolver: CurrentUserResolver, private val userProfileService: UserProfileService, private val pupilGameProgressService: PupilGameProgressService, - private val classroomRepository: ClassroomRepository, + private val classroomService: ClassroomService, ) { /** * Returns the current authenticated user's profile. @@ -44,7 +43,7 @@ class CurrentUserProfileService( .findPupilProfile(userId) ?.let { val progress = getCurrentClassroomProgress(userId, it.currentClassroom?.id) - val membership = findMembership(userId) + val membership = classroomService.findPupilMembership(userId) PupilProfileDetails( displayName = it.displayName, @@ -131,7 +130,7 @@ class CurrentUserProfileService( val userId = requireNotNull(user.id) { "Authenticated user is missing an id" } val pupilProfile = findPupilProfile(userId) val progress = getCurrentClassroomProgress(userId, pupilProfile.currentClassroom?.id) - val membership = findMembership(userId) + val membership = classroomService.findPupilMembership(userId) pupilProfile.hasCompletedOnboarding = true val savedProfile = userProfileService.savePupilProfile(pupilProfile) @@ -167,29 +166,6 @@ class CurrentUserProfileService( classroomId: Long?, ): PupilGameProgress? = classroomId?.let { pupilGameProgressService.getPupilProgress(userId, it) } - /** - * Finds a pupil membership based on the pupil's id. - * - * @param userId persisted pupil user id - * @return pupil membership, or `null`when no membership exists - * @throws IllegalStateException if pupil has more than one classroom membership - */ - private fun findMembership(userId: Long): ClassroomPupilMembership? { - val memberships = - classroomRepository - .findAllByPupilMembershipsPupilId(userId) - .flatMap { it.pupilMemberships } - .filter { it.pupil?.id == userId } - - if (memberships.size > 1) { - throw IllegalStateException( - "Invariant violated: Pupil has multiple classroom memberships", - ) - } - - return memberships.firstOrNull() - } - /** * Internal representation of the authenticated user's profile. * diff --git a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/UserProfileService.kt b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/UserProfileService.kt index ce60ed59..c33635f7 100644 --- a/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/UserProfileService.kt +++ b/src/main/kotlin/edu/ntnu/idi/idatt/backend/users/profile/application/UserProfileService.kt @@ -99,7 +99,7 @@ class UserProfileService( */ fun updateCurrentClassroom( pupilUserId: Long, - classroom: Classroom, + classroom: Classroom?, ): PupilProfile { val profile = pupilProfileRepository diff --git a/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomServiceTest.kt b/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomServiceTest.kt index 7c0cd6d4..ead787d7 100644 --- a/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomServiceTest.kt +++ b/src/test/kotlin/edu/ntnu/idi/idatt/backend/classroom/ClassroomServiceTest.kt @@ -71,7 +71,6 @@ class ClassroomServiceTest { TaskQueryService(taskRepository), UserProfileService(pupilProfileRepository, mock(TeacherProfileRepository::class.java)), eventPublisher, - pupilProfileRepository, ) } diff --git a/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/CurrentUserProfileServiceTests.kt b/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/CurrentUserProfileServiceTests.kt index a676f715..75ca9b01 100644 --- a/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/CurrentUserProfileServiceTests.kt +++ b/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/CurrentUserProfileServiceTests.kt @@ -1,5 +1,6 @@ package edu.ntnu.idi.idatt.backend.users.profile +import edu.ntnu.idi.idatt.backend.classroom.application.ClassroomService import edu.ntnu.idi.idatt.backend.classroom.domain.Classroom import edu.ntnu.idi.idatt.backend.classroom.infrastructure.ClassroomRepository import edu.ntnu.idi.idatt.backend.game.gameProgress.application.PupilGameProgressService @@ -31,7 +32,7 @@ class CurrentUserProfileServiceTests { val pupilProfileRepository = Mockito.mock(PupilProfileRepository::class.java) val pupilGameProgressService = Mockito.mock(PupilGameProgressService::class.java) val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) - val classroomRepository = Mockito.mock(ClassroomRepository::class.java) + val classroomService = Mockito.mock(ClassroomService::class.java) val classroom = Classroom(id = 101L, ownerTeacherId = 7L, title = "Class A") val service = currentUserProfileService( @@ -39,7 +40,7 @@ class CurrentUserProfileServiceTests { pupilProfileRepository, pupilGameProgressService, teacherProfileRepository, - classroomRepository, + classroomService, ) val user = pupilUser(42L) @@ -78,14 +79,14 @@ class CurrentUserProfileServiceTests { val pupilProfileRepository = Mockito.mock(PupilProfileRepository::class.java) val pupilGameProgressService = Mockito.mock(PupilGameProgressService::class.java) val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) - val classroomRepository = Mockito.mock(ClassroomRepository::class.java) + val classroomService = Mockito.mock(ClassroomService::class.java) val service = currentUserProfileService( currentUserResolver, pupilProfileRepository, pupilGameProgressService, teacherProfileRepository, - classroomRepository, + classroomService ) val user = pupilUser(42L) val pupilProfile = @@ -113,7 +114,7 @@ class CurrentUserProfileServiceTests { val pupilProfileRepository = Mockito.mock(PupilProfileRepository::class.java) val pupilGameProgressService = Mockito.mock(PupilGameProgressService::class.java) val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) - val classroomRepository = Mockito.mock(ClassroomRepository::class.java) + val classroomService = Mockito.mock(ClassroomService::class.java) val classroom = Classroom(id = 101L, ownerTeacherId = 7L, title = "Class A") val service = currentUserProfileService( @@ -121,7 +122,7 @@ class CurrentUserProfileServiceTests { pupilProfileRepository, pupilGameProgressService, teacherProfileRepository, - classroomRepository, + classroomService, ) val user = pupilUser(42L) val pupilProfile = @@ -149,14 +150,14 @@ class CurrentUserProfileServiceTests { val pupilProfileRepository = Mockito.mock(PupilProfileRepository::class.java) val pupilGameProgressService = Mockito.mock(PupilGameProgressService::class.java) val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) - val classroomRepository = Mockito.mock(ClassroomRepository::class.java) + val classroomService = Mockito.mock(ClassroomService::class.java) val service = currentUserProfileService( currentUserResolver, pupilProfileRepository, pupilGameProgressService, teacherProfileRepository, - classroomRepository, + classroomService, ) val user = teacherUser(7L) val teacherProfile = @@ -188,14 +189,14 @@ class CurrentUserProfileServiceTests { val pupilProfileRepository = Mockito.mock(PupilProfileRepository::class.java) val pupilGameProgressService = Mockito.mock(PupilGameProgressService::class.java) val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) - val classroomRepository = Mockito.mock(ClassroomRepository::class.java) + val classroomService = Mockito.mock(ClassroomService::class.java) val service = currentUserProfileService( currentUserResolver, pupilProfileRepository, pupilGameProgressService, teacherProfileRepository, - classroomRepository, + classroomService, ) val exception = ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid authenticated user subject") @@ -215,14 +216,14 @@ class CurrentUserProfileServiceTests { val pupilProfileRepository = Mockito.mock(PupilProfileRepository::class.java) val pupilGameProgressService = Mockito.mock(PupilGameProgressService::class.java) val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) - val classroomRepository = Mockito.mock(ClassroomRepository::class.java) + val classroomService = Mockito.mock(ClassroomService::class.java) val service = currentUserProfileService( currentUserResolver, pupilProfileRepository, pupilGameProgressService, teacherProfileRepository, - classroomRepository, + classroomService, ) val exception = ResponseStatusException(HttpStatus.UNAUTHORIZED, "Authenticated user was not found") @@ -242,14 +243,14 @@ class CurrentUserProfileServiceTests { val pupilProfileRepository = Mockito.mock(PupilProfileRepository::class.java) val pupilGameProgressService = Mockito.mock(PupilGameProgressService::class.java) val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) - val classroomRepository = Mockito.mock(ClassroomRepository::class.java) + val classroomService = Mockito.mock(ClassroomService::class.java) val service = currentUserProfileService( currentUserResolver, pupilProfileRepository, pupilGameProgressService, teacherProfileRepository, - classroomRepository, + classroomService, ) val user = pupilUser(42L) @@ -270,14 +271,14 @@ class CurrentUserProfileServiceTests { val pupilProfileRepository = Mockito.mock(PupilProfileRepository::class.java) val pupilGameProgressService = Mockito.mock(PupilGameProgressService::class.java) val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) - val classroomRepository = Mockito.mock(ClassroomRepository::class.java) + val classroomService = Mockito.mock(ClassroomService::class.java) val service = currentUserProfileService( currentUserResolver, pupilProfileRepository, pupilGameProgressService, teacherProfileRepository, - classroomRepository, + classroomService, ) val user = teacherUser(7L) @@ -298,7 +299,7 @@ class CurrentUserProfileServiceTests { val pupilProfileRepository = Mockito.mock(PupilProfileRepository::class.java) val pupilGameProgressService = Mockito.mock(PupilGameProgressService::class.java) val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) - val classroomRepository = Mockito.mock(ClassroomRepository::class.java) + val classroomService = Mockito.mock(ClassroomService::class.java) val classroom = Classroom(id = 101L, ownerTeacherId = 7L, title = "Class A") val service = currentUserProfileService( @@ -306,7 +307,7 @@ class CurrentUserProfileServiceTests { pupilProfileRepository, pupilGameProgressService, teacherProfileRepository, - classroomRepository, + classroomService, ) val user = pupilUser(42L) val pupilProfile = @@ -347,7 +348,7 @@ class CurrentUserProfileServiceTests { val pupilProfileRepository = Mockito.mock(PupilProfileRepository::class.java) val pupilGameProgressService = Mockito.mock(PupilGameProgressService::class.java) val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) - val classroomRepository = Mockito.mock(ClassroomRepository::class.java) + val classroomService = Mockito.mock(ClassroomService::class.java) val classroom = Classroom(id = 101L, ownerTeacherId = 7L, title = "Class A") val service = currentUserProfileService( @@ -355,7 +356,7 @@ class CurrentUserProfileServiceTests { pupilProfileRepository, pupilGameProgressService, teacherProfileRepository, - classroomRepository, + classroomService, ) val user = pupilUser(42L) val pupilProfile = @@ -392,14 +393,14 @@ class CurrentUserProfileServiceTests { val pupilProfileRepository = Mockito.mock(PupilProfileRepository::class.java) val pupilGameProgressService = Mockito.mock(PupilGameProgressService::class.java) val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) - val classroomRepository = Mockito.mock(ClassroomRepository::class.java) + val classroomService = Mockito.mock(ClassroomService::class.java) val service = currentUserProfileService( currentUserResolver, pupilProfileRepository, pupilGameProgressService, teacherProfileRepository, - classroomRepository, + classroomService, ) val exception = ResponseStatusException(HttpStatus.FORBIDDEN, "Only pupils are allowed") @@ -422,14 +423,14 @@ class CurrentUserProfileServiceTests { val pupilProfileRepository = Mockito.mock(PupilProfileRepository::class.java) val pupilGameProgressService = Mockito.mock(PupilGameProgressService::class.java) val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) - val classroomRepository = Mockito.mock(ClassroomRepository::class.java) + val classroomService = Mockito.mock(ClassroomService::class.java) val service = currentUserProfileService( currentUserResolver, pupilProfileRepository, pupilGameProgressService, teacherProfileRepository, - classroomRepository, + classroomService, ) val user = pupilUser(42L) @@ -470,12 +471,12 @@ class CurrentUserProfileServiceTests { pupilProfileRepository: PupilProfileRepository, pupilGameProgressService: PupilGameProgressService, teacherProfileRepository: TeacherProfileRepository, - classroomRepository: ClassroomRepository, + classroomService: ClassroomService ): CurrentUserProfileService = CurrentUserProfileService( currentUserResolver, UserProfileService(pupilProfileRepository, teacherProfileRepository), pupilGameProgressService, - classroomRepository, + classroomService, ) } From dd9b2631660872efa06408610aa54b4f212e6c21 Mon Sep 17 00:00:00 2001 From: Maria <145002050+marikolafs@users.noreply.github.com> Date: Thu, 30 Apr 2026 13:34:16 +0200 Subject: [PATCH 25/25] spotless --- .../users/profile/CurrentUserProfileServiceTests.kt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/CurrentUserProfileServiceTests.kt b/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/CurrentUserProfileServiceTests.kt index 75ca9b01..a315e800 100644 --- a/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/CurrentUserProfileServiceTests.kt +++ b/src/test/kotlin/edu/ntnu/idi/idatt/backend/users/profile/CurrentUserProfileServiceTests.kt @@ -2,7 +2,6 @@ package edu.ntnu.idi.idatt.backend.users.profile import edu.ntnu.idi.idatt.backend.classroom.application.ClassroomService import edu.ntnu.idi.idatt.backend.classroom.domain.Classroom -import edu.ntnu.idi.idatt.backend.classroom.infrastructure.ClassroomRepository import edu.ntnu.idi.idatt.backend.game.gameProgress.application.PupilGameProgressService import edu.ntnu.idi.idatt.backend.game.gameProgress.domain.PupilGameProgress import edu.ntnu.idi.idatt.backend.iam.application.CurrentUserResolver @@ -86,7 +85,7 @@ class CurrentUserProfileServiceTests { pupilProfileRepository, pupilGameProgressService, teacherProfileRepository, - classroomService + classroomService, ) val user = pupilUser(42L) val pupilProfile = @@ -114,7 +113,7 @@ class CurrentUserProfileServiceTests { val pupilProfileRepository = Mockito.mock(PupilProfileRepository::class.java) val pupilGameProgressService = Mockito.mock(PupilGameProgressService::class.java) val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) - val classroomService = Mockito.mock(ClassroomService::class.java) + val classroomService = Mockito.mock(ClassroomService::class.java) val classroom = Classroom(id = 101L, ownerTeacherId = 7L, title = "Class A") val service = currentUserProfileService( @@ -243,7 +242,7 @@ class CurrentUserProfileServiceTests { val pupilProfileRepository = Mockito.mock(PupilProfileRepository::class.java) val pupilGameProgressService = Mockito.mock(PupilGameProgressService::class.java) val teacherProfileRepository = Mockito.mock(TeacherProfileRepository::class.java) - val classroomService = Mockito.mock(ClassroomService::class.java) + val classroomService = Mockito.mock(ClassroomService::class.java) val service = currentUserProfileService( currentUserResolver, @@ -471,7 +470,7 @@ class CurrentUserProfileServiceTests { pupilProfileRepository: PupilProfileRepository, pupilGameProgressService: PupilGameProgressService, teacherProfileRepository: TeacherProfileRepository, - classroomService: ClassroomService + classroomService: ClassroomService, ): CurrentUserProfileService = CurrentUserProfileService( currentUserResolver,