Skip to content

인물 삭제 기능을 개선하고 테스트 코드를 작성한다#63

Merged
komodgn merged 2 commits intodevelopfrom
refactor/#62
Jan 7, 2026
Merged

인물 삭제 기능을 개선하고 테스트 코드를 작성한다#63
komodgn merged 2 commits intodevelopfrom
refactor/#62

Conversation

@komodgn
Copy link
Owner

@komodgn komodgn commented Jan 7, 2026

Summary by CodeRabbit

  • Bug Fixes

    • Enhanced deletion error messaging with more helpful guidance for users.
  • Chores

    • Updated deletion confirmation dialog text for improved clarity.
    • Improved efficiency of person deletion operations.

Test

삭제 실패 토스트 메시지 확인

- coroutineScope와 async를 사용하여 Web/AI 서버 삭제 요청 병렬 처리
- 하드코딩된 Dispatchers.IO를 @iodispatcher 주입 방식으로 변경하여 테스트 용이성 확보
- runCatching을 통한 통합 예외 처리로 서버 실패 시 로컬 DB 무결성 보장
@coderabbitai
Copy link

coderabbitai bot commented Jan 7, 2026

📝 Walkthrough

Walkthrough

Introduces Dagger/Hilt integration for coroutine dispatchers via a new IoDispatcher qualifier, refactors PersonRepositoryImpl to inject and use the dispatcher while parallelizing concurrent remote and AI deletions, adds comprehensive unit tests for deletion scenarios, and updates UI strings for deletion dialogs and error messages.

Changes

Cohort / File(s) Summary
Coroutine Dependency Injection
core/data/impl/src/main/java/com/example/metasearch/core/data/impl/di/CoroutineModule.kt
New Hilt module adds @IoDispatcher qualifier annotation and provideIoDispatcher() singleton provider that exposes Dispatchers.IO for constructor injection throughout the codebase.
Repository Refactoring
core/data/impl/src/main/java/com/example/metasearch/core/data/impl/repository/PersonRepositoryImpl.kt
Injects @IoDispatcher CoroutineDispatcher parameter into constructor; replaces hardcoded Dispatchers.IO with injected instance; refactors deleteAnalyzedPerson to parallelize WebService and AIService deletions using coroutineScope with async/awaitAll instead of sequential calls.
Unit Tests
core/data/impl/src/test/java/com/example/metasearch/core/data/impl/repository/PersonRepositoryImplTest.kt
New test class with Mockito-based dependency mocking and StandardTestDispatcher injection; covers successful concurrent deletion flow and error handling when AIService deletion fails, ensuring local DB deletion is conditional on server operation success.
UI String Resources
feature/person/src/main/res/values/strings.xml
Updates person_delete_dialog_title from "인물 등록 취소" to "인물 삭제 확인"; updates person_delete_failed_toast_message with detailed error guidance and emoji indicator.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant PersonRepositoryImpl
    participant WebService
    participant AIService
    participant PersonDao
    
    Client->>PersonRepositoryImpl: deleteAnalyzedPerson(personId)
    
    rect rgb(200, 220, 240)
        Note over WebService,AIService: Concurrent Operations
        par WebService Deletion
            PersonRepositoryImpl->>WebService: deleteEntity(personId)
            WebService-->>PersonRepositoryImpl: success/exception
        and AIService Deletion
            PersonRepositoryImpl->>AIService: deletePerson(personId)
            AIService-->>PersonRepositoryImpl: success/exception
        end
    end
    
    alt All Remote Operations Succeed
        PersonRepositoryImpl->>PersonDao: deletePersonById(personId)
        PersonDao-->>PersonRepositoryImpl: completed
        PersonRepositoryImpl-->>Client: Result.success()
    else Any Remote Operation Fails
        rect rgb(255, 220, 220)
            Note over PersonRepositoryImpl: Error Handling
        end
        PersonRepositoryImpl-->>Client: Result.failure(exception)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly related issues

  • 인물 삭제 기능 개선 및 테스트 작성 #62 — Directly implements the "인물 삭제 기능 개선 및 테스트 작성" (improve person deletion functionality and write tests) feature with concurrent deletion operations and comprehensive test coverage.

Possibly related PRs

  • 인물 통합 기능을 추가한다 #47 — Also modifies PersonRepositoryImpl with additional repository methods; potential for merge conflict or coordinated refactoring around shared repository logic.

Poem

🐰 Dispatchers dance in harmony,
Async deletions run freely,
No more waiting, threads aligned,
Concurrent joy, the fast kind! 🚀

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main changes: improving person deletion functionality and adding test code for it.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
@core/data/impl/src/test/java/com/example/metasearch/core/data/impl/repository/PersonRepositoryImplTest.kt:
- Around line 41-53: The test opens Mockito annotations in setUp via
MockitoAnnotations.openMocks(this) but never closes the returned AutoCloseable;
store the returned value (e.g., a field named mocks or openMocks) when calling
MockitoAnnotations.openMocks(this) inside PersonRepositoryImplTest.setUp and add
an @AfterEach tearDown method that calls mocks.close() (or safely closes it) to
prevent resource leaks.
🧹 Nitpick comments (2)
core/data/impl/src/main/java/com/example/metasearch/core/data/impl/repository/PersonRepositoryImpl.kt (1)

102-105: Inconsistent dispatcher usage.

getPersonById still uses Dispatchers.IO directly instead of the injected ioDispatcher. For consistency and testability, consider using the injected dispatcher here as well.

♻️ Suggested fix
     override fun getPersonById(personId: Long): Flow<PersonModel?> =
         personDao.getPersonWithFacesFlow(personId)
             .map { it?.toModel(emptyMap()) }
-            .flowOn(Dispatchers.IO)
+            .flowOn(ioDispatcher)
core/data/impl/src/test/java/com/example/metasearch/core/data/impl/repository/PersonRepositoryImplTest.kt (1)

71-86: Consider adding a test for WebService failure.

The current tests cover AI service failure, but adding a similar test for webService.deleteEntity failure would provide symmetric coverage for both concurrent operations.

📝 Example test case
@Test
fun `webService 삭제 실패 시 로컬 DB 삭제를 호출하지 않는다`() = runTest(testDispatcher) {
    // Given
    whenever(mockDbRepo.getPersistentDeviceDatabaseName()).thenReturn("db_name")
    whenever(mockWebService.deleteEntity(any())).thenThrow(RuntimeException("Web Server Error"))

    // When
    val result = repository.deleteAnalyzedPerson(fakePerson)
    advanceUntilIdle()

    // Then
    assertTrue(result.isFailure)
    verify(mockDao, never()).deletePersonById(fakePerson.id)
}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fe39ff6 and 2b51815.

📒 Files selected for processing (4)
  • core/data/impl/src/main/java/com/example/metasearch/core/data/impl/di/CoroutineModule.kt
  • core/data/impl/src/main/java/com/example/metasearch/core/data/impl/repository/PersonRepositoryImpl.kt
  • core/data/impl/src/test/java/com/example/metasearch/core/data/impl/repository/PersonRepositoryImplTest.kt
  • feature/person/src/main/res/values/strings.xml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ci-build
🔇 Additional comments (5)
feature/person/src/main/res/values/strings.xml (1)

5-9: LGTM! Clear and user-friendly messages.

The updated dialog title and error message are more descriptive. The emoji in the toast message adds visual emphasis, though ensure it renders correctly across all supported Android versions.

core/data/impl/src/main/java/com/example/metasearch/core/data/impl/repository/PersonRepositoryImpl.kt (2)

166-178: Verify the name field usage for each service.

The code uses person.inputName for webService.deleteEntity (line 168) but person.name for aiService.deletePerson (line 173). Ensure this difference is intentional based on each service's API contract.


203-227: LGTM!

Good use of the injected ioDispatcher for IO-bound operations, maintaining consistency with the rest of the refactored code.

core/data/impl/src/main/java/com/example/metasearch/core/data/impl/di/CoroutineModule.kt (1)

12-24: LGTM! Standard Hilt pattern for dispatcher injection.

This enables testability by allowing test dispatchers to be injected. Consider adding DefaultDispatcher and MainDispatcher qualifiers in the future if other parts of the codebase need injectable dispatchers.

core/data/impl/src/test/java/com/example/metasearch/core/data/impl/repository/PersonRepositoryImplTest.kt (1)

55-69: LGTM! Well-structured test for success path.

Good verification of both remote service calls and local database deletion. The use of advanceUntilIdle() correctly handles the async operations.

Comment on lines +41 to +53
@BeforeEach
fun setUp() {
MockitoAnnotations.openMocks(this)
// 생성자 주입을 통해 테스트 디스패처 전달
repository = PersonRepositoryImpl(
personDao = mockDao,
aiService = mockAiService,
webService = mockWebService,
databaseNameRepository = mockDbRepo,
ioDispatcher = testDispatcher,
context = mockContext,
)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Close MockitoAnnotations to prevent resource leaks.

MockitoAnnotations.openMocks() returns an AutoCloseable that should be closed after each test to avoid resource leaks.

♻️ Suggested fix
+import org.junit.jupiter.api.AfterEach
+
 @OptIn(ExperimentalCoroutinesApi::class)
 class PersonRepositoryImplTest {
 
     @Mock private lateinit var mockDao: PersonDao
     // ... other mocks ...

     private val testDispatcher = StandardTestDispatcher()
     private lateinit var repository: PersonRepositoryImpl
+    private lateinit var closeable: AutoCloseable

     private val fakePerson = PersonModel(id = 1L, name = "test_uuid", inputName = "홍길동")

     @BeforeEach
     fun setUp() {
-        MockitoAnnotations.openMocks(this)
+        closeable = MockitoAnnotations.openMocks(this)
         repository = PersonRepositoryImpl(
             // ...
         )
     }
+
+    @AfterEach
+    fun tearDown() {
+        closeable.close()
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@BeforeEach
fun setUp() {
MockitoAnnotations.openMocks(this)
// 생성자 주입을 통해 테스트 디스패처 전달
repository = PersonRepositoryImpl(
personDao = mockDao,
aiService = mockAiService,
webService = mockWebService,
databaseNameRepository = mockDbRepo,
ioDispatcher = testDispatcher,
context = mockContext,
)
}
private lateinit var closeable: AutoCloseable
private val fakePerson = PersonModel(id = 1L, name = "test_uuid", inputName = "홍길동")
@BeforeEach
fun setUp() {
closeable = MockitoAnnotations.openMocks(this)
// 생성자 주입을 통해 테스트 디스패처 전달
repository = PersonRepositoryImpl(
personDao = mockDao,
aiService = mockAiService,
webService = mockWebService,
databaseNameRepository = mockDbRepo,
ioDispatcher = testDispatcher,
context = mockContext,
)
}
@AfterEach
fun tearDown() {
closeable.close()
}
🤖 Prompt for AI Agents
In
@core/data/impl/src/test/java/com/example/metasearch/core/data/impl/repository/PersonRepositoryImplTest.kt
around lines 41 - 53, The test opens Mockito annotations in setUp via
MockitoAnnotations.openMocks(this) but never closes the returned AutoCloseable;
store the returned value (e.g., a field named mocks or openMocks) when calling
MockitoAnnotations.openMocks(this) inside PersonRepositoryImplTest.setUp and add
an @AfterEach tearDown method that calls mocks.close() (or safely closes it) to
prevent resource leaks.

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Android CI Summary

Step Results:

  • Unit Test: ✅ Success (2m 58s)
  • Debug Build: ✅ Success (1m 12s)
  • Code Style Check: ✅ Success (3m 42s)

Total Time: 7m 52s

🎉 All steps completed successfully!

@komodgn komodgn merged commit a6afe5a into develop Jan 7, 2026
2 checks passed
@komodgn komodgn deleted the refactor/#62 branch January 7, 2026 08:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant