fix: address copilot review issues - explicit imports, trailing commas, 404 for not-found search, tests for available-films endpoint

Agent-Logs-Url: https://github.com/devitq/movienight-backend/sessions/60cd9b1b-e3e1-46a1-bfd9-04a75bd0d569

Co-authored-by: devitq <118541411+devitq@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-05-03 12:02:31 +00:00
committed by GitHub
co-authored by devitq
parent f563c3e7cf
commit b7a246e0b4
6 changed files with 317 additions and 174 deletions
@@ -57,7 +57,8 @@ class FilmController(
FilmResponse.fromDomain( FilmResponse.fromDomain(
editFilmUseCase.edit( editFilmUseCase.edit(
id = id, id = id,
command = EditFilmCommand( command =
EditFilmCommand(
title = request.title, title = request.title,
description = request.description, description = request.description,
), ),
@@ -73,12 +74,10 @@ class FilmController(
@GetMapping("/{id}") @GetMapping("/{id}")
fun getById( fun getById(
@PathVariable id: UUID, @PathVariable id: UUID,
): FilmResponse = ): FilmResponse = FilmResponse.fromDomain(getFilmByIdUseCase.getById(id))
FilmResponse.fromDomain(getFilmByIdUseCase.getById(id))
@GetMapping @GetMapping
fun getAll(): List<FilmResponse> = fun getAll(): List<FilmResponse> = getAllFilmsUseCase.getAll().map { FilmResponse.fromDomain(it) }
getAllFilmsUseCase.getAll().map { FilmResponse.fromDomain(it) }
@GetMapping("/search") @GetMapping("/search")
fun searchByTitle( fun searchByTitle(
@@ -64,7 +64,8 @@ class FilmLibraryController(
fun getAllFilmsInLibrary( fun getAllFilmsInLibrary(
@PathVariable userId: UUID, @PathVariable userId: UUID,
): List<FilmResponse> { ): List<FilmResponse> {
val library = getFilmLibraryUseCase.getLibrary( val library =
getFilmLibraryUseCase.getLibrary(
GetFilmLibraryQuery(userId = userId), GetFilmLibraryQuery(userId = userId),
) )
val film = getFilmByIdUseCase.getById(library.filmId) val film = getFilmByIdUseCase.getById(library.filmId)
@@ -102,7 +103,8 @@ class FilmLibraryController(
fun getAvailableFilms( fun getAvailableFilms(
@PathVariable userId: UUID, @PathVariable userId: UUID,
): List<FilmResponse> { ): List<FilmResponse> {
val userLibrary = try { val userLibrary =
try {
getFilmLibraryUseCase.getLibrary( getFilmLibraryUseCase.getLibrary(
GetFilmLibraryQuery(userId = userId), GetFilmLibraryQuery(userId = userId),
) )
@@ -112,7 +114,8 @@ class FilmLibraryController(
val allFilms = getAllFilmsUseCase.getAll() val allFilms = getAllFilmsUseCase.getAll()
val availableFilms = if (userLibrary != null) { val availableFilms =
if (userLibrary != null) {
allFilms.filter { it.id != userLibrary.filmId } allFilms.filter { it.id != userLibrary.filmId }
} else { } else {
allFilms allFilms
@@ -28,7 +28,6 @@ class FilmService(
GetFilmByIdUseCase, GetFilmByIdUseCase,
GetAllFilmsUseCase, GetAllFilmsUseCase,
SearchFilmByTitleUseCase { SearchFilmByTitleUseCase {
override fun create(command: CreateFilmCommand): Film { override fun create(command: CreateFilmCommand): Film {
if (filmConfig.isBlocked(command.title)) { if (filmConfig.isBlocked(command.title)) {
throw BlockedValueException(target = "Film", field = "title") throw BlockedValueException(target = "Film", field = "title")
@@ -37,7 +36,8 @@ class FilmService(
throw BlockedValueException(target = "Film", field = "description") throw BlockedValueException(target = "Film", field = "description")
} }
val film = Film( val film =
Film(
id = idGenerator.generateId(), id = idGenerator.generateId(),
title = command.title, title = command.title,
description = command.description, description = command.description,
@@ -45,7 +45,10 @@ class FilmService(
return filmRepository.save(film) return filmRepository.save(film)
} }
override fun edit(id: UUID, command: EditFilmCommand): Film { override fun edit(
id: UUID,
command: EditFilmCommand,
): Film {
if (filmConfig.isBlocked(command.title)) { if (filmConfig.isBlocked(command.title)) {
throw BlockedValueException(target = "Film", field = "title") throw BlockedValueException(target = "Film", field = "title")
} }
@@ -63,9 +66,8 @@ class FilmService(
filmRepository.deleteById(id) filmRepository.deleteById(id)
} }
override fun getById(id: UUID): Film { override fun getById(id: UUID): Film =
return filmRepository.findById(id) ?: throw EntityNotFoundException(entity = "Film", id = id.toString()) filmRepository.findById(id) ?: throw EntityNotFoundException(entity = "Film", id = id.toString())
}
override fun getAll(): List<Film> = filmRepository.findAll() override fun getAll(): List<Film> = filmRepository.findAll()
@@ -9,13 +9,16 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMock
import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.test.context.SpringBootTest
import org.springframework.http.MediaType import org.springframework.http.MediaType
import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.* import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.* import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
@SpringBootTest @SpringBootTest
@AutoConfigureMockMvc @AutoConfigureMockMvc
class FilmControllerTest { class FilmControllerTest {
@Autowired @Autowired
private lateinit var mockMvc: MockMvc private lateinit var mockMvc: MockMvc
@@ -24,17 +27,18 @@ class FilmControllerTest {
@Test @Test
fun `create film should return 201 CREATED`() { fun `create film should return 201 CREATED`() {
val request = CreateFilmRequest( val request =
CreateFilmRequest(
title = "The Matrix", title = "The Matrix",
description = "A computer hacker learns about the true nature of reality", description = "A computer hacker learns about the true nature of reality",
) )
mockMvc.perform( mockMvc
.perform(
post("/api/films") post("/api/films")
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)) .content(objectMapper.writeValueAsString(request)),
) ).andExpect(status().isCreated)
.andExpect(status().isCreated)
.andExpect(jsonPath("$.title").value("The Matrix")) .andExpect(jsonPath("$.title").value("The Matrix"))
.andExpect(jsonPath("$.description").value("A computer hacker learns about the true nature of reality")) .andExpect(jsonPath("$.description").value("A computer hacker learns about the true nature of reality"))
.andExpect(jsonPath("$.id").exists()) .andExpect(jsonPath("$.id").exists())
@@ -42,86 +46,95 @@ class FilmControllerTest {
@Test @Test
fun `edit film should return updated film`() { fun `edit film should return updated film`() {
val createRequest = CreateFilmRequest( val createRequest =
CreateFilmRequest(
title = "Old Title", title = "Old Title",
description = "Old Description" description = "Old Description",
) )
val response = mockMvc.perform( val response =
mockMvc
.perform(
post("/api/films") post("/api/films")
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(createRequest)) .content(objectMapper.writeValueAsString(createRequest)),
).andReturn() ).andReturn()
val filmId = objectMapper.readTree(response.response.contentAsString).get("id").asText() val filmId = objectMapper.readTree(response.response.contentAsString).get("id").asText()
val editRequest = EditFilmRequest( val editRequest =
EditFilmRequest(
title = "New Title", title = "New Title",
description = "New Description", description = "New Description",
) )
mockMvc.perform( mockMvc
.perform(
patch("/api/films/$filmId") patch("/api/films/$filmId")
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(editRequest)) .content(objectMapper.writeValueAsString(editRequest)),
) ).andExpect(status().isOk)
.andExpect(status().isOk)
.andExpect(jsonPath("$.title").value("New Title")) .andExpect(jsonPath("$.title").value("New Title"))
.andExpect(jsonPath("$.description").value("New Description")) .andExpect(jsonPath("$.description").value("New Description"))
} }
@Test @Test
fun `search film by title should return film`() { fun `search film by title should return film`() {
val request = CreateFilmRequest( val request =
CreateFilmRequest(
title = "Inception", title = "Inception",
description = "Dream within a dream" description = "Dream within a dream",
) )
mockMvc.perform( mockMvc.perform(
post("/api/films") post("/api/films")
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)) .content(objectMapper.writeValueAsString(request)),
) )
mockMvc.perform( mockMvc
.perform(
get("/api/films/search") get("/api/films/search")
.param("title", "Inception") .param("title", "Inception"),
) ).andExpect(status().isOk)
.andExpect(status().isOk)
.andExpect(jsonPath("$.title").value("Inception")) .andExpect(jsonPath("$.title").value("Inception"))
.andExpect(jsonPath("$.description").value("Dream within a dream")) .andExpect(jsonPath("$.description").value("Dream within a dream"))
} }
@Test @Test
fun `search film by non-existent title should return empty`() { fun `search film by non-existent title should return 404`() {
mockMvc.perform( mockMvc
.perform(
get("/api/films/search") get("/api/films/search")
.param("title", "NonExistentFilm12345") .param("title", "NonExistentFilm12345"),
) ).andExpect(status().isNotFound)
.andExpect(status().isOk)
.andExpect(content().string(""))
} }
@Test @Test
fun `delete film should return 204 NO CONTENT`() { fun `delete film should return 204 NO CONTENT`() {
val request = CreateFilmRequest( val request =
CreateFilmRequest(
title = "Film To Delete", title = "Film To Delete",
description = "This film will be deleted" description = "This film will be deleted",
) )
val response = mockMvc.perform( val response =
mockMvc
.perform(
post("/api/films") post("/api/films")
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)) .content(objectMapper.writeValueAsString(request)),
).andReturn() ).andReturn()
val filmId = objectMapper.readTree(response.response.contentAsString).get("id").asText() val filmId = objectMapper.readTree(response.response.contentAsString).get("id").asText()
mockMvc.perform(delete("/api/films/$filmId")) mockMvc
.perform(delete("/api/films/$filmId"))
.andExpect(status().isNoContent()) .andExpect(status().isNoContent())
mockMvc.perform(get("/api/films/search").param("title", "Film To Delete")) mockMvc
.andExpect(status().isOk) .perform(
.andExpect(content().string("")) get("/api/films/search").param("title", "Film To Delete"),
).andExpect(status().isNotFound)
} }
} }
@@ -10,7 +10,9 @@ import org.springframework.boot.test.context.SpringBootTest
import org.springframework.http.MediaType import org.springframework.http.MediaType
import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import org.springframework.transaction.annotation.Transactional import org.springframework.transaction.annotation.Transactional
@@ -18,7 +20,6 @@ import org.springframework.transaction.annotation.Transactional
@AutoConfigureMockMvc @AutoConfigureMockMvc
@Transactional @Transactional
class FilmLibraryControllerTest { class FilmLibraryControllerTest {
@Autowired @Autowired
private lateinit var mockMvc: MockMvc private lateinit var mockMvc: MockMvc
@@ -27,52 +28,64 @@ class FilmLibraryControllerTest {
@Test @Test
fun `add film to library should work`() { fun `add film to library should work`() {
val userRequest = CreateUserRequest( val userRequest =
CreateUserRequest(
name = "Film Adder", name = "Film Adder",
email = "adder@example.com", email = "adder@example.com",
) )
val userResponse = mockMvc.perform( val userResponse =
mockMvc
.perform(
post("/api/users") post("/api/users")
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(userRequest)), .content(objectMapper.writeValueAsString(userRequest)),
).andReturn() ).andReturn()
val userId = objectMapper.readTree(userResponse.response.contentAsString).get("id").asText() val userId = objectMapper.readTree(userResponse.response.contentAsString).get("id").asText()
val filmRequest = CreateFilmRequest( val filmRequest =
CreateFilmRequest(
title = "Library Film", title = "Library Film",
description = "Film description", description = "Film description",
) )
val filmResponse = mockMvc.perform( val filmResponse =
mockMvc
.perform(
post("/api/films") post("/api/films")
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(filmRequest)), .content(objectMapper.writeValueAsString(filmRequest)),
).andReturn() ).andReturn()
val filmId = objectMapper.readTree(filmResponse.response.contentAsString).get("id").asText() val filmId = objectMapper.readTree(filmResponse.response.contentAsString).get("id").asText()
mockMvc.perform( mockMvc
.perform(
post("/api/users/$userId/library/films/$filmId"), post("/api/users/$userId/library/films/$filmId"),
) ).andExpect(status().isCreated())
.andExpect(status().isCreated())
} }
@Test @Test
fun `remove film from library should return 204`() { fun `remove film from library should return 204`() {
val userRequest = CreateUserRequest( val userRequest =
CreateUserRequest(
name = "Remove Film", name = "Remove Film",
email = "remove@example.com", email = "remove@example.com",
) )
val userResponse = mockMvc.perform( val userResponse =
mockMvc
.perform(
post("/api/users") post("/api/users")
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(userRequest)), .content(objectMapper.writeValueAsString(userRequest)),
).andReturn() ).andReturn()
val userId = objectMapper.readTree(userResponse.response.contentAsString).get("id").asText() val userId = objectMapper.readTree(userResponse.response.contentAsString).get("id").asText()
val filmRequest = CreateFilmRequest( val filmRequest =
CreateFilmRequest(
title = "Film To Remove", title = "Film To Remove",
description = "Will be removed", description = "Will be removed",
) )
val filmResponse = mockMvc.perform( val filmResponse =
mockMvc
.perform(
post("/api/films") post("/api/films")
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(filmRequest)), .content(objectMapper.writeValueAsString(filmRequest)),
@@ -80,7 +93,112 @@ class FilmLibraryControllerTest {
val filmId = objectMapper.readTree(filmResponse.response.contentAsString).get("id").asText() val filmId = objectMapper.readTree(filmResponse.response.contentAsString).get("id").asText()
mockMvc.perform(post("/api/users/$userId/library/films/$filmId")) mockMvc.perform(post("/api/users/$userId/library/films/$filmId"))
mockMvc.perform(delete("/api/users/$userId/library/films/$filmId")) mockMvc
.perform(delete("/api/users/$userId/library/films/$filmId"))
.andExpect(status().isNoContent()) .andExpect(status().isNoContent())
} }
@Test
fun `get available films should exclude film in user's library`() {
val userRequest =
CreateUserRequest(
name = "Available Films User",
email = "availablefilms@example.com",
)
val userResponse =
mockMvc
.perform(
post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(userRequest)),
).andReturn()
val userId = objectMapper.readTree(userResponse.response.contentAsString).get("id").asText()
val film1Request =
CreateFilmRequest(
title = "Film In Library",
description = "This will be in the library",
)
val film1Response =
mockMvc
.perform(
post("/api/films")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(film1Request)),
).andReturn()
val film1Id = objectMapper.readTree(film1Response.response.contentAsString).get("id").asText()
val film2Request =
CreateFilmRequest(
title = "Film Not In Library",
description = "This will not be in the library",
)
val film2Response =
mockMvc
.perform(
post("/api/films")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(film2Request)),
).andReturn()
val film2Id = objectMapper.readTree(film2Response.response.contentAsString).get("id").asText()
mockMvc.perform(post("/api/users/$userId/library/films/$film1Id"))
val result =
mockMvc
.perform(
get("/api/users/$userId/library/available-films"),
).andExpect(status().isOk)
.andReturn()
val responseBody = result.response.contentAsString
val films = objectMapper.readTree(responseBody)
val returnedIds = (0 until films.size()).map { films[it].get("id").asText() }
assert(!returnedIds.contains(film1Id)) { "Film in library should not appear in available films" }
assert(returnedIds.contains(film2Id)) { "Film not in library should appear in available films" }
}
@Test
fun `get available films for user without library returns all films`() {
val userRequest =
CreateUserRequest(
name = "No Library User",
email = "nolibrary@example.com",
)
val userResponse =
mockMvc
.perform(
post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(userRequest)),
).andReturn()
val userId = objectMapper.readTree(userResponse.response.contentAsString).get("id").asText()
val filmRequest =
CreateFilmRequest(
title = "Available Film",
description = "Should appear in available films",
)
val filmResponse =
mockMvc
.perform(
post("/api/films")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(filmRequest)),
).andReturn()
val filmId = objectMapper.readTree(filmResponse.response.contentAsString).get("id").asText()
val result =
mockMvc
.perform(
get("/api/users/$userId/library/available-films"),
).andExpect(status().isOk)
.andExpect(jsonPath("$[*].id").isArray)
.andReturn()
val responseBody = result.response.contentAsString
val films = objectMapper.readTree(responseBody)
val returnedIds = (0 until films.size()).map { films[it].get("id").asText() }
assert(returnedIds.contains(filmId)) { "Film should appear in available films when user has no library" }
}
} }
@@ -20,7 +20,6 @@ import org.springframework.transaction.annotation.Transactional
@AutoConfigureMockMvc @AutoConfigureMockMvc
@Transactional @Transactional
class UserControllerTest { class UserControllerTest {
@Autowired @Autowired
private lateinit var mockMvc: MockMvc private lateinit var mockMvc: MockMvc
@@ -29,17 +28,18 @@ class UserControllerTest {
@Test @Test
fun `create user should return 201 CREATED`() { fun `create user should return 201 CREATED`() {
val request = CreateUserRequest( val request =
CreateUserRequest(
name = "John Doe", name = "John Doe",
email = "john@example.com", email = "john@example.com",
) )
mockMvc.perform( mockMvc
.perform(
post("/api/users") post("/api/users")
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)), .content(objectMapper.writeValueAsString(request)),
) ).andExpect(status().isCreated)
.andExpect(status().isCreated)
.andExpect(jsonPath("$.name").value("John Doe")) .andExpect(jsonPath("$.name").value("John Doe"))
.andExpect(jsonPath("$.email").value("john@example.com")) .andExpect(jsonPath("$.email").value("john@example.com"))
.andExpect(jsonPath("$.id").exists()) .andExpect(jsonPath("$.id").exists())
@@ -47,12 +47,15 @@ class UserControllerTest {
@Test @Test
fun `edit user should return updated user`() { fun `edit user should return updated user`() {
val createRequest = CreateUserRequest( val createRequest =
CreateUserRequest(
name = "Old Name", name = "Old Name",
email = "edit@example.com", email = "edit@example.com",
) )
val response = mockMvc.perform( val response =
mockMvc
.perform(
post("/api/users") post("/api/users")
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(createRequest)), .content(objectMapper.writeValueAsString(createRequest)),
@@ -62,24 +65,27 @@ class UserControllerTest {
val editRequest = EditUserRequest(name = "New Name") val editRequest = EditUserRequest(name = "New Name")
mockMvc.perform( mockMvc
.perform(
patch("/api/users/$userId") patch("/api/users/$userId")
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(editRequest)), .content(objectMapper.writeValueAsString(editRequest)),
) ).andExpect(status().isOk)
.andExpect(status().isOk)
.andExpect(jsonPath("$.name").value("New Name")) .andExpect(jsonPath("$.name").value("New Name"))
.andExpect(jsonPath("$.email").value("edit@example.com")) .andExpect(jsonPath("$.email").value("edit@example.com"))
} }
@Test @Test
fun `delete user should return 204 NO CONTENT`() { fun `delete user should return 204 NO CONTENT`() {
val request = CreateUserRequest( val request =
CreateUserRequest(
name = "User To Delete", name = "User To Delete",
email = "delete@example.com", email = "delete@example.com",
) )
val response = mockMvc.perform( val response =
mockMvc
.perform(
post("/api/users") post("/api/users")
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)), .content(objectMapper.writeValueAsString(request)),
@@ -87,14 +93,16 @@ class UserControllerTest {
val userId = objectMapper.readTree(response.response.contentAsString).get("id").asText() val userId = objectMapper.readTree(response.response.contentAsString).get("id").asText()
mockMvc.perform(delete("/api/users/$userId")) mockMvc
.perform(delete("/api/users/$userId"))
.andExpect(status().isNoContent()) .andExpect(status().isNoContent())
} }
@Test @Test
fun `delete non-existent user should return 404`() { fun `delete non-existent user should return 404`() {
val nonExistentId = "123e4567-e89b-12d3-a456-426614174000" val nonExistentId = "123e4567-e89b-12d3-a456-426614174000"
mockMvc.perform(delete("/api/users/$nonExistentId")) mockMvc
.perform(delete("/api/users/$nonExistentId"))
.andExpect(status().isNotFound()) .andExpect(status().isNotFound())
} }
} }