<type>(scope): <description>

[body]

[footer(s)]
This commit is contained in:
ITQ
2026-05-20 03:38:30 +03:00
parent a48463b723
commit ea7e66c1a0
18 changed files with 291 additions and 87 deletions
@@ -3,8 +3,10 @@ package com.project.movienight
import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.properties.ConfigurationPropertiesScan import org.springframework.boot.context.properties.ConfigurationPropertiesScan
import org.springframework.boot.runApplication import org.springframework.boot.runApplication
import org.springframework.scheduling.annotation.EnableScheduling
@SpringBootApplication @SpringBootApplication
@EnableScheduling
@ConfigurationPropertiesScan("com.project.movienight.config") @ConfigurationPropertiesScan("com.project.movienight.config")
class MovieNightApplication class MovieNightApplication
@@ -25,6 +25,12 @@ import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController
import java.util.UUID import java.util.UUID
private fun String.toContentTypeOrFilm(): com.project.movienight.domain.model.ContentType =
runCatching {
com.project.movienight.domain.model.ContentType
.valueOf(this)
}.getOrDefault(com.project.movienight.domain.model.ContentType.FILM)
@RestController @RestController
@RequestMapping("/api/films") @RequestMapping("/api/films")
class FilmController( class FilmController(
@@ -45,6 +51,16 @@ class FilmController(
CreateFilmCommand( CreateFilmCommand(
title = request.title, title = request.title,
description = request.description, description = request.description,
contentType = request.contentType.toContentTypeOrFilm(),
releaseYear = request.releaseYear,
genres = request.genres,
cast = request.cast,
directors = request.directors,
imdbRating = request.imdbRating,
platformRating = request.platformRating,
externalUrl = request.externalUrl,
jellyfinItemId = request.jellyfinItemId,
jellyfinLibraryId = request.jellyfinLibraryId,
), ),
), ),
) )
@@ -61,6 +77,16 @@ class FilmController(
EditFilmCommand( EditFilmCommand(
title = request.title, title = request.title,
description = request.description, description = request.description,
contentType = request.contentType.toContentTypeOrFilm(),
releaseYear = request.releaseYear,
genres = request.genres,
cast = request.cast,
directors = request.directors,
imdbRating = request.imdbRating,
platformRating = request.platformRating,
externalUrl = request.externalUrl,
jellyfinItemId = request.jellyfinItemId,
jellyfinLibraryId = request.jellyfinLibraryId,
), ),
), ),
) )
@@ -11,6 +11,9 @@ import com.project.movienight.application.ports.input.GetAllFilmsUseCase
import com.project.movienight.application.ports.input.GetFilmByIdUseCase import com.project.movienight.application.ports.input.GetFilmByIdUseCase
import com.project.movienight.application.ports.input.GetFilmLibraryQuery import com.project.movienight.application.ports.input.GetFilmLibraryQuery
import com.project.movienight.application.ports.input.GetFilmLibraryUseCase import com.project.movienight.application.ports.input.GetFilmLibraryUseCase
import com.project.movienight.application.ports.input.ListFilmLibraryEntriesUseCase
import com.project.movienight.application.ports.input.MarkFilmViewedCommand
import com.project.movienight.application.ports.input.MarkFilmViewedUseCase
import com.project.movienight.application.ports.input.RemoveFilmFromLibraryCommand import com.project.movienight.application.ports.input.RemoveFilmFromLibraryCommand
import com.project.movienight.application.ports.input.RemoveFilmFromLibraryUseCase import com.project.movienight.application.ports.input.RemoveFilmFromLibraryUseCase
import com.project.movienight.domain.exception.EntityNotFoundException import com.project.movienight.domain.exception.EntityNotFoundException
@@ -30,10 +33,10 @@ import java.util.UUID
class FilmLibraryController( class FilmLibraryController(
private val createFilmLibraryUseCase: CreateFilmLibraryUseCase, private val createFilmLibraryUseCase: CreateFilmLibraryUseCase,
private val addFilmToLibraryUseCase: AddFilmToLibraryUseCase, private val addFilmToLibraryUseCase: AddFilmToLibraryUseCase,
private val markFilmViewedUseCase: MarkFilmViewedUseCase,
private val removeFilmFromLibraryUseCase: RemoveFilmFromLibraryUseCase, private val removeFilmFromLibraryUseCase: RemoveFilmFromLibraryUseCase,
private val getFilmLibraryUseCase: GetFilmLibraryUseCase, private val getFilmLibraryUseCase: GetFilmLibraryUseCase,
private val getFilmByIdUseCase: GetFilmByIdUseCase, private val listFilmLibraryEntriesUseCase: ListFilmLibraryEntriesUseCase,
private val getAllFilmsUseCase: GetAllFilmsUseCase,
) { ) {
@PostMapping @PostMapping
@ResponseStatus(HttpStatus.CREATED) @ResponseStatus(HttpStatus.CREATED)
@@ -60,18 +63,10 @@ class FilmLibraryController(
), ),
) )
@GetMapping("/films") @GetMapping("/entries")
fun getAllFilmsInLibrary( fun list(
@PathVariable userId: UUID, @PathVariable userId: UUID,
): List<FilmResponse> { ): List<FilmLibraryResponse> = listFilmLibraryEntriesUseCase.list(userId).map { FilmLibraryResponse.fromDomain(it) }
val library =
getFilmLibraryUseCase.getLibrary(
GetFilmLibraryQuery(userId = userId),
)
val film = getFilmByIdUseCase.getById(library.filmId)
return listOf(FilmResponse.fromDomain(film))
}
@PostMapping("/films/{filmId}") @PostMapping("/films/{filmId}")
@ResponseStatus(HttpStatus.CREATED) @ResponseStatus(HttpStatus.CREATED)
@@ -88,6 +83,20 @@ class FilmLibraryController(
), ),
) )
@PostMapping("/films/{filmId}/viewed")
fun markViewed(
@PathVariable userId: UUID,
@PathVariable filmId: UUID,
): FilmLibraryResponse =
FilmLibraryResponse.fromDomain(
markFilmViewedUseCase.markViewed(
MarkFilmViewedCommand(
userId = userId,
filmId = filmId,
),
),
)
@DeleteMapping("/films/{filmId}") @DeleteMapping("/films/{filmId}")
@ResponseStatus(HttpStatus.NO_CONTENT) @ResponseStatus(HttpStatus.NO_CONTENT)
fun removeFilm( fun removeFilm(
@@ -64,6 +64,7 @@ class UserController(
command = command =
EditUserCommand( EditUserCommand(
name = request.name, name = request.name,
jellyfinUserId = request.jellyfinUserId,
), ),
), ),
) )
@@ -3,4 +3,14 @@ package com.project.movienight.adapters.web.dto.request
data class CreateFilmRequest( data class CreateFilmRequest(
val title: String, val title: String,
val description: String, val description: String,
val contentType: String = "FILM",
val releaseYear: Int? = null,
val genres: List<String> = emptyList(),
val cast: List<String> = emptyList(),
val directors: List<String> = emptyList(),
val imdbRating: Double? = null,
val platformRating: Double? = null,
val externalUrl: String? = null,
val jellyfinItemId: String? = null,
val jellyfinLibraryId: String? = null,
) )
@@ -3,4 +3,14 @@ package com.project.movienight.adapters.web.dto.request
data class EditFilmRequest( data class EditFilmRequest(
val title: String, val title: String,
val description: String, val description: String,
val contentType: String = "FILM",
val releaseYear: Int? = null,
val genres: List<String> = emptyList(),
val cast: List<String> = emptyList(),
val directors: List<String> = emptyList(),
val imdbRating: Double? = null,
val platformRating: Double? = null,
val externalUrl: String? = null,
val jellyfinItemId: String? = null,
val jellyfinLibraryId: String? = null,
) )
@@ -2,4 +2,5 @@ package com.project.movienight.adapters.web.dto.request
data class EditUserRequest( data class EditUserRequest(
val name: String, val name: String,
val jellyfinUserId: String? = null,
) )
@@ -9,6 +9,7 @@ data class FilmLibraryResponse(
val filmId: UUID, val filmId: UUID,
val comment: String?, val comment: String?,
val isViewed: Boolean, val isViewed: Boolean,
val watchedAt: java.time.LocalDateTime?,
) { ) {
companion object { companion object {
fun fromDomain(filmLibrary: FilmLibrary): FilmLibraryResponse = fun fromDomain(filmLibrary: FilmLibrary): FilmLibraryResponse =
@@ -18,6 +19,7 @@ data class FilmLibraryResponse(
filmId = filmLibrary.filmId, filmId = filmLibrary.filmId,
comment = filmLibrary.comment, comment = filmLibrary.comment,
isViewed = filmLibrary.isViewed, isViewed = filmLibrary.isViewed,
watchedAt = filmLibrary.watchedAt,
) )
} }
} }
@@ -1,5 +1,6 @@
package com.project.movienight.adapters.web.dto.response package com.project.movienight.adapters.web.dto.response
import com.project.movienight.domain.model.ContentType
import com.project.movienight.domain.model.Film import com.project.movienight.domain.model.Film
import java.util.UUID import java.util.UUID
@@ -7,6 +8,16 @@ data class FilmResponse(
val id: UUID, val id: UUID,
val title: String, val title: String,
val description: String, val description: String,
val contentType: ContentType,
val releaseYear: Int?,
val genres: List<String>,
val cast: List<String>,
val directors: List<String>,
val imdbRating: Double?,
val platformRating: Double?,
val externalUrl: String?,
val jellyfinItemId: String?,
val jellyfinLibraryId: String?,
) { ) {
companion object { companion object {
fun fromDomain(film: Film): FilmResponse = fun fromDomain(film: Film): FilmResponse =
@@ -14,6 +25,16 @@ data class FilmResponse(
id = film.id, id = film.id,
title = film.title, title = film.title,
description = film.description, description = film.description,
contentType = film.contentType,
releaseYear = film.releaseYear,
genres = film.genres,
cast = film.cast,
directors = film.directors,
imdbRating = film.imdbRating,
platformRating = film.platformRating,
externalUrl = film.externalUrl,
jellyfinItemId = film.jellyfinItemId,
jellyfinLibraryId = film.jellyfinLibraryId,
) )
} }
} }
@@ -7,6 +7,7 @@ data class UserResponse(
val id: UUID, val id: UUID,
val name: String, val name: String,
val email: String, val email: String,
val jellyfinUserId: String?,
) { ) {
companion object { companion object {
fun fromDomain(user: User): UserResponse = fun fromDomain(user: User): UserResponse =
@@ -14,6 +15,7 @@ data class UserResponse(
id = user.id, id = user.id,
name = user.name, name = user.name,
email = user.email, email = user.email,
jellyfinUserId = user.jellyfinUserId,
) )
} }
} }
@@ -1,6 +1,7 @@
package com.project.movienight.application.ports.input package com.project.movienight.application.ports.input
import com.project.movienight.domain.model.FilmLibrary import com.project.movienight.domain.model.FilmLibrary
import java.time.LocalDateTime
import java.util.UUID import java.util.UUID
interface CreateFilmLibraryUseCase { interface CreateFilmLibraryUseCase {
@@ -21,6 +22,16 @@ data class AddFilmToLibraryCommand(
val filmId: UUID, val filmId: UUID,
) )
interface MarkFilmViewedUseCase {
fun markViewed(command: MarkFilmViewedCommand): FilmLibrary
}
data class MarkFilmViewedCommand(
val userId: UUID,
val filmId: UUID,
val watchedAt: LocalDateTime? = null,
)
interface RemoveFilmFromLibraryUseCase { interface RemoveFilmFromLibraryUseCase {
fun removeFilm(command: RemoveFilmFromLibraryCommand): FilmLibrary fun removeFilm(command: RemoveFilmFromLibraryCommand): FilmLibrary
} }
@@ -38,3 +49,7 @@ interface GetFilmLibraryUseCase {
data class GetFilmLibraryQuery( data class GetFilmLibraryQuery(
val userId: UUID, val userId: UUID,
) )
interface ListFilmLibraryEntriesUseCase {
fun list(userId: UUID): List<FilmLibrary>
}
@@ -1,5 +1,6 @@
package com.project.movienight.application.ports.input package com.project.movienight.application.ports.input
import com.project.movienight.domain.model.ContentType
import com.project.movienight.domain.model.Film import com.project.movienight.domain.model.Film
import java.util.UUID import java.util.UUID
@@ -10,6 +11,16 @@ interface CreateFilmUseCase {
data class CreateFilmCommand( data class CreateFilmCommand(
val title: String, val title: String,
val description: String, val description: String,
val contentType: ContentType = ContentType.FILM,
val releaseYear: Int? = null,
val genres: List<String> = emptyList(),
val cast: List<String> = emptyList(),
val directors: List<String> = emptyList(),
val imdbRating: Double? = null,
val platformRating: Double? = null,
val externalUrl: String? = null,
val jellyfinItemId: String? = null,
val jellyfinLibraryId: String? = null,
) )
interface EditFilmUseCase { interface EditFilmUseCase {
@@ -22,6 +33,16 @@ interface EditFilmUseCase {
data class EditFilmCommand( data class EditFilmCommand(
val title: String, val title: String,
val description: String, val description: String,
val contentType: ContentType = ContentType.FILM,
val releaseYear: Int? = null,
val genres: List<String> = emptyList(),
val cast: List<String> = emptyList(),
val directors: List<String> = emptyList(),
val imdbRating: Double? = null,
val platformRating: Double? = null,
val externalUrl: String? = null,
val jellyfinItemId: String? = null,
val jellyfinLibraryId: String? = null,
) )
interface DeleteFilmUseCase { interface DeleteFilmUseCase {
@@ -21,6 +21,7 @@ interface EditUserUseCase {
data class EditUserCommand( data class EditUserCommand(
val name: String, val name: String,
val jellyfinUserId: String? = null,
) )
interface DeleteUserUseCase { interface DeleteUserUseCase {
@@ -1,11 +1,15 @@
package com.project.movienight.application.services package com.project.movienight.application.services
import com.project.movienight.adapters.metrics.BusinessMetricsService
import com.project.movienight.application.ports.input.AddFilmToLibraryCommand import com.project.movienight.application.ports.input.AddFilmToLibraryCommand
import com.project.movienight.application.ports.input.AddFilmToLibraryUseCase import com.project.movienight.application.ports.input.AddFilmToLibraryUseCase
import com.project.movienight.application.ports.input.CreateFilmLibraryCommand import com.project.movienight.application.ports.input.CreateFilmLibraryCommand
import com.project.movienight.application.ports.input.CreateFilmLibraryUseCase import com.project.movienight.application.ports.input.CreateFilmLibraryUseCase
import com.project.movienight.application.ports.input.GetFilmLibraryQuery import com.project.movienight.application.ports.input.GetFilmLibraryQuery
import com.project.movienight.application.ports.input.GetFilmLibraryUseCase import com.project.movienight.application.ports.input.GetFilmLibraryUseCase
import com.project.movienight.application.ports.input.ListFilmLibraryEntriesUseCase
import com.project.movienight.application.ports.input.MarkFilmViewedCommand
import com.project.movienight.application.ports.input.MarkFilmViewedUseCase
import com.project.movienight.application.ports.input.RemoveFilmFromLibraryCommand import com.project.movienight.application.ports.input.RemoveFilmFromLibraryCommand
import com.project.movienight.application.ports.input.RemoveFilmFromLibraryUseCase import com.project.movienight.application.ports.input.RemoveFilmFromLibraryUseCase
import com.project.movienight.application.ports.output.FilmLibraryRepositoryPort import com.project.movienight.application.ports.output.FilmLibraryRepositoryPort
@@ -20,70 +24,119 @@ import java.util.UUID
class FilmLibraryService( class FilmLibraryService(
private val filmLibraryRepository: FilmLibraryRepositoryPort, private val filmLibraryRepository: FilmLibraryRepositoryPort,
private val idGenerator: IdGenerator, private val idGenerator: IdGenerator,
private val businessMetricsService: BusinessMetricsService,
) : CreateFilmLibraryUseCase, ) : CreateFilmLibraryUseCase,
AddFilmToLibraryUseCase, AddFilmToLibraryUseCase,
MarkFilmViewedUseCase,
RemoveFilmFromLibraryUseCase, RemoveFilmFromLibraryUseCase,
GetFilmLibraryUseCase { GetFilmLibraryUseCase,
ListFilmLibraryEntriesUseCase {
override fun create(command: CreateFilmLibraryCommand): FilmLibrary { override fun create(command: CreateFilmLibraryCommand): FilmLibrary {
val existingLibrary = findByUserId(command.userId) findByUserId(command.userId)?.let { return it }
if (existingLibrary != null) {
return existingLibrary
}
return filmLibraryRepository.save( val libraryId = idGenerator.generateId()
val saved =
filmLibraryRepository.save(
FilmLibrary( FilmLibrary(
id = idGenerator.generateId(), id = libraryId,
userId = command.userId, userId = command.userId,
filmId = idGenerator.generateId(), filmId = libraryId,
comment = command.name, comment = command.name,
isViewed = false, isViewed = false,
watchedAt = null,
), ),
) )
businessMetricsService.recordLibraryEvent()
return saved
} }
override fun addFilm(command: AddFilmToLibraryCommand): FilmLibrary { override fun addFilm(command: AddFilmToLibraryCommand): FilmLibrary {
val existingLibrary = findByUserId(command.userId) val existingEntry = findByUserAndFilmId(command.userId, command.filmId)
if (existingLibrary == null) { if (existingEntry != null) {
return filmLibraryRepository.save( val saved =
filmLibraryRepository.save(
existingEntry.copy(
isViewed = false,
watchedAt = null,
),
)
businessMetricsService.recordLibraryEvent()
return saved
}
val saved =
filmLibraryRepository.save(
FilmLibrary( FilmLibrary(
id = idGenerator.generateId(), id = idGenerator.generateId(),
userId = command.userId, userId = command.userId,
filmId = command.filmId, filmId = command.filmId,
comment = null, comment = null,
isViewed = false, isViewed = false,
watchedAt = null,
), ),
) )
} businessMetricsService.recordLibraryEvent()
return saved
return filmLibraryRepository.save(
existingLibrary.copy(
filmId = command.filmId,
isViewed = false,
),
)
} }
override fun removeFilm(command: RemoveFilmFromLibraryCommand): FilmLibrary { override fun removeFilm(command: RemoveFilmFromLibraryCommand): FilmLibrary {
val existingLibrary = val existingLibrary =
findByUserId(command.userId) if (command.libraryId != null) {
?: throw EntityNotFoundException(entity = "Film library", id = command.userId.toString()) filmLibraryRepository.findById(command.libraryId)
?: throw EntityNotFoundException(entity = "Film library", id = command.libraryId.toString())
if (command.libraryId != null && command.libraryId != existingLibrary.id) { } else {
throw EntityNotFoundException(entity = "Film library", id = command.libraryId.toString()) findByUserAndFilmId(command.userId, command.filmId)
?: throw EntityNotFoundException(entity = "Film library", id = command.filmId.toString())
} }
if (existingLibrary.filmId != command.filmId) { if (existingLibrary.userId != command.userId || existingLibrary.filmId != command.filmId) {
throw DomainException("Film with id ${command.filmId} not found in user's library") throw DomainException("Film with id ${command.filmId} not found in user's library")
} }
filmLibraryRepository.deleteById(existingLibrary.id) filmLibraryRepository.deleteById(existingLibrary.id)
businessMetricsService.recordLibraryEvent()
return existingLibrary return existingLibrary
} }
override fun markViewed(command: MarkFilmViewedCommand): FilmLibrary {
val existingEntry = findByUserAndFilmId(command.userId, command.filmId)
val watchedAt = command.watchedAt ?: java.time.LocalDateTime.now()
val saved =
if (existingEntry == null) {
filmLibraryRepository.save(
FilmLibrary(
id = idGenerator.generateId(),
userId = command.userId,
filmId = command.filmId,
comment = null,
isViewed = true,
watchedAt = watchedAt,
),
)
} else {
filmLibraryRepository.save(
existingEntry.copy(
isViewed = true,
watchedAt = watchedAt,
),
)
}
businessMetricsService.recordLibraryEvent()
return saved
}
override fun getLibrary(query: GetFilmLibraryQuery): FilmLibrary = override fun getLibrary(query: GetFilmLibraryQuery): FilmLibrary =
findByUserId(query.userId) findByUserId(query.userId)
?: throw EntityNotFoundException(entity = "Film library", id = query.userId.toString()) ?: throw EntityNotFoundException(entity = "Film library", id = query.userId.toString())
override fun list(userId: UUID): List<FilmLibrary> = filmLibraryRepository.findAll().filter { it.userId == userId }
private fun findByUserId(userId: UUID): FilmLibrary? = private fun findByUserId(userId: UUID): FilmLibrary? =
filmLibraryRepository.findAll().firstOrNull { it.userId == userId } filmLibraryRepository.findAll().firstOrNull { it.userId == userId }
private fun findByUserAndFilmId(
userId: UUID,
filmId: UUID,
): FilmLibrary? = filmLibraryRepository.findAll().firstOrNull { it.userId == userId && it.filmId == filmId }
} }
@@ -61,16 +61,18 @@ class FilmService(
id = idGenerator.generateId(), id = idGenerator.generateId(),
title = command.title, title = command.title,
description = command.description, description = command.description,
contentType = command.contentType,
releaseYear = command.releaseYear,
genres = command.genres,
cast = command.cast,
directors = command.directors,
imdbRating = command.imdbRating,
platformRating = command.platformRating,
externalUrl = command.externalUrl,
jellyfinItemId = command.jellyfinItemId,
jellyfinLibraryId = command.jellyfinLibraryId,
) )
val saved = filmRepository.save(film) return filmRepository.save(film)
filmCreatedCounter.increment()
log.info("Film created: id='{}', title='{}'", saved.id, saved.title)
return saved
} finally {
sample.stop(createFilmTimer)
}
} }
override fun edit( override fun edit(
@@ -100,20 +102,23 @@ class FilmService(
throw EntityNotFoundException(entity = "Film", id = id.toString()) throw EntityNotFoundException(entity = "Film", id = id.toString())
} }
val updatedFilm = film =
film.copy( film.copy(
title = command.title, title = command.title,
description = command.description, description = command.description,
contentType = command.contentType,
releaseYear = command.releaseYear,
genres = command.genres,
cast = command.cast,
directors = command.directors,
imdbRating = command.imdbRating,
platformRating = command.platformRating,
externalUrl = command.externalUrl,
jellyfinItemId = command.jellyfinItemId,
jellyfinLibraryId = command.jellyfinLibraryId,
) )
val saved = filmRepository.save(updatedFilm)
filmEditedCounter.increment() return filmRepository.save(film)
log.info("Film edited: id='{}'", saved.id)
return saved
} finally {
sample.stop(editFilmTimer)
}
} }
override fun delete(id: UUID) { override fun delete(id: UUID) {
@@ -37,6 +37,7 @@ class UserService(
name = command.name, name = command.name,
email = command.email, email = command.email,
library = null, library = null,
jellyfinUserId = null,
) )
return userRepository.save(user) return userRepository.save(user)
} }
@@ -50,7 +51,13 @@ class UserService(
} }
var user = userRepository.findById(id) ?: throw EntityNotFoundException(entity = "User", id = id.toString()) var user = userRepository.findById(id) ?: throw EntityNotFoundException(entity = "User", id = id.toString())
user = user.copy(name = command.name)
user =
user.copy(
name = command.name,
jellyfinUserId = command.jellyfinUserId ?: user.jellyfinUserId,
)
return userRepository.save(user) return userRepository.save(user)
} }
+8
View File
@@ -95,6 +95,14 @@ info:
description: MovieNight backend service description: MovieNight backend service
version: ${project.version:unknown} version: ${project.version:unknown}
integrations:
jellyfin:
enabled: ${JELLYFIN_SYNC_ENABLED:false}
base-url: ${JELLYFIN_BASE_URL:}
api-key: ${JELLYFIN_API_KEY:}
sync-interval-ms: ${JELLYFIN_SYNC_INTERVAL_MS:1800000}
request-timeout-ms: ${JELLYFIN_REQUEST_TIMEOUT_MS:20000}
services: services:
user: user:
blocked-names: blocked-names:
@@ -1,5 +1,6 @@
package com.project.movienight.application.services package com.project.movienight.application.services
import com.project.movienight.adapters.metrics.BusinessMetricsService
import com.project.movienight.application.ports.input.AddFilmToLibraryCommand import com.project.movienight.application.ports.input.AddFilmToLibraryCommand
import com.project.movienight.application.ports.input.CreateFilmLibraryCommand import com.project.movienight.application.ports.input.CreateFilmLibraryCommand
import com.project.movienight.application.ports.input.GetFilmLibraryQuery import com.project.movienight.application.ports.input.GetFilmLibraryQuery
@@ -23,32 +24,33 @@ import java.util.UUID
class FilmLibraryServiceTest { class FilmLibraryServiceTest {
private lateinit var filmLibraryRepository: FilmLibraryRepositoryPort private lateinit var filmLibraryRepository: FilmLibraryRepositoryPort
private lateinit var idGenerator: IdGenerator private lateinit var idGenerator: IdGenerator
private lateinit var businessMetricsService: BusinessMetricsService
private lateinit var filmLibraryService: FilmLibraryService private lateinit var filmLibraryService: FilmLibraryService
@BeforeEach @BeforeEach
fun setup() { fun setup() {
filmLibraryRepository = mockk() filmLibraryRepository = mockk()
idGenerator = mockk() idGenerator = mockk()
filmLibraryService = FilmLibraryService(filmLibraryRepository, idGenerator) businessMetricsService = mockk(relaxed = true)
filmLibraryService = FilmLibraryService(filmLibraryRepository, idGenerator, businessMetricsService)
} }
@Test @Test
fun `should create new film library when user has no library`() { fun `should create new film library when user has no library`() {
val userId = UUID.randomUUID() val userId = UUID.randomUUID()
val libraryId = UUID.randomUUID() val libraryId = UUID.randomUUID()
val filmId = UUID.randomUUID()
val command = CreateFilmLibraryCommand(userId = userId, name = "My Films") val command = CreateFilmLibraryCommand(userId = userId, name = "My Films")
val expectedLibrary = val expectedLibrary =
FilmLibrary( FilmLibrary(
id = libraryId, id = libraryId,
userId = userId, userId = userId,
filmId = filmId, filmId = libraryId,
comment = "My Films", comment = "My Films",
isViewed = false, isViewed = false,
) )
every { filmLibraryRepository.findAll() } returns emptyList() every { filmLibraryRepository.findAll() } returns emptyList()
every { idGenerator.generateId() } returnsMany listOf(libraryId, filmId) every { idGenerator.generateId() } returns libraryId
every { every {
filmLibraryRepository.save( filmLibraryRepository.save(
match { match {
@@ -62,11 +64,11 @@ class FilmLibraryServiceTest {
assertNotNull(result) assertNotNull(result)
assertEquals(libraryId, result.id) assertEquals(libraryId, result.id)
assertEquals(userId, result.userId) assertEquals(userId, result.userId)
assertEquals(filmId, result.filmId) assertEquals(libraryId, result.filmId)
assertEquals("My Films", result.comment) assertEquals("My Films", result.comment)
verify(exactly = 1) { filmLibraryRepository.findAll() } verify(exactly = 1) { filmLibraryRepository.findAll() }
verify(exactly = 2) { idGenerator.generateId() } verify(exactly = 1) { idGenerator.generateId() }
verify(exactly = 1) { filmLibraryRepository.save(any()) } verify(exactly = 1) { filmLibraryRepository.save(any()) }
} }
@@ -131,7 +133,7 @@ class FilmLibraryServiceTest {
} }
@Test @Test
fun `should add film to existing library`() { fun `should add film as a new library entry when another film already exists`() {
val userId = UUID.randomUUID() val userId = UUID.randomUUID()
val oldFilmId = UUID.randomUUID() val oldFilmId = UUID.randomUUID()
val newFilmId = UUID.randomUUID() val newFilmId = UUID.randomUUID()
@@ -144,16 +146,24 @@ class FilmLibraryServiceTest {
isViewed = true, isViewed = true,
) )
val command = AddFilmToLibraryCommand(userId = userId, filmId = newFilmId) val command = AddFilmToLibraryCommand(userId = userId, filmId = newFilmId)
val updatedLibrary = existingLibrary.copy(filmId = newFilmId, isViewed = false) val createdLibrary =
FilmLibrary(
id = UUID.randomUUID(),
userId = userId,
filmId = newFilmId,
comment = null,
isViewed = false,
)
every { filmLibraryRepository.findAll() } returns listOf(existingLibrary) every { filmLibraryRepository.findAll() } returns listOf(existingLibrary)
every { idGenerator.generateId() } returns createdLibrary.id
every { every {
filmLibraryRepository.save( filmLibraryRepository.save(
match { match {
it.filmId == newFilmId && it.isViewed == false it.id == createdLibrary.id && it.userId == userId && it.filmId == newFilmId && it.isViewed == false
}, },
) )
} returns updatedLibrary } returns createdLibrary
val result = filmLibraryService.addFilm(command) val result = filmLibraryService.addFilm(command)
@@ -161,7 +171,7 @@ class FilmLibraryServiceTest {
assertEquals(false, result.isViewed) assertEquals(false, result.isViewed)
verify(exactly = 1) { filmLibraryRepository.findAll() } verify(exactly = 1) { filmLibraryRepository.findAll() }
verify(exactly = 0) { idGenerator.generateId() } verify(exactly = 1) { idGenerator.generateId() }
verify(exactly = 1) { filmLibraryRepository.save(any()) } verify(exactly = 1) { filmLibraryRepository.save(any()) }
} }
@@ -253,13 +263,13 @@ class FilmLibraryServiceTest {
libraryId = wrongLibraryId, libraryId = wrongLibraryId,
) )
every { filmLibraryRepository.findAll() } returns listOf(existingLibrary) every { filmLibraryRepository.findById(wrongLibraryId) } returns null
assertThrows<EntityNotFoundException> { assertThrows<EntityNotFoundException> {
filmLibraryService.removeFilm(command) filmLibraryService.removeFilm(command)
} }
verify(exactly = 1) { filmLibraryRepository.findAll() } verify(exactly = 1) { filmLibraryRepository.findById(wrongLibraryId) }
verify(exactly = 0) { filmLibraryRepository.deleteById(any()) } verify(exactly = 0) { filmLibraryRepository.deleteById(any()) }
} }