Merge branch 'develop' into feat/oauth2-user-service-26
This commit is contained in:
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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}"
|
||||||
@@ -42,6 +42,9 @@ gradle-app.setting
|
|||||||
*.tar.gz
|
*.tar.gz
|
||||||
*.rar
|
*.rar
|
||||||
|
|
||||||
|
# Gradle wrapper
|
||||||
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||||
hs_err_pid*
|
hs_err_pid*
|
||||||
replay_pid*
|
replay_pid*
|
||||||
|
|||||||
Generated
+1
-1
@@ -2,6 +2,6 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="KotlinJpsPluginSettings">
|
<component name="KotlinJpsPluginSettings">
|
||||||
<option name="externalSystemId" value="Gradle" />
|
<option name="externalSystemId" value="Gradle" />
|
||||||
<option name="version" value="2.1.20" />
|
<option name="version" value="2.0.21" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
+3
-8
@@ -26,13 +26,7 @@ RUN --mount=type=cache,target=${GRADLE_USER_HOME} \
|
|||||||
COPY src src
|
COPY src src
|
||||||
|
|
||||||
RUN --mount=type=cache,target=${GRADLE_USER_HOME} \
|
RUN --mount=type=cache,target=${GRADLE_USER_HOME} \
|
||||||
./gradlew --no-daemon build \
|
./gradlew --no-daemon bootJar
|
||||||
-x test \
|
|
||||||
-x detekt \
|
|
||||||
-x ktlintCheck \
|
|
||||||
-x ktlintKotlinScriptCheck \
|
|
||||||
-x ktlintMainSourceSetCheck \
|
|
||||||
-x ktlintTestSourceSetCheck
|
|
||||||
|
|
||||||
RUN mkdir -p ${APP_HOME}/dist \
|
RUN mkdir -p ${APP_HOME}/dist \
|
||||||
&& cp ${APP_HOME}/build/libs/*.jar ${APP_HOME}/dist/${JAR_NAME} \
|
&& 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
|
USER app
|
||||||
|
|
||||||
ENV JAVA_OPTS="-Xms256m -Xmx512m -XX:+UseG1GC" \
|
ENV JAVA_OPTS="-Xms256m -Xmx512m -XX:+UseG1GC" \
|
||||||
|
SPRING_AOT_ENABLED=true \
|
||||||
SERVER_PORT=8080 \
|
SERVER_PORT=8080 \
|
||||||
PATH="/app:$PATH"
|
PATH="/app:$PATH"
|
||||||
|
|
||||||
@@ -70,4 +65,4 @@ HEALTHCHECK --interval=30s --timeout=3s --start-period=30s --retries=3 \
|
|||||||
|
|
||||||
STOPSIGNAL SIGTERM
|
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"]
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ plugins {
|
|||||||
jacoco
|
jacoco
|
||||||
}
|
}
|
||||||
|
|
||||||
|
apply(plugin = "org.springframework.boot.aot")
|
||||||
|
|
||||||
apply(from = "$rootDir/gradle/docker.gradle.kts")
|
apply(from = "$rootDir/gradle/docker.gradle.kts")
|
||||||
|
|
||||||
group = "com.project"
|
group = "com.project"
|
||||||
@@ -57,6 +59,7 @@ dependencies {
|
|||||||
testImplementation(libs.spring.boot.starter.test)
|
testImplementation(libs.spring.boot.starter.test)
|
||||||
testImplementation(libs.kotlin.test.junit5)
|
testImplementation(libs.kotlin.test.junit5)
|
||||||
testImplementation(libs.spring.grpc.test)
|
testImplementation(libs.spring.grpc.test)
|
||||||
|
testImplementation(libs.mockk)
|
||||||
testRuntimeOnly(libs.junit.platform.launcher)
|
testRuntimeOnly(libs.junit.platform.launcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Vendored
BIN
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
|
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.application.ports.output.UserRepositoryPort
|
||||||
|
import com.project.movienight.domain.model.AuthProvider
|
||||||
import com.project.movienight.domain.model.User
|
import com.project.movienight.domain.model.User
|
||||||
import org.springframework.jdbc.core.JdbcTemplate
|
import org.springframework.jdbc.core.JdbcTemplate
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
@@ -11,8 +15,8 @@ import java.util.UUID
|
|||||||
class UserRepository(
|
class UserRepository(
|
||||||
private val jdbc: JdbcTemplate,
|
private val jdbc: JdbcTemplate,
|
||||||
) : UserRepositoryPort {
|
) : UserRepositoryPort {
|
||||||
private val userRowMapper = { rs: ResultSet, _: Int ->
|
private val userEntityRowMapper = { rs: ResultSet, _: Int ->
|
||||||
User(
|
UserEntity(
|
||||||
id = UUID.fromString(rs.getString("id")),
|
id = UUID.fromString(rs.getString("id")),
|
||||||
name = rs.getString("name"),
|
name = rs.getString("name"),
|
||||||
email = rs.getString("email"),
|
email = rs.getString("email"),
|
||||||
@@ -22,6 +26,7 @@ class UserRepository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun save(user: User): User {
|
override fun save(user: User): User {
|
||||||
|
val entity = user.toEntity()
|
||||||
val updatedRows =
|
val updatedRows =
|
||||||
jdbc.update(
|
jdbc.update(
|
||||||
"""
|
"""
|
||||||
@@ -50,13 +55,13 @@ class UserRepository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun findById(id: UUID): User? {
|
override fun findById(id: UUID): User? {
|
||||||
val users =
|
val entities =
|
||||||
jdbc.query(
|
jdbc.query(
|
||||||
"SELECT id, name, email, password FROM users WHERE id = ?",
|
"SELECT id, name, email, password FROM users WHERE id = ?",
|
||||||
userRowMapper,
|
userRowMapper,
|
||||||
id,
|
id,
|
||||||
)
|
)
|
||||||
return users.firstOrNull()
|
return entities.firstOrNull()?.toDomain()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun findByEmail(email: String): User? {
|
override fun findByEmail(email: String): User? {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.project.movienight.application.ports.output
|
package com.project.movienight.application.ports.output
|
||||||
|
|
||||||
|
import com.project.movienight.domain.model.AuthProvider
|
||||||
import com.project.movienight.domain.model.User
|
import com.project.movienight.domain.model.User
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@@ -17,4 +18,9 @@ interface UserRepositoryPort {
|
|||||||
fun findAll(): List<User>
|
fun findAll(): List<User>
|
||||||
|
|
||||||
fun deleteById(id: UUID)
|
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,
|
||||||
|
}
|
||||||
+81
@@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+220
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
+153
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
+160
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
+301
@@ -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()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user