Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into convert-to-jpa/place-…
Browse files Browse the repository at this point in the history
…domain
  • Loading branch information
Zeniuus committed Aug 10, 2024
2 parents f8e7d3d + a4c16bf commit 2f71723
Show file tree
Hide file tree
Showing 45 changed files with 1,371 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,10 @@ dependencies {

implementation("net.coobird:thumbnailator:0.4.20")
implementation("org.sejda.webp-imageio:webp-imageio-sejda:0.1.0")
implementation("org.bytedeco:javacv-platform:1.5.10")

testImplementation("org.mockito.kotlin:mockito-kotlin:5.1.0")

integrationTestImplementation(projects.crossCuttingConcern.test.springIt)
integrationTestImplementation("org.springframework.boot:spring-boot-starter-web")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package club.staircrusher.accessibility.application.port.`in`

import club.staircrusher.accessibility.application.port.`in`.image.ImageProcessor
import club.staircrusher.accessibility.application.port.out.DetectFacesResponse
import club.staircrusher.accessibility.application.port.out.DetectFacesService
import club.staircrusher.accessibility.domain.model.DetectedFacePosition
import club.staircrusher.stdlib.Size
import club.staircrusher.testing.spring_it.ITDataGenerator
import club.staircrusher.testing.spring_it.base.SccSpringITBase
import club.staircrusher.testing.spring_it.mock.MockSccClock
import kotlinx.coroutines.runBlocking
import org.mockito.Mockito
import org.mockito.kotlin.eq
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.mock.mockito.MockBean

class BlurFacesITBase : SccSpringITBase() {
@Autowired
lateinit var dataGenerator: ITDataGenerator

@Autowired
lateinit var clock: MockSccClock

@MockBean
lateinit var imageProcessor: ImageProcessor

@MockBean
lateinit var detectFacesService: DetectFacesService


fun mockDetectFacesWithFaceImage(imageUrl: String, imageBytes: ByteArray) = runBlocking {
Mockito.`when`(detectFacesService.detect(eq(imageUrl))).thenReturn(
DetectFacesResponse(
imageBytes = imageBytes, imageSize = Size(100, 100), positions = listOf(DetectedFacePosition(0, 0, 10, 10))
)
)
}

fun mockDetectFacesWithNoFaceImage(imageUrl: String, imageBytes: ByteArray) = runBlocking {
Mockito.`when`(detectFacesService.detect(eq(imageUrl))).thenReturn(
DetectFacesResponse(
imageBytes = imageBytes, imageSize = Size(100, 100), positions = emptyList()
)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package club.staircrusher.accessibility.application.port.`in`

import club.staircrusher.accessibility.application.port.out.persistence.AccessibilityImageFaceBlurringHistoryRepository
import club.staircrusher.accessibility.application.port.out.persistence.BuildingAccessibilityRepository
import club.staircrusher.accessibility.application.port.out.persistence.PlaceAccessibilityRepository
import club.staircrusher.accessibility.domain.model.AccessibilityImage
import club.staircrusher.accessibility.domain.model.AccessibilityImageFaceBlurringHistory
import club.staircrusher.stdlib.testing.SccRandom
import club.staircrusher.testing.spring_it.base.SccSpringITApplication
import club.staircrusher.testing.spring_it.mock.MockDetectFacesService
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.mockito.Mockito
import org.mockito.kotlin.any
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import java.time.Duration

@SpringBootTest(classes = [SccSpringITApplication::class])
class BlurFacesInLatestBuildingAccessibilityImagesUseCaseTest : BlurFacesITBase() {
@Autowired
private lateinit var accessibilityImageFaceBlurringHistoryRepository: AccessibilityImageFaceBlurringHistoryRepository

@Autowired
private lateinit var blurFacesInLatestBuildingAccessibilityImagesUseCase: BlurFacesInLatestBuildingAccessibilityImagesUseCase

@Autowired
private lateinit var buildingAccessibilityRepository: BuildingAccessibilityRepository

@Autowired
private lateinit var placeAccessibilityRepository: PlaceAccessibilityRepository

@BeforeEach
fun setUp() = runBlocking {
val imageBytes = ByteArray(10) { it.toByte() }
mockDetectFacesWithFaceImage(MockDetectFacesService.URL_WITH_FACES, imageBytes)
mockDetectFacesWithNoFaceImage(MockDetectFacesService.URL_WITHOUT_FACES, imageBytes)
Mockito.`when`(imageProcessor.blur(any(), any(), any())).thenReturn(imageBytes)

placeAccessibilityRepository.removeAll()
buildingAccessibilityRepository.removeAll()
accessibilityImageFaceBlurringHistoryRepository.removeAll()
}

@Test
fun `얼굴 블러링 기록이 없으면 가장 오래된 building accessibility 의 이미지부터 얼굴 블러링한다`() {
val (_, _, oldestBuildingAccessibility) = registerPlaceAccessibilityAndBuildingAccessibility(
listOf(MockDetectFacesService.URL_WITH_FACES)
)
clock.advanceTime(Duration.ofMinutes(1))
val (_, _, secondOldestBuildingAccessibility) = registerPlaceAccessibilityAndBuildingAccessibility(
listOf(MockDetectFacesService.URL_WITH_FACES)
)

blurFacesInLatestBuildingAccessibilityImagesUseCase.handle()

val blurredResult = accessibilityImageFaceBlurringHistoryRepository.findByBuildingAccessibilityId(
oldestBuildingAccessibility.id
)
Assertions.assertTrue(blurredResult.isNotEmpty())
val notBlurredResult = accessibilityImageFaceBlurringHistoryRepository.findByBuildingAccessibilityId(
secondOldestBuildingAccessibility.id
)
Assertions.assertTrue(notBlurredResult.isEmpty())
}

@Test
fun `얼굴 블러링 기록이 이후 가장 오래된 building accessibility 의 이미지부터 얼굴 블러링한다`() {
val (_, _, oldestBuildingAccessibility) = registerPlaceAccessibilityAndBuildingAccessibility(
listOf(MockDetectFacesService.URL_WITH_FACES)
)
clock.advanceTime(Duration.ofMinutes(1))
val (_, _, secondOldestBuildingAccessibility) = registerPlaceAccessibilityAndBuildingAccessibility(
listOf(MockDetectFacesService.URL_WITH_FACES)
)
transactionManager.doInTransaction {
accessibilityImageFaceBlurringHistoryRepository.save(
AccessibilityImageFaceBlurringHistory(
id = "", placeAccessibilityId = null,
buildingAccessibilityId = oldestBuildingAccessibility.id,
originalImageUrls = listOf("image_url"),
blurredImageUrls = listOf("blurred_image_url"),
detectedPeopleCounts = emptyList(),
createdAt = clock.instant(),
updatedAt = clock.instant()
)
)
}

blurFacesInLatestBuildingAccessibilityImagesUseCase.handle()

val result = accessibilityImageFaceBlurringHistoryRepository.findByBuildingAccessibilityId(
secondOldestBuildingAccessibility.id
)
Assertions.assertTrue(result.isNotEmpty())
}

@Test
fun `BuildingAccessibility 이미지 중 얼굴이 감지된 사진만 업데이트한다`() = runBlocking {
val (_, _, buildingAccessibility) = registerPlaceAccessibilityAndBuildingAccessibility(
listOf(MockDetectFacesService.URL_WITH_FACES, MockDetectFacesService.URL_WITHOUT_FACES)
)

blurFacesInLatestBuildingAccessibilityImagesUseCase.handle()

val reloadBuildingAccessibility = transactionManager.doInTransaction {
buildingAccessibilityRepository.findByIdOrNull(buildingAccessibility.id)
}
val entranceImages = reloadBuildingAccessibility?.entranceImages?.map { it.imageUrl } ?: emptyList()
Assertions.assertFalse(entranceImages.contains(MockDetectFacesService.URL_WITH_FACES))
Assertions.assertTrue(entranceImages.contains(MockDetectFacesService.URL_WITHOUT_FACES))
val elevatorImages = reloadBuildingAccessibility?.entranceImages?.map { it.imageUrl } ?: emptyList()
Assertions.assertFalse(elevatorImages.contains(MockDetectFacesService.URL_WITH_FACES))
Assertions.assertTrue(elevatorImages.contains(MockDetectFacesService.URL_WITHOUT_FACES))
}


@Test
fun `이미 썸네일 처리가 된 BuildingAccessibility 의 경우 블러링 한 이미지 사용을 위해 썸네일 url을 제거한다`() = runBlocking {
val (_, _, buildingAccessibility) = registerPlaceAccessibilityAndBuildingAccessibility(
imageUrls = listOf(MockDetectFacesService.URL_WITH_FACES, MockDetectFacesService.URL_WITHOUT_FACES)
)

blurFacesInLatestBuildingAccessibilityImagesUseCase.handle()

val reloadBuildingAccessibility = transactionManager.doInTransaction {
buildingAccessibilityRepository.findByIdOrNull(buildingAccessibility.id)
}
val entranceImages = reloadBuildingAccessibility?.entranceImages ?: emptyList()
Assertions.assertTrue(entranceImages.isNotEmpty())
Assertions.assertTrue(entranceImages.mapNotNull { it.thumbnailUrl }.isEmpty())
val elevatorImages = reloadBuildingAccessibility?.entranceImages ?: emptyList()
Assertions.assertTrue(elevatorImages.isNotEmpty())
Assertions.assertTrue(elevatorImages.mapNotNull { it.thumbnailUrl }.isEmpty())
}

private fun registerPlaceAccessibilityAndBuildingAccessibility(imageUrls: List<String>) =
transactionManager.doInTransaction {
val user = dataGenerator.createUser()
val building = dataGenerator.createBuilding()
val place = dataGenerator.createPlace(placeName = SccRandom.string(32), building = building)
val (placeAccessibility, buildingAccessibility) = dataGenerator.registerBuildingAndPlaceAccessibility(
place = place, user = user, imageUrls = imageUrls, images = imageUrls.map { AccessibilityImage(it, it) }
)
Triple(user, placeAccessibility, buildingAccessibility)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package club.staircrusher.accessibility.application.port.`in`

import club.staircrusher.accessibility.application.port.out.persistence.AccessibilityImageFaceBlurringHistoryRepository
import club.staircrusher.accessibility.application.port.out.persistence.BuildingAccessibilityRepository
import club.staircrusher.accessibility.application.port.out.persistence.PlaceAccessibilityRepository
import club.staircrusher.accessibility.domain.model.AccessibilityImage
import club.staircrusher.accessibility.domain.model.AccessibilityImageFaceBlurringHistory
import club.staircrusher.stdlib.testing.SccRandom
import club.staircrusher.testing.spring_it.base.SccSpringITApplication
import club.staircrusher.testing.spring_it.mock.MockDetectFacesService
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.mockito.Mockito
import org.mockito.kotlin.any
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import java.time.Duration

@SpringBootTest(classes = [SccSpringITApplication::class])
class BlurFacesInLatestPlaceAccessibilityImagesUseCaseTest : BlurFacesITBase() {
@Autowired
private lateinit var accessibilityImageFaceBlurringHistoryRepository: AccessibilityImageFaceBlurringHistoryRepository

@Autowired
private lateinit var blurFacesInLatestPlaceAccessibilityImagesUseCase: BlurFacesInLatestPlaceAccessibilityImagesUseCase

@Autowired
private lateinit var buildingAccessibilityRepository: BuildingAccessibilityRepository

@Autowired
private lateinit var placeAccessibilityRepository: PlaceAccessibilityRepository

@BeforeEach
fun setUp() = runBlocking {
val imageBytes = ByteArray(10) { it.toByte() }
mockDetectFacesWithFaceImage(MockDetectFacesService.URL_WITH_FACES, imageBytes)
mockDetectFacesWithNoFaceImage(MockDetectFacesService.URL_WITHOUT_FACES, imageBytes)
Mockito.`when`(imageProcessor.blur(any(), any(), any())).thenReturn(imageBytes)

placeAccessibilityRepository.removeAll()
buildingAccessibilityRepository.removeAll()
accessibilityImageFaceBlurringHistoryRepository.removeAll()
}

@Test
fun `얼굴 블러링 기록이 없으면 가장 오래된 place accessibility 의 이미지부터 얼굴 블러링한다`() {
val (_, oldestPlaceAccessibility, _) = registerPlaceAccessibilityAndBuildingAccessibility(
listOf(MockDetectFacesService.URL_WITH_FACES)
)
clock.advanceTime(Duration.ofMinutes(1))
val (_, secondOldestPlaceAccessibility, _) = registerPlaceAccessibilityAndBuildingAccessibility(
listOf(MockDetectFacesService.URL_WITH_FACES)
)

blurFacesInLatestPlaceAccessibilityImagesUseCase.handle()

val blurredResult =
accessibilityImageFaceBlurringHistoryRepository.findByPlaceAccessibilityId(oldestPlaceAccessibility.id)
Assertions.assertTrue(blurredResult.isNotEmpty())
val notBlurredResult =
accessibilityImageFaceBlurringHistoryRepository.findByPlaceAccessibilityId(secondOldestPlaceAccessibility.id)
Assertions.assertTrue(notBlurredResult.isEmpty())
}

@Test
fun `얼굴 블러링 기록이 이후 가장 오래된 place accessibility 의 이미지부터 얼굴 블러링한다`() {
val (_, oldestPlaceAccessibility, _) = registerPlaceAccessibilityAndBuildingAccessibility(
listOf(MockDetectFacesService.URL_WITH_FACES)
)
clock.advanceTime(Duration.ofMinutes(1))
val (_, secondOldestPlaceAccessibility, _) = registerPlaceAccessibilityAndBuildingAccessibility(
listOf(MockDetectFacesService.URL_WITH_FACES)
)
transactionManager.doInTransaction {
accessibilityImageFaceBlurringHistoryRepository.save(
AccessibilityImageFaceBlurringHistory(
id = "",
placeAccessibilityId = oldestPlaceAccessibility.id,
buildingAccessibilityId = null,
originalImageUrls = listOf("image_url"),
blurredImageUrls = listOf("blurred_image_url"),
detectedPeopleCounts = emptyList(),
createdAt = clock.instant(),
updatedAt = clock.instant()
)
)
}

blurFacesInLatestPlaceAccessibilityImagesUseCase.handle()

val result =
accessibilityImageFaceBlurringHistoryRepository.findByPlaceAccessibilityId(secondOldestPlaceAccessibility.id)
Assertions.assertTrue(result.isNotEmpty())
}

@Test
fun `PlaceAccessibility 이미지 중 얼굴이 감지된 사진만 업데이트한다`() = runBlocking {
val (_, placeAccessibility, _) = registerPlaceAccessibilityAndBuildingAccessibility(
listOf(MockDetectFacesService.URL_WITH_FACES, MockDetectFacesService.URL_WITHOUT_FACES)
)

blurFacesInLatestPlaceAccessibilityImagesUseCase.handle()

val reloadPlaceAccessibility = transactionManager.doInTransaction {
placeAccessibilityRepository.findByIdOrNull(placeAccessibility.id)
}
val imageUrls = reloadPlaceAccessibility?.images?.map { it.imageUrl } ?: emptyList()
Assertions.assertFalse(imageUrls.contains(MockDetectFacesService.URL_WITH_FACES))
Assertions.assertTrue(imageUrls.contains(MockDetectFacesService.URL_WITHOUT_FACES))
}

@Test
fun `이미 썸네일 처리가 된 PlaceAccessibility 의 경우 블러링 한 이미지 사용을 위해 썸네일 url을 제거한다`() = runBlocking {
val (_, placeAccessibility, _) = registerPlaceAccessibilityAndBuildingAccessibility(
listOf(MockDetectFacesService.URL_WITH_FACES, MockDetectFacesService.URL_WITHOUT_FACES)
)

blurFacesInLatestPlaceAccessibilityImagesUseCase.handle()

val reloadPlaceAccessibility = transactionManager.doInTransaction {
placeAccessibilityRepository.findByIdOrNull(placeAccessibility.id)
}
val images = reloadPlaceAccessibility?.images ?: emptyList()
Assertions.assertTrue(images.isNotEmpty())
Assertions.assertTrue(images.mapNotNull { it.thumbnailUrl }.isEmpty())
}

private fun registerPlaceAccessibilityAndBuildingAccessibility(imageUrls: List<String>) =
transactionManager.doInTransaction {
val user = dataGenerator.createUser()
val building = dataGenerator.createBuilding()
val place = dataGenerator.createPlace(placeName = SccRandom.string(32), building = building)
val (placeAccessibility, buildingAccessibility) = dataGenerator.registerBuildingAndPlaceAccessibility(
place = place, user = user, imageUrls = imageUrls, images = imageUrls.map { AccessibilityImage(it, it) }
)
Triple(user, placeAccessibility, buildingAccessibility)
}
}
Loading

0 comments on commit 2f71723

Please sign in to comment.