fix: address PR review comments for OAuth2 implementation #32

Merged
skettiks merged 25 commits from feat/oauth2-user-service-26 into develop 2026-05-16 19:30:55 +00:00
21 changed files with 1623 additions and 13 deletions
Showing only changes of commit c03b49157f - Show all commits
+54
View File
@@ -0,0 +1,54 @@
name: Build & Test
on:
workflow_call:
outputs:
artifact-name:
description: "Uploaded artifact name for downstream jobs"
value: ${{ jobs.build.outputs.artifact-name }}
jobs:
build:
name: Build & Test
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
outputs:
artifact-name: ${{ steps.meta.outputs.artifact-name }}
steps:
- name: Checkout source
uses: actions/checkout@v6
- name: Set up JDK 21
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: "21"
cache: gradle
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v6
- name: Run CI quality gate
run: ./gradlew clean check bootJar --stacktrace --no-daemon
- name: Set artifact name
id: meta
run: echo "artifact-name=build-${{ github.run_id }}-${{ github.run_attempt }}" >> "$GITHUB_OUTPUT"
- name: Upload build artifacts
if: always()
uses: actions/upload-artifact@v7
with:
name: ${{ steps.meta.outputs.artifact-name }}
path: |
build/libs/*.jar
build/reports/detekt/**
build/reports/ktlint/**
build/reports/jacoco/**
build/reports/**
build/test-results/**
build/jacoco/**
retention-days: 7
if-no-files-found: error
+86
View File
@@ -0,0 +1,86 @@
name: CI
run-name: "${{ github.ref_name }} - ${{ github.event_name }} by @${{ github.actor }}"
on:
push:
branches: [develop, main]
tags: ["v*"]
pull_request:
branches: [develop, main]
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
trufflehog:
name: TruffleHog Secret Scan
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout source
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Run TruffleHog
uses: trufflesecurity/trufflehog@main
with:
extra_args: --results=verified,unknown
build:
name: Build & Test
needs: [trufflehog]
uses: ./.github/workflows/build.yaml
docker:
name: Docker
needs: build
if: github.event_name == 'pull_request' || github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')
uses: ./.github/workflows/docker.yaml
permissions:
contents: read
packages: write
with:
push: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') }}
secrets: inherit
notify-main:
name: Notify Main Build
needs: docker
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Send Telegram notification
env:
TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
REPO: ${{ github.repository }}
SHA: ${{ github.sha }}
DIGEST: ${{ needs.docker.outputs.image-digest }}
run: |
COMMIT_URL="https://github.com/${REPO}/commit/${SHA}"
MSG="*${REPO}* - main branch CI succeeded"
MSG="${MSG}%0A🔗 [Commit](${COMMIT_URL})"
if [ -n "${DIGEST}" ]; then
MSG="${MSG}%0AImage: \`ghcr.io/${REPO}@${DIGEST}\`"
fi
curl -sf -X POST \
"https://api.telegram.org/bot${TOKEN}/sendMessage" \
-d "chat_id=${CHAT_ID}" \
-d "parse_mode=Markdown" \
-d "text=${MSG}"
release:
name: Release
needs: docker
if: startsWith(github.ref, 'refs/tags/v')
permissions:
contents: write
uses: ./.github/workflows/release.yaml
with:
version: ${{ github.ref_name }}
image-digest: ${{ needs.docker.outputs.image-digest }}
secrets: inherit
+77
View File
@@ -0,0 +1,77 @@
name: Docker Build & Push
on:
workflow_call:
inputs:
push:
description: "Push image to GHCR"
type: boolean
required: true
outputs:
image-digest:
description: "Pushed image digest (sha256:…)"
value: ${{ jobs.docker.outputs.image-digest }}
image-tags:
description: "Comma-separated list of applied tags"
value: ${{ jobs.docker.outputs.image-tags }}
jobs:
docker:
name: Docker Build & Push
runs-on: ubuntu-latest
timeout-minutes: 20
outputs:
image-digest: ${{ steps.build-push.outputs.digest }}
image-tags: ${{ steps.meta.outputs.tags }}
steps:
- name: Checkout source
uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Build image (no push)
if: inputs.push == false
uses: docker/build-push-action@v7
with:
context: .
file: ./Containerfile
push: false
provenance: false
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Log in to GHCR
if: inputs.push == true
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract Docker metadata
if: inputs.push == true
id: meta
uses: docker/metadata-action@v6
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=ref,event=branch
type=ref,event=pr
type=sha,prefix=sha-
- name: Build and push image
if: inputs.push == true
id: build-push
uses: docker/build-push-action@v7
with:
context: .
file: ./Containerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
provenance: false
cache-from: type=gha
cache-to: type=gha,mode=max
+58
View File
@@ -0,0 +1,58 @@
name: Release & Notify
on:
workflow_call:
inputs:
version:
description: "Tag name, e.g. v1.2.3"
type: string
required: true
image-digest:
description: "Docker image digest from docker job"
type: string
required: false
default: ""
secrets:
TELEGRAM_BOT_TOKEN:
required: true
TELEGRAM_CHAT_ID:
required: true
jobs:
release:
name: GitHub Release
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Create GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
run: |
if gh release view "${{ inputs.version }}" >/dev/null 2>&1; then
echo "Release ${{ inputs.version }} already exists. Skipping creation."
else
gh release create "${{ inputs.version }}" \
--generate-notes \
--title "${{ inputs.version }}"
fi
- name: Send Telegram notification
env:
TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
VERSION: ${{ inputs.version }}
REPO: ${{ github.repository }}
DIGEST: ${{ inputs.image-digest }}
run: |
RELEASE_URL="https://github.com/${REPO}/releases/tag/${VERSION}"
MSG="*${REPO}* - released *${VERSION}*"
MSG="${MSG}%0A🔗 [Release notes](${RELEASE_URL})"
if [ -n "${DIGEST}" ]; then
MSG="${MSG}%0AImage: \`ghcr.io/${REPO}@${DIGEST}\`"
fi
curl -sf -X POST \
"https://api.telegram.org/bot${TOKEN}/sendMessage" \
-d "chat_id=${CHAT_ID}" \
-d "parse_mode=Markdown" \
-d "text=${MSG}"
+3
View File
@@ -42,6 +42,9 @@ gradle-app.setting
*.tar.gz
*.rar
# Gradle wrapper
!gradle/wrapper/gradle-wrapper.jar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
+1 -1
View File
@@ -2,6 +2,6 @@
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="externalSystemId" value="Gradle" />
<option name="version" value="2.1.20" />
<option name="version" value="2.0.21" />
</component>
</project>
+3 -8
View File
@@ -26,13 +26,7 @@ RUN --mount=type=cache,target=${GRADLE_USER_HOME} \
COPY src src
RUN --mount=type=cache,target=${GRADLE_USER_HOME} \
./gradlew --no-daemon build \
-x test \
-x detekt \
-x ktlintCheck \
-x ktlintKotlinScriptCheck \
-x ktlintMainSourceSetCheck \
-x ktlintTestSourceSetCheck
./gradlew --no-daemon bootJar
RUN mkdir -p ${APP_HOME}/dist \
&& cp ${APP_HOME}/build/libs/*.jar ${APP_HOME}/dist/${JAR_NAME} \
@@ -60,6 +54,7 @@ COPY --from=builder --chown=app:app /workspace/dist/app.jar /app/app.jar
USER app
ENV JAVA_OPTS="-Xms256m -Xmx512m -XX:+UseG1GC" \
SPRING_AOT_ENABLED=true \
SERVER_PORT=8080 \
PATH="/app:$PATH"
@@ -70,4 +65,4 @@ HEALTHCHECK --interval=30s --timeout=3s --start-period=30s --retries=3 \
STOPSIGNAL SIGTERM
ENTRYPOINT ["sh", "-c", "exec java ${JAVA_OPTS} -Dserver.port=${SERVER_PORT} -jar /app/app.jar"]
ENTRYPOINT ["sh", "-c", "exec java ${JAVA_OPTS} -Dspring.aot.enabled=${SPRING_AOT_ENABLED} -Dserver.port=${SERVER_PORT} -jar /app/app.jar"]
+3
View File
2
@@ -15,6 +15,8 @@ plugins {
jacoco
devitq commented 2026-05-08 17:32:26 +00:00 (Migrated from github.com)
Review

nope, you are not allowed to disable AOT
figure out how to resolve issues

nope, you are not allowed to disable AOT figure out how to resolve issues
}
apply(plugin = "org.springframework.boot.aot")
apply(from = "$rootDir/gradle/docker.gradle.kts")
group = "com.project"
1
@@ -57,6 +59,7 @@ dependencies {
testImplementation(libs.spring.boot.starter.test)
testImplementation(libs.kotlin.test.junit5)
devitq commented 2026-05-15 19:38:46 +00:00 (Migrated from github.com)
Review

do not remove any deps, this removal does not make any sense to your PR

do not remove any deps, this removal does not make any sense to your PR
testImplementation(libs.spring.grpc.test)
testImplementation(libs.mockk)
testRuntimeOnly(libs.junit.platform.launcher)
}
4
Binary file not shown.
@@ -0,0 +1,37 @@
package com.project.movienight.adapters.persistence.entity
import com.project.movienight.domain.model.AuthProvider
import com.project.movienight.domain.model.User
import java.time.LocalDateTime
import java.util.UUID
data class UserEntity(
val id: UUID,
val name: String,
val email: String,
val provider: String?,
val providerId: String?,
val createdAt: LocalDateTime,
)
fun UserEntity.toDomain(): User =
User(
id = id,
name = name,
email = email,
library = null,
)
fun User.toEntity(
provider: AuthProvider? = null,
providerId: String? = null,
createdAt: LocalDateTime = LocalDateTime.now(),
): UserEntity =
UserEntity(
id = id,
name = name,
email = email,
provider = provider?.name,
providerId = providerId,
createdAt = createdAt,
)
@@ -1,6 +1,10 @@
package com.project.movienight.adapters.persistence.jdbc
import com.project.movienight.adapters.persistence.entity.UserEntity
import com.project.movienight.adapters.persistence.entity.toDomain
import com.project.movienight.adapters.persistence.entity.toEntity
import com.project.movienight.application.ports.output.UserRepositoryPort
import com.project.movienight.domain.model.AuthProvider
import com.project.movienight.domain.model.User
import org.springframework.jdbc.core.JdbcTemplate
devitq commented 2026-05-08 17:27:06 +00:00 (Migrated from github.com)
Review

wh... for what?

wh... for what?
import org.springframework.stereotype.Repository
@@ -11,8 +15,8 @@ import java.util.UUID
class UserRepository(
private val jdbc: JdbcTemplate,
) : UserRepositoryPort {
private val userRowMapper = { rs: ResultSet, _: Int ->
User(
private val userEntityRowMapper = { rs: ResultSet, _: Int ->
UserEntity(
id = UUID.fromString(rs.getString("id")),
name = rs.getString("name"),
email = rs.getString("email"),
@@ -22,6 +26,7 @@ class UserRepository(
}
override fun save(user: User): User {
val entity = user.toEntity()
val updatedRows =
jdbc.update(
"""
devitq commented 2026-05-08 17:28:14 +00:00 (Migrated from github.com)
Review

create a mapper to reduce code repetion

create a mapper to reduce code repetion
devitq commented 2026-05-15 19:37:53 +00:00 (Migrated from github.com)
Review

well, nevermind

well, nevermind
@@ -50,13 +55,13 @@ class UserRepository(
}
override fun findById(id: UUID): User? {
val users =
val entities =
jdbc.query(
"SELECT id, name, email, password FROM users WHERE id = ?",
userRowMapper,
id,
)
return users.firstOrNull()
return entities.firstOrNull()?.toDomain()
}
override fun findByEmail(email: String): User? {
@@ -1,5 +1,6 @@
package com.project.movienight.application.ports.output
import com.project.movienight.domain.model.AuthProvider
import com.project.movienight.domain.model.User
import java.util.UUID
@@ -17,4 +18,9 @@ interface UserRepositoryPort {
fun findAll(): List<User>
fun deleteById(id: UUID)
fun findByProviderAndProviderId(
provider: AuthProvider,
providerId: String,
): User?
}
@@ -0,0 +1,7 @@
package com.project.movienight.domain.model
enum class AuthProvider {
GOOGLE,
YANDEX,
VK,
}
@@ -0,0 +1,81 @@
package com.project.movienight.adapters.persistence.entity
import com.project.movienight.domain.model.AuthProvider
import com.project.movienight.domain.model.User
import org.junit.jupiter.api.Test
import java.time.LocalDateTime
import java.util.UUID
import kotlin.test.assertEquals
import kotlin.test.assertNull
class UserEntityMappingTest {
@Test
fun `toDomain maps UserEntity correctly`() {
val entity = UserEntity(
id = UUID.randomUUID(),
name = "John Pork",
email = "john@email.com",
provider = "GOOGLE",
providerId = "google1234",
createdAt = LocalDateTime.now(),
)
val user = entity.toDomain()
assertEquals(entity.id, user.id)
assertEquals(entity.name, user.name)
assertEquals(entity.email, user.email)
assertNull(user.library)
}
@Test
fun `toEntity maps User with OAuth provider`() {
val user = User(
id = UUID.randomUUID(),
name = "Jane",
email = "jane@mail.com",
library = null
)
val entity = user.toEntity(AuthProvider.YANDEX, "yandex456")
assertEquals(user.id, entity.id)
assertEquals(user.name, entity.name)
assertEquals(user.email, entity.email)
assertEquals("YANDEX", entity.provider)
assertEquals("yandex456", entity.providerId)
}
@Test
fun `toEntity maps User without OAuth provider`() {
val user = User(
id = UUID.randomUUID(),
name = "Bob",
email = "bob@mail.com",
library = null
)
val entity = user.toEntity()
assertNull(entity.provider)
assertNull(entity.providerId)
}
@Test
fun `mapping is reversible for basic fields`() {
val original = User(
id = UUID.randomUUID(),
name = "Alice",
email = "alice@email.com",
library = null
)
val mapped = original.toEntity().toDomain()
assertEquals(original.id, mapped.id)
assertEquals(original.name, mapped.name)
assertEquals(original.email, mapped.email)
}
}
@@ -0,0 +1,220 @@
package com.project.movienight.adapters.persistence.jdbc
import com.project.movienight.domain.model.Film
import com.project.movienight.domain.model.FilmLibrary
import com.project.movienight.domain.model.User
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.test.context.ActiveProfiles
import java.util.UUID
@SpringBootTest
@ActiveProfiles("test")
class FilmLibraryRepositoryIntegrationTest {
@Autowired
private lateinit var filmLibraryRepository: FilmLibraryRepository
@Autowired
private lateinit var userRepository: UserRepository
@Autowired
private lateinit var filmRepository: FilmRepository
@Autowired
private lateinit var jdbcTemplate: JdbcTemplate
private lateinit var testUser: User
private lateinit var testFilm: Film
@BeforeEach
fun setup() {
cleanDatabase()
createTestData()
}
@AfterEach
fun cleanup() {
cleanDatabase()
}
private fun cleanDatabase() {
jdbcTemplate.execute("DELETE FROM favorites")
jdbcTemplate.execute("DELETE FROM films")
jdbcTemplate.execute("DELETE FROM users")
}
private fun createTestData() {
testUser = User(UUID.randomUUID(), "Иван Иванов", "ivan@example.com", null)
testFilm = Film(UUID.randomUUID(), "Начало", "Захватывающий триллер")
userRepository.save(testUser)
filmRepository.save(testFilm)
}
@Test
fun `should save new film library entry and return saved entry`() {
val entry =
FilmLibrary(
id = UUID.randomUUID(),
userId = testUser.id,
filmId = testFilm.id,
comment = "Отличный фильм!",
isViewed = false,
)
val savedEntry = filmLibraryRepository.save(entry)
assertNotNull(savedEntry)
assertEquals(entry.id, savedEntry.id)
assertEquals(entry.userId, savedEntry.userId)
assertEquals(entry.filmId, savedEntry.filmId)
assertEquals(entry.comment, savedEntry.comment)
assertEquals(entry.isViewed, savedEntry.isViewed)
}
@Test
fun `should update existing film library entry`() {
val entryId = UUID.randomUUID()
val originalEntry = FilmLibrary(entryId, testUser.id, testFilm.id, "Хочу посмотреть", false)
filmLibraryRepository.save(originalEntry)
val updatedEntry = FilmLibrary(entryId, testUser.id, testFilm.id, "Уже посмотрел, потрясающе!", true)
val result = filmLibraryRepository.save(updatedEntry)
assertEquals(entryId, result.id)
assertEquals("Уже посмотрел, потрясающе!", result.comment)
assertTrue(result.isViewed)
val foundEntry = filmLibraryRepository.findById(entryId)
assertNotNull(foundEntry)
assertEquals("Уже посмотрел, потрясающе!", foundEntry?.comment)
assertTrue(foundEntry?.isViewed ?: false)
}
@Test
fun `should find film library entry by id`() {
val entry = FilmLibrary(UUID.randomUUID(), testUser.id, testFilm.id, "Обязательно посмотреть", false)
filmLibraryRepository.save(entry)
val foundEntry = filmLibraryRepository.findById(entry.id)
assertNotNull(foundEntry)
assertEquals(entry.id, foundEntry?.id)
assertEquals(entry.userId, foundEntry?.userId)
assertEquals(entry.filmId, foundEntry?.filmId)
assertEquals(entry.comment, foundEntry?.comment)
assertEquals(entry.isViewed, foundEntry?.isViewed)
}
@Test
fun `should return null when film library entry not found by id`() {
val nonExistentId = UUID.randomUUID()
val foundEntry = filmLibraryRepository.findById(nonExistentId)
assertNull(foundEntry)
}
@Test
fun `should find all film library entries`() {
val entry1 = FilmLibrary(UUID.randomUUID(), testUser.id, testFilm.id, "Комментарий 1", false)
val entry2 = FilmLibrary(UUID.randomUUID(), testUser.id, testFilm.id, "Комментарий 2", true)
val entry3 = FilmLibrary(UUID.randomUUID(), testUser.id, testFilm.id, null, false)
filmLibraryRepository.save(entry1)
filmLibraryRepository.save(entry2)
filmLibraryRepository.save(entry3)
val allEntries = filmLibraryRepository.findAll()
assertEquals(3, allEntries.size)
assertTrue(allEntries.any { it.id == entry1.id })
assertTrue(allEntries.any { it.id == entry2.id })
assertTrue(allEntries.any { it.id == entry3.id })
}
@Test
fun `should return empty list when no film library entries exist`() {
val allEntries = filmLibraryRepository.findAll()
assertTrue(allEntries.isEmpty())
}
@Test
fun `should delete film library entry by id`() {
val entry = FilmLibrary(UUID.randomUUID(), testUser.id, testFilm.id, "Для удаления", false)
filmLibraryRepository.save(entry)
filmLibraryRepository.deleteById(entry.id)
val foundEntry = filmLibraryRepository.findById(entry.id)
assertNull(foundEntry)
}
@Test
fun `should not throw exception when deleting non-existent entry`() {
val nonExistentId = UUID.randomUUID()
filmLibraryRepository.deleteById(nonExistentId)
}
@Test
fun `should save entry with null comment`() {
val entry = FilmLibrary(UUID.randomUUID(), testUser.id, testFilm.id, null, false)
val savedEntry = filmLibraryRepository.save(entry)
assertNotNull(savedEntry)
assertNull(savedEntry.comment)
}
@Test
fun `should save entry with isViewed true`() {
val entry = FilmLibrary(UUID.randomUUID(), testUser.id, testFilm.id, "Посмотрел", true)
val savedEntry = filmLibraryRepository.save(entry)
assertNotNull(savedEntry)
assertTrue(savedEntry.isViewed)
}
@Test
fun `should save entry with isViewed false`() {
val entry = FilmLibrary(UUID.randomUUID(), testUser.id, testFilm.id, "Еще не смотрел", false)
val savedEntry = filmLibraryRepository.save(entry)
assertNotNull(savedEntry)
assertFalse(savedEntry.isViewed)
}
@Test
fun `should cascade delete entries when user is deleted`() {
val entry = FilmLibrary(UUID.randomUUID(), testUser.id, testFilm.id, "Любимый фильм пользователя", false)
filmLibraryRepository.save(entry)
userRepository.deleteById(testUser.id)
val foundEntry = filmLibraryRepository.findById(entry.id)
assertNull(foundEntry)
}
@Test
fun `should cascade delete entries when film is deleted`() {
val entry = FilmLibrary(UUID.randomUUID(), testUser.id, testFilm.id, "Запись о фильме", false)
filmLibraryRepository.save(entry)
filmRepository.deleteById(testFilm.id)
val foundEntry = filmLibraryRepository.findById(entry.id)
assertNull(foundEntry)
}
}
@@ -0,0 +1,153 @@
package com.project.movienight.adapters.persistence.jdbc
import com.project.movienight.domain.model.Film
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.test.context.ActiveProfiles
import java.util.UUID
@SpringBootTest
@ActiveProfiles("test")
class FilmRepositoryIntegrationTest {
@Autowired
private lateinit var filmRepository: FilmRepository
@Autowired
private lateinit var jdbcTemplate: JdbcTemplate
@BeforeEach
fun setup() {
cleanDatabase()
}
@AfterEach
fun cleanup() {
cleanDatabase()
}
private fun cleanDatabase() {
jdbcTemplate.execute("DELETE FROM favorites")
jdbcTemplate.execute("DELETE FROM films")
jdbcTemplate.execute("DELETE FROM users")
}
@Test
fun `should save new film and return saved film`() {
val film =
Film(
id = UUID.randomUUID(),
title = "Начало",
description = "Захватывающий триллер о снах внутри снов",
)
val savedFilm = filmRepository.save(film)
assertNotNull(savedFilm)
assertEquals(film.id, savedFilm.id)
assertEquals(film.title, savedFilm.title)
assertEquals(film.description, savedFilm.description)
}
@Test
fun `should update existing film`() {
val filmId = UUID.randomUUID()
val originalFilm = Film(filmId, "Начало", "Оригинальное описание")
filmRepository.save(originalFilm)
val updatedFilm = Film(filmId, "Начало (Обновлено)", "Обновленное описание с дополнительными деталями")
val result = filmRepository.save(updatedFilm)
assertEquals(filmId, result.id)
assertEquals("Начало (Обновлено)", result.title)
assertEquals("Обновленное описание с дополнительными деталями", result.description)
val foundFilm = filmRepository.findById(filmId)
assertNotNull(foundFilm)
assertEquals("Начало (Обновлено)", foundFilm?.title)
assertEquals("Обновленное описание с дополнительными деталями", foundFilm?.description)
}
@Test
fun `should find film by id`() {
val film = Film(UUID.randomUUID(), "Матрица", "Хакер узнает правду о реальности")
filmRepository.save(film)
val foundFilm = filmRepository.findById(film.id)
assertNotNull(foundFilm)
assertEquals(film.id, foundFilm?.id)
assertEquals(film.title, foundFilm?.title)
assertEquals(film.description, foundFilm?.description)
}
@Test
fun `should return null when film not found by id`() {
val nonExistentId = UUID.randomUUID()
val foundFilm = filmRepository.findById(nonExistentId)
assertNull(foundFilm)
}
@Test
fun `should find all films`() {
val film1 = Film(UUID.randomUUID(), "Начало", "Сны внутри снов")
val film2 = Film(UUID.randomUUID(), "Матрица", "Реальность не то, чем кажется")
val film3 = Film(UUID.randomUUID(), "Интерстеллар", "Путешествие сквозь пространство и время")
filmRepository.save(film1)
filmRepository.save(film2)
filmRepository.save(film3)
val allFilms = filmRepository.findAll()
assertEquals(3, allFilms.size)
assertTrue(allFilms.any { it.id == film1.id })
assertTrue(allFilms.any { it.id == film2.id })
assertTrue(allFilms.any { it.id == film3.id })
}
@Test
fun `should return empty list when no films exist`() {
val allFilms = filmRepository.findAll()
assertTrue(allFilms.isEmpty())
}
@Test
fun `should delete film by id`() {
val film = Film(UUID.randomUUID(), "Начало", "Сны внутри снов")
filmRepository.save(film)
filmRepository.deleteById(film.id)
val foundFilm = filmRepository.findById(film.id)
assertNull(foundFilm)
}
@Test
fun `should not throw exception when deleting non-existent film`() {
val nonExistentId = UUID.randomUUID()
filmRepository.deleteById(nonExistentId)
}
@Test
fun `should save film with long description`() {
val longDescription = "А".repeat(1000)
val film = Film(UUID.randomUUID(), "Тестовый фильм", longDescription)
val savedFilm = filmRepository.save(film)
assertNotNull(savedFilm)
assertEquals(longDescription, savedFilm.description)
}
}
@@ -0,0 +1,160 @@
package com.project.movienight.adapters.persistence.jdbc
import com.project.movienight.domain.model.User
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.test.context.ActiveProfiles
import java.util.UUID
@SpringBootTest
@ActiveProfiles("test")
class UserRepositoryIntegrationTest {
@Autowired
private lateinit var userRepository: UserRepository
@Autowired
private lateinit var jdbcTemplate: JdbcTemplate
@BeforeEach
fun setup() {
cleanDatabase()
}
@AfterEach
fun cleanup() {
cleanDatabase()
}
private fun cleanDatabase() {
jdbcTemplate.execute("DELETE FROM favorites")
jdbcTemplate.execute("DELETE FROM films")
jdbcTemplate.execute("DELETE FROM users")
}
@Test
fun `should save new user and return saved user`() {
val user =
User(
id = UUID.randomUUID(),
name = "John Doe",
email = "john@example.com",
library = null,
)
val savedUser = userRepository.save(user)
assertNotNull(savedUser)
assertEquals(user.id, savedUser.id)
assertEquals(user.name, savedUser.name)
assertEquals(user.email, savedUser.email)
}
@Test
fun `should update existing user`() {
// given
val userId = UUID.randomUUID()
val originalUser = User(userId, "John Doe", "john@example.com", null)
userRepository.save(originalUser)
val updatedUser = User(userId, "Jane Doe", "jane@example.com", null)
val result = userRepository.save(updatedUser)
assertEquals(userId, result.id)
assertEquals("Jane Doe", result.name)
assertEquals("jane@example.com", result.email)
val foundUser = userRepository.findById(userId)
assertNotNull(foundUser)
assertEquals("Jane Doe", foundUser?.name)
assertEquals("jane@example.com", foundUser?.email)
}
@Test
fun `should find user by id`() {
// given
val user = User(UUID.randomUUID(), "John Doe", "john@example.com", null)
userRepository.save(user)
// when
val foundUser = userRepository.findById(user.id)
// then
assertNotNull(foundUser)
assertEquals(user.id, foundUser?.id)
assertEquals(user.name, foundUser?.name)
assertEquals(user.email, foundUser?.email)
}
@Test
fun `should return null when user not found by id`() {
// given
val nonExistentId = UUID.randomUUID()
// when
val foundUser = userRepository.findById(nonExistentId)
// then
assertNull(foundUser)
}
@Test
fun `should find all users`() {
// given
val user1 = User(UUID.randomUUID(), "John Doe", "john@example.com", null)
val user2 = User(UUID.randomUUID(), "Jane Smith", "jane@example.com", null)
val user3 = User(UUID.randomUUID(), "Bob Johnson", "bob@example.com", null)
userRepository.save(user1)
userRepository.save(user2)
userRepository.save(user3)
// when
val allUsers = userRepository.findAll()
// then
assertEquals(3, allUsers.size)
assertTrue(allUsers.any { it.id == user1.id })
assertTrue(allUsers.any { it.id == user2.id })
assertTrue(allUsers.any { it.id == user3.id })
}
@Test
fun `should return empty list when no users exist`() {
// when
val allUsers = userRepository.findAll()
// then
assertTrue(allUsers.isEmpty())
}
@Test
fun `should delete user by id`() {
// given
val user = User(UUID.randomUUID(), "John Doe", "john@example.com", null)
userRepository.save(user)
// when
userRepository.deleteById(user.id)
// then
val foundUser = userRepository.findById(user.id)
assertNull(foundUser)
}
@Test
fun `should not throw exception when deleting non-existent user`() {
// given
val nonExistentId = UUID.randomUUID()
// when & then (no exception should be thrown)
userRepository.deleteById(nonExistentId)
}
}
@@ -0,0 +1,301 @@
package com.project.movienight.application.services
import com.project.movienight.application.ports.input.AddFilmToLibraryCommand
import com.project.movienight.application.ports.input.CreateFilmLibraryCommand
import com.project.movienight.application.ports.input.GetFilmLibraryQuery
import com.project.movienight.application.ports.input.RemoveFilmFromLibraryCommand
import com.project.movienight.application.ports.output.FilmLibraryRepositoryPort
import com.project.movienight.application.ports.output.IdGenerator
import com.project.movienight.domain.exception.DomainException
import com.project.movienight.domain.exception.EntityNotFoundException
import com.project.movienight.domain.model.FilmLibrary
import io.mockk.every
import io.mockk.justRun
import io.mockk.mockk
import io.mockk.verify
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import java.util.UUID
class FilmLibraryServiceTest {
private lateinit var filmLibraryRepository: FilmLibraryRepositoryPort
private lateinit var idGenerator: IdGenerator
private lateinit var filmLibraryService: FilmLibraryService
@BeforeEach
fun setup() {
filmLibraryRepository = mockk()
idGenerator = mockk()
filmLibraryService = FilmLibraryService(filmLibraryRepository, idGenerator)
}
@Test
fun `should create new film library when user has no library`() {
val userId = UUID.randomUUID()
val libraryId = UUID.randomUUID()
val filmId = UUID.randomUUID()
val command = CreateFilmLibraryCommand(userId = userId, name = "My Films")
val expectedLibrary =
FilmLibrary(
id = libraryId,
userId = userId,
filmId = filmId,
comment = "My Films",
isViewed = false,
)
every { filmLibraryRepository.findAll() } returns emptyList()
every { idGenerator.generateId() } returnsMany listOf(libraryId, filmId)
every {
filmLibraryRepository.save(
match {
it.userId == userId && it.comment == "My Films" && it.isViewed == false
},
)
} returns expectedLibrary
val result = filmLibraryService.create(command)
assertNotNull(result)
assertEquals(libraryId, result.id)
assertEquals(userId, result.userId)
assertEquals(filmId, result.filmId)
assertEquals("My Films", result.comment)
verify(exactly = 1) { filmLibraryRepository.findAll() }
verify(exactly = 2) { idGenerator.generateId() }
verify(exactly = 1) { filmLibraryRepository.save(any()) }
}
@Test
fun `should return existing library when user already has one`() {
val userId = UUID.randomUUID()
val existingLibrary =
FilmLibrary(
id = UUID.randomUUID(),
userId = userId,
filmId = UUID.randomUUID(),
comment = "Existing Library",
isViewed = false,
)
val command = CreateFilmLibraryCommand(userId = userId, name = "New Library")
every { filmLibraryRepository.findAll() } returns listOf(existingLibrary)
val result = filmLibraryService.create(command)
assertEquals(existingLibrary, result)
verify(exactly = 1) { filmLibraryRepository.findAll() }
verify(exactly = 0) { idGenerator.generateId() }
verify(exactly = 0) { filmLibraryRepository.save(any()) }
}
@Test
fun `should add film to new library when user has no library`() {
val userId = UUID.randomUUID()
val filmId = UUID.randomUUID()
val libraryId = UUID.randomUUID()
val command = AddFilmToLibraryCommand(userId = userId, filmId = filmId)
val expectedLibrary =
FilmLibrary(
id = libraryId,
userId = userId,
filmId = filmId,
comment = null,
isViewed = false,
)
every { filmLibraryRepository.findAll() } returns emptyList()
every { idGenerator.generateId() } returns libraryId
every {
filmLibraryRepository.save(
match {
it.userId == userId && it.filmId == filmId && it.comment == null && it.isViewed == false
},
)
} returns expectedLibrary
val result = filmLibraryService.addFilm(command)
assertNotNull(result)
assertEquals(filmId, result.filmId)
assertEquals(userId, result.userId)
verify(exactly = 1) { filmLibraryRepository.findAll() }
verify(exactly = 1) { idGenerator.generateId() }
verify(exactly = 1) { filmLibraryRepository.save(any()) }
}
@Test
fun `should add film to existing library`() {
val userId = UUID.randomUUID()
val oldFilmId = UUID.randomUUID()
val newFilmId = UUID.randomUUID()
val existingLibrary =
FilmLibrary(
id = UUID.randomUUID(),
userId = userId,
filmId = oldFilmId,
comment = "My Library",
isViewed = true,
)
val command = AddFilmToLibraryCommand(userId = userId, filmId = newFilmId)
val updatedLibrary = existingLibrary.copy(filmId = newFilmId, isViewed = false)
every { filmLibraryRepository.findAll() } returns listOf(existingLibrary)
every {
filmLibraryRepository.save(
match {
it.filmId == newFilmId && it.isViewed == false
},
)
} returns updatedLibrary
val result = filmLibraryService.addFilm(command)
assertEquals(newFilmId, result.filmId)
assertEquals(false, result.isViewed)
verify(exactly = 1) { filmLibraryRepository.findAll() }
verify(exactly = 0) { idGenerator.generateId() }
verify(exactly = 1) { filmLibraryRepository.save(any()) }
}
@Test
fun `should remove film from library successfully`() {
val userId = UUID.randomUUID()
val filmId = UUID.randomUUID()
val libraryId = UUID.randomUUID()
val existingLibrary =
FilmLibrary(
id = libraryId,
userId = userId,
filmId = filmId,
comment = "My Library",
isViewed = false,
)
val command = RemoveFilmFromLibraryCommand(userId = userId, filmId = filmId)
every { filmLibraryRepository.findAll() } returns listOf(existingLibrary)
justRun { filmLibraryRepository.deleteById(libraryId) }
val result = filmLibraryService.removeFilm(command)
assertEquals(existingLibrary, result)
verify(exactly = 1) { filmLibraryRepository.findAll() }
verify(exactly = 1) { filmLibraryRepository.deleteById(libraryId) }
}
@Test
fun `should throw EntityNotFoundException when removing film from non-existent library`() {
val userId = UUID.randomUUID()
val filmId = UUID.randomUUID()
val command = RemoveFilmFromLibraryCommand(userId = userId, filmId = filmId)
every { filmLibraryRepository.findAll() } returns emptyList()
assertThrows<EntityNotFoundException> {
filmLibraryService.removeFilm(command)
}
verify(exactly = 1) { filmLibraryRepository.findAll() }
verify(exactly = 0) { filmLibraryRepository.deleteById(any()) }
}
@Test
fun `should throw DomainException when removing film that is not in library`() {
val userId = UUID.randomUUID()
val libraryFilmId = UUID.randomUUID()
val differentFilmId = UUID.randomUUID()
val existingLibrary =
FilmLibrary(
id = UUID.randomUUID(),
userId = userId,
filmId = libraryFilmId,
comment = "My Library",
isViewed = false,
)
val command = RemoveFilmFromLibraryCommand(userId = userId, filmId = differentFilmId)
every { filmLibraryRepository.findAll() } returns listOf(existingLibrary)
assertThrows<DomainException> {
filmLibraryService.removeFilm(command)
}
verify(exactly = 1) { filmLibraryRepository.findAll() }
verify(exactly = 0) { filmLibraryRepository.deleteById(any()) }
}
@Test
fun `should throw EntityNotFoundException when libraryId does not match`() {
val userId = UUID.randomUUID()
val filmId = UUID.randomUUID()
val actualLibraryId = UUID.randomUUID()
val wrongLibraryId = UUID.randomUUID()
val existingLibrary =
FilmLibrary(
id = actualLibraryId,
userId = userId,
filmId = filmId,
comment = "My Library",
isViewed = false,
)
val command =
RemoveFilmFromLibraryCommand(
userId = userId,
filmId = filmId,
libraryId = wrongLibraryId,
)
every { filmLibraryRepository.findAll() } returns listOf(existingLibrary)
assertThrows<EntityNotFoundException> {
filmLibraryService.removeFilm(command)
}
verify(exactly = 1) { filmLibraryRepository.findAll() }
verify(exactly = 0) { filmLibraryRepository.deleteById(any()) }
}
@Test
fun `should get library successfully`() {
val userId = UUID.randomUUID()
val existingLibrary =
FilmLibrary(
id = UUID.randomUUID(),
userId = userId,
filmId = UUID.randomUUID(),
comment = "My Library",
isViewed = false,
)
val query = GetFilmLibraryQuery(userId = userId)
every { filmLibraryRepository.findAll() } returns listOf(existingLibrary)
val result = filmLibraryService.getLibrary(query)
assertEquals(existingLibrary, result)
verify(exactly = 1) { filmLibraryRepository.findAll() }
}
@Test
fun `should throw EntityNotFoundException when getting non-existent library`() {
val userId = UUID.randomUUID()
val query = GetFilmLibraryQuery(userId = userId)
every { filmLibraryRepository.findAll() } returns emptyList()
assertThrows<EntityNotFoundException> {
filmLibraryService.getLibrary(query)
}
verify(exactly = 1) { filmLibraryRepository.findAll() }
}
}
@@ -0,0 +1,181 @@
package com.project.movienight.application.services
import com.project.movienight.application.ports.input.CreateFilmCommand
import com.project.movienight.application.ports.input.EditFilmCommand
import com.project.movienight.application.ports.output.FilmRepositoryPort
import com.project.movienight.application.ports.output.IdGenerator
import com.project.movienight.config.FilmServiceProperties
import com.project.movienight.domain.exception.BlockedValueException
import com.project.movienight.domain.exception.EntityNotFoundException
import com.project.movienight.domain.model.Film
import io.mockk.every
import io.mockk.justRun
import io.mockk.mockk
import io.mockk.verify
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import java.util.UUID
class FilmServiceTest {
private lateinit var filmRepository: FilmRepositoryPort
private lateinit var idGenerator: IdGenerator
private lateinit var filmConfig: FilmServiceProperties
private lateinit var filmService: FilmService
@BeforeEach
fun setup() {
filmRepository = mockk()
idGenerator = mockk()
filmConfig = mockk()
filmService = FilmService(filmRepository, idGenerator, filmConfig)
}
@Test
fun `should create film successfully`() {
val command = CreateFilmCommand(title = "Inception", description = "A mind-bending thriller")
val filmId = UUID.randomUUID()
val expectedFilm = Film(id = filmId, title = "Inception", description = "A mind-bending thriller")
every { filmConfig.isBlocked("Inception") } returns false
every { filmConfig.isBlocked("A mind-bending thriller") } returns false
every { idGenerator.generateId() } returns filmId
every { filmRepository.save(any()) } returns expectedFilm
val result = filmService.create(command)
assertNotNull(result)
assertEquals(filmId, result.id)
assertEquals("Inception", result.title)
assertEquals("A mind-bending thriller", result.description)
verify(exactly = 1) { filmConfig.isBlocked("Inception") }
verify(exactly = 1) { filmConfig.isBlocked("A mind-bending thriller") }
verify(exactly = 1) { idGenerator.generateId() }
verify(exactly = 1) { filmRepository.save(any()) }
}
@Test
fun `should throw BlockedValueException when creating film with blocked title`() {
val command = CreateFilmCommand(title = "censored", description = "Some description")
every { filmConfig.isBlocked("censored") } returns true
every { filmConfig.isBlocked("Some description") } returns false
assertThrows<BlockedValueException> {
filmService.create(command)
}
verify(exactly = 1) { filmConfig.isBlocked("censored") }
verify(exactly = 0) { filmConfig.isBlocked("Some description") }
verify(exactly = 0) { idGenerator.generateId() }
verify(exactly = 0) { filmRepository.save(any()) }
}
@Test
fun `should throw BlockedValueException when creating film with blocked description`() {
val command = CreateFilmCommand(title = "Good Film", description = "python")
every { filmConfig.isBlocked("Good Film") } returns false
every { filmConfig.isBlocked("python") } returns true
assertThrows<BlockedValueException> {
filmService.create(command)
}
verify(exactly = 1) { filmConfig.isBlocked("Good Film") }
verify(exactly = 1) { filmConfig.isBlocked("python") }
verify(exactly = 0) { idGenerator.generateId() }
verify(exactly = 0) { filmRepository.save(any()) }
}
@Test
fun `should edit film successfully`() {
val filmId = UUID.randomUUID()
val command = EditFilmCommand(title = "Inception 2", description = "The sequel")
val existingFilm = Film(id = filmId, title = "Inception", description = "A mind-bending thriller")
val updatedFilm = Film(id = filmId, title = "Inception 2", description = "The sequel")
every { filmConfig.isBlocked("Inception 2") } returns false
every { filmConfig.isBlocked("The sequel") } returns false
every { filmRepository.findById(filmId) } returns existingFilm
every { filmRepository.save(any()) } returns updatedFilm
val result = filmService.edit(filmId, command)
assertNotNull(result)
assertEquals(filmId, result.id)
assertEquals("Inception 2", result.title)
assertEquals("The sequel", result.description)
verify(exactly = 1) { filmConfig.isBlocked("Inception 2") }
verify(exactly = 1) { filmConfig.isBlocked("The sequel") }
verify(exactly = 1) { filmRepository.findById(filmId) }
verify(exactly = 1) { filmRepository.save(any()) }
}
@Test
fun `should throw BlockedValueException when editing film with blocked title`() {
val filmId = UUID.randomUUID()
val command = EditFilmCommand(title = "epstein", description = "Some description")
every { filmConfig.isBlocked("epstein") } returns true
assertThrows<BlockedValueException> {
filmService.edit(filmId, command)
}
verify(exactly = 1) { filmConfig.isBlocked("epstein") }
verify(exactly = 0) { filmRepository.findById(any()) }
verify(exactly = 0) { filmRepository.save(any()) }
}
@Test
fun `should throw EntityNotFoundException when editing non-existent film`() {
val filmId = UUID.randomUUID()
val command = EditFilmCommand(title = "New Title", description = "New Description")
every { filmConfig.isBlocked("New Title") } returns false
every { filmConfig.isBlocked("New Description") } returns false
every { filmRepository.findById(filmId) } returns null
assertThrows<EntityNotFoundException> {
filmService.edit(filmId, command)
}
verify(exactly = 1) { filmConfig.isBlocked("New Title") }
verify(exactly = 1) { filmConfig.isBlocked("New Description") }
verify(exactly = 1) { filmRepository.findById(filmId) }
verify(exactly = 0) { filmRepository.save(any()) }
}
@Test
fun `should delete film successfully`() {
val filmId = UUID.randomUUID()
val existingFilm = Film(id = filmId, title = "Inception", description = "A mind-bending thriller")
every { filmRepository.findById(filmId) } returns existingFilm
justRun { filmRepository.deleteById(filmId) }
filmService.delete(filmId)
verify(exactly = 1) { filmRepository.findById(filmId) }
verify(exactly = 1) { filmRepository.deleteById(filmId) }
}
@Test
fun `should throw EntityNotFoundException when deleting non-existent film`() {
val filmId = UUID.randomUUID()
every { filmRepository.findById(filmId) } returns null
assertThrows<EntityNotFoundException> {
filmService.delete(filmId)
}
verify(exactly = 1) { filmRepository.findById(filmId) }
verify(exactly = 0) { filmRepository.deleteById(any()) }
}
}
@@ -0,0 +1,155 @@
package com.project.movienight.application.services
import com.project.movienight.application.ports.input.CreateUserCommand
import com.project.movienight.application.ports.input.EditUserCommand
import com.project.movienight.application.ports.output.IdGenerator
import com.project.movienight.application.ports.output.UserRepositoryPort
import com.project.movienight.config.UserServiceProperties
import com.project.movienight.domain.exception.BlockedValueException
import com.project.movienight.domain.exception.EntityNotFoundException
import com.project.movienight.domain.model.User
import io.mockk.every
import io.mockk.justRun
import io.mockk.mockk
import io.mockk.verify
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import java.util.UUID
class UserServiceTest {
private lateinit var userRepository: UserRepositoryPort
private lateinit var idGenerator: IdGenerator
private lateinit var userConfig: UserServiceProperties
private lateinit var userService: UserService
@BeforeEach
fun setup() {
userRepository = mockk()
idGenerator = mockk()
userConfig = mockk()
userService = UserService(userRepository, idGenerator, userConfig)
}
@Test
fun `should create user successfully`() {
val command = CreateUserCommand(name = "John Doe", email = "john@example.com")
val userId = UUID.randomUUID()
val expectedUser = User(id = userId, name = "John Doe", email = "john@example.com", library = null)
every { userConfig.isBlocked("John Doe") } returns false
every { idGenerator.generateId() } returns userId
every { userRepository.save(any()) } returns expectedUser
val result = userService.create(command)
assertNotNull(result)
assertEquals(userId, result.id)
assertEquals("John Doe", result.name)
assertEquals("john@example.com", result.email)
verify(exactly = 1) { userConfig.isBlocked("John Doe") }
verify(exactly = 1) { idGenerator.generateId() }
verify(exactly = 1) { userRepository.save(any()) }
}
@Test
fun `should throw BlockedValueException when creating user with blocked name`() {
val command = CreateUserCommand(name = "admin", email = "admin@example.com")
every { userConfig.isBlocked("admin") } returns true
assertThrows<BlockedValueException> {
userService.create(command)
}
verify(exactly = 1) { userConfig.isBlocked("admin") }
verify(exactly = 0) { idGenerator.generateId() }
verify(exactly = 0) { userRepository.save(any()) }
}
@Test
fun `should edit user successfully`() {
val userId = UUID.randomUUID()
val command = EditUserCommand(name = "Jane Doe")
val existingUser = User(id = userId, name = "John Doe", email = "john@example.com", library = null)
val updatedUser = User(id = userId, name = "Jane Doe", email = "john@example.com", library = null)
every { userConfig.isBlocked("Jane Doe") } returns false
every { userRepository.findById(userId) } returns existingUser
every { userRepository.save(any()) } returns updatedUser
val result = userService.edit(userId, command)
assertNotNull(result)
assertEquals(userId, result.id)
assertEquals("Jane Doe", result.name)
verify(exactly = 1) { userConfig.isBlocked("Jane Doe") }
verify(exactly = 1) { userRepository.findById(userId) }
verify(exactly = 1) { userRepository.save(any()) }
}
@Test
fun `should throw BlockedValueException when editing user with blocked name`() {
val userId = UUID.randomUUID()
val command = EditUserCommand(name = "root")
every { userConfig.isBlocked("root") } returns true
assertThrows<BlockedValueException> {
userService.edit(userId, command)
}
verify(exactly = 1) { userConfig.isBlocked("root") }
verify(exactly = 0) { userRepository.findById(any()) }
verify(exactly = 0) { userRepository.save(any()) }
}
@Test
fun `should throw EntityNotFoundException when editing non-existent user`() {
val userId = UUID.randomUUID()
val command = EditUserCommand(name = "Jane Doe")
every { userConfig.isBlocked("Jane Doe") } returns false
every { userRepository.findById(userId) } returns null
assertThrows<EntityNotFoundException> {
userService.edit(userId, command)
}
verify(exactly = 1) { userConfig.isBlocked("Jane Doe") }
verify(exactly = 1) { userRepository.findById(userId) }
verify(exactly = 0) { userRepository.save(any()) }
}
@Test
fun `should delete user successfully`() {
val userId = UUID.randomUUID()
val existingUser = User(id = userId, name = "John Doe", email = "john@example.com", library = null)
every { userRepository.findById(userId) } returns existingUser
justRun { userRepository.deleteById(userId) }
userService.delete(userId)
verify(exactly = 1) { userRepository.findById(userId) }
verify(exactly = 1) { userRepository.deleteById(userId) }
}
@Test
fun `should throw EntityNotFoundException when deleting non-existent user`() {
val userId = UUID.randomUUID()
every { userRepository.findById(userId) } returns null
assertThrows<EntityNotFoundException> {
userService.delete(userId)
}
verify(exactly = 1) { userRepository.findById(userId) }
verify(exactly = 0) { userRepository.deleteById(any()) }
}
}
+28
View File
@@ -0,0 +1,28 @@
spring:
application:
name: MovieNight-Test
datasource:
url: jdbc:h2:mem:testdb;MODE=PostgreSQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
username: sa
password:
driver-class-name: org.h2.Driver
flyway:
enabled: true
url: jdbc:h2:mem:testdb;MODE=PostgreSQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
locations: classpath:db/migration
baseline-on-migrate: true
h2:
console:
enabled: false
services:
user:
blocked-names:
- admin
- root
- system
film:
blocked-patterns:
- censored
- epstein
- python