-
Notifications
You must be signed in to change notification settings - Fork 3
test(submission): add infra layer tests #120
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| package org.ezcode.codetest.infrastructure.event; | ||
|
|
||
| import static org.assertj.core.api.Assertions.*; | ||
| import static org.junit.jupiter.api.Assertions.*; | ||
| import static org.mockito.BDDMockito.*; | ||
|
|
||
| import java.util.Map; | ||
|
|
||
| import org.ezcode.codetest.application.submission.service.SubmissionService; | ||
| import org.ezcode.codetest.domain.submission.exception.SubmissionException; | ||
| import org.ezcode.codetest.domain.submission.exception.code.SubmissionExceptionCode; | ||
| import org.ezcode.codetest.infrastructure.event.dto.submission.SubmissionMessage; | ||
| import org.ezcode.codetest.infrastructure.event.listener.RedisJudgeQueueConsumer; | ||
| import org.junit.jupiter.api.BeforeEach; | ||
| import org.junit.jupiter.api.DisplayName; | ||
| import org.junit.jupiter.api.Test; | ||
| import org.junit.jupiter.api.extension.ExtendWith; | ||
| import org.mockito.ArgumentCaptor; | ||
| import org.mockito.InjectMocks; | ||
| import org.mockito.Mock; | ||
| import org.mockito.junit.jupiter.MockitoExtension; | ||
| import org.springframework.data.redis.connection.stream.MapRecord; | ||
| import org.springframework.data.redis.connection.stream.RecordId; | ||
| import org.springframework.data.redis.core.StreamOperations; | ||
| import org.springframework.data.redis.core.StringRedisTemplate; | ||
|
|
||
| @ExtendWith(MockitoExtension.class) | ||
| @DisplayName("RedisJudgeQueueConsumer 단위 테스트") | ||
| public class RedisJudgeQueueConsumerTest { | ||
|
|
||
| @InjectMocks | ||
| private RedisJudgeQueueConsumer consumer; | ||
|
|
||
| @Mock | ||
| private SubmissionService submissionService; | ||
|
|
||
| @Mock | ||
| private StringRedisTemplate redisTemplate; | ||
|
|
||
| @Mock | ||
| private StreamOperations<String, Object, Object> streamOps; | ||
|
|
||
| @Mock | ||
| private MapRecord<String, String, String> record; | ||
|
|
||
| @BeforeEach | ||
| void setUp() { | ||
| Map<String, String> payload = Map.of( | ||
| "sessionKey", "session-key", | ||
| "problemId", "42", | ||
| "languageId", "7", | ||
| "userId", "100", | ||
| "sourceCode", "System.out.println(123);" | ||
| ); | ||
| RecordId recordId = RecordId.of("1-0"); | ||
|
|
||
| given(record.getValue()).willReturn(payload); | ||
| given(record.getId()).willReturn(recordId); | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("정상 처리 -> processSubmissionAsync & ack 호출") | ||
| void onMessage_success() { | ||
|
|
||
| // given | ||
| given(redisTemplate.opsForStream()).willReturn(streamOps); | ||
|
|
||
| // when | ||
| consumer.onMessage(record); | ||
|
|
||
| // then | ||
| ArgumentCaptor<SubmissionMessage> captor = ArgumentCaptor.forClass(SubmissionMessage.class); | ||
| then(submissionService).should().processSubmissionAsync(captor.capture()); | ||
|
|
||
| SubmissionMessage sent = captor.getValue(); | ||
|
|
||
| assertAll( | ||
| () -> assertThat(sent.sessionKey()).isEqualTo("session-key"), | ||
| () -> assertThat(sent.problemId()).isEqualTo(42L), | ||
| () -> assertThat(sent.languageId()).isEqualTo(7L), | ||
| () -> assertThat(sent.userId()).isEqualTo(100L), | ||
| () -> assertThat(sent.sourceCode()).isEqualTo("System.out.println(123);") | ||
| ); | ||
|
|
||
| then(redisTemplate.opsForStream()).should().acknowledge("judge-group", record); | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("processSubmissionAsync 에러 -> SubmissionException & ack 미호출") | ||
| void onMessage_failure() { | ||
|
|
||
| // given | ||
| doThrow(new RuntimeException("oops")) | ||
| .when(submissionService).processSubmissionAsync(any()); | ||
|
|
||
| // when & then | ||
| assertThatThrownBy(() -> consumer.onMessage(record)) | ||
| .isInstanceOf(SubmissionException.class) | ||
| .hasMessage(SubmissionExceptionCode.REDIS_SERVER_ERROR.getMessage()); | ||
|
|
||
| then(streamOps).should(never()).acknowledge(anyString(), any()); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| package org.ezcode.codetest.infrastructure.event; | ||
|
|
||
| import static org.assertj.core.api.Assertions.*; | ||
| import static org.junit.jupiter.api.Assertions.*; | ||
| import static org.mockito.BDDMockito.*; | ||
|
|
||
| import java.util.Map; | ||
|
|
||
| import org.ezcode.codetest.infrastructure.event.dto.submission.SubmissionMessage; | ||
| import org.ezcode.codetest.infrastructure.event.publisher.RedisJudgeQueueProducer; | ||
| import org.junit.jupiter.api.BeforeEach; | ||
| import org.junit.jupiter.api.DisplayName; | ||
| import org.junit.jupiter.api.Test; | ||
| import org.junit.jupiter.api.extension.ExtendWith; | ||
| import org.mockito.ArgumentCaptor; | ||
| import org.mockito.InjectMocks; | ||
| import org.mockito.Mock; | ||
| import org.mockito.junit.jupiter.MockitoExtension; | ||
| import org.springframework.data.redis.core.RedisTemplate; | ||
| import org.springframework.data.redis.core.StreamOperations; | ||
|
|
||
| @ExtendWith(MockitoExtension.class) | ||
| @DisplayName("RedisJudgeQueueProducer 단위 테스트") | ||
| public class RedisJudgeQueueProducerTest { | ||
|
|
||
| @InjectMocks | ||
| private RedisJudgeQueueProducer producer; | ||
|
|
||
| @Mock | ||
| private RedisTemplate<String, String> redisTemplate; | ||
|
|
||
| @Mock | ||
| private StreamOperations<String, Object, Object> streamOps; | ||
|
|
||
| @BeforeEach | ||
| void setUp() { | ||
| given(redisTemplate.opsForStream()).willReturn(streamOps); | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("enqueue -> judge-queue에 메시지 추가") | ||
| void enqueue_addToStream() { | ||
|
|
||
| // given | ||
| SubmissionMessage msg = new SubmissionMessage( | ||
| "session-key", | ||
| 42L, | ||
| 7L, | ||
| 100L, | ||
| "System.out.println(123);" | ||
| ); | ||
|
|
||
| // when | ||
| producer.enqueue(msg); | ||
|
|
||
| // then | ||
| @SuppressWarnings("unchecked") | ||
| ArgumentCaptor<Map<String, String>> captor = ArgumentCaptor.forClass(Map.class); | ||
|
|
||
| then(streamOps).should() | ||
| .add(eq("judge-queue"), captor.capture()); | ||
|
|
||
| Map<String, String> actual = captor.getValue(); | ||
| assertAll( | ||
| () -> assertThat(actual.get("sessionKey")).isEqualTo("session-key"), | ||
| () -> assertThat(actual.get("problemId")).isEqualTo("42"), | ||
| () -> assertThat(actual.get("languageId")).isEqualTo("7"), | ||
| () -> assertThat(actual.get("userId")).isEqualTo("100"), | ||
| () -> assertThat(actual.get("sourceCode")).isEqualTo("System.out.println(123);") | ||
| ); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,121 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package org.ezcode.codetest.infrastructure.github; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import static org.mockito.BDDMockito.*; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.List; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.Map; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.Optional; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.ezcode.codetest.application.submission.dto.request.github.GitHubPushRequest; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.ezcode.codetest.infrastructure.github.model.CommitContext; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.junit.jupiter.api.BeforeEach; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.junit.jupiter.api.DisplayName; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.junit.jupiter.api.Test; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.junit.jupiter.api.extension.ExtendWith; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.mockito.InjectMocks; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.mockito.Mock; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.mockito.junit.jupiter.MockitoExtension; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @ExtendWith(MockitoExtension.class) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @DisplayName("GitHubClient 단위 테스트") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public class GitHubClientTest { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @InjectMocks | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| GitHubClientImpl gitHubClientImpl; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Mock | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private GitHubApiClient gitHubApiClient; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Mock | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private GitHubContentBuilder templateBuilder; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Mock | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private GitBlobCalculator blobCalculator; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Mock | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private GitHubPushRequest request; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Mock | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private CommitContext ctx; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private String sourceCode; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @BeforeEach | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void setup() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sourceCode = "source-code"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Test | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @DisplayName("동일한 SHA -> 얼리 리턴") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void blobUnchanged_earlyReturn() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // given | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String sha = "abc123"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| given(request.sourceCode()).willReturn(sourceCode); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| given(blobCalculator.calculateBlobSha(request.sourceCode())).willReturn(sha); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| given(gitHubApiClient.fetchSourceBlobSha(request)).willReturn(Optional.of(sha)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // when | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gitHubClientImpl.commitAndPushToRepo(request); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| then(gitHubApiClient).should(never()).fetchCommitContext(any()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| then(templateBuilder).should(never()).buildGitTreeEntries(any(), any()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| then(gitHubApiClient).should(never()).createTree(any(), any(), anyList()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| then(gitHubApiClient).should(never()).commitAndPush(any(), any(), any()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Test | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @DisplayName("새 SHA == 트리 SHA -> 얼리 리턴") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void newBlobEqualsTreeBlob_earlyReturn() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // given | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String oldSha = "old"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String newSha = "new"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| given(request.sourceCode()).willReturn(sourceCode); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| given(blobCalculator.calculateBlobSha(request.sourceCode())).willReturn(newSha); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| given(gitHubApiClient.fetchSourceBlobSha(request)).willReturn(Optional.of(oldSha)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| given(gitHubApiClient.fetchCommitContext(request)).willReturn(ctx); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| List<Map<String, Object>> entries = List.of(Map.of()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| given(templateBuilder.buildGitTreeEntries(request, newSha)).willReturn(entries); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| given(ctx.baseTreeSha()).willReturn("tree"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| given(gitHubApiClient.createTree(request, "tree", entries)).willReturn("tree"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // when | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gitHubClientImpl.commitAndPushToRepo(request); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| then(gitHubApiClient).should().fetchCommitContext(request); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| then(templateBuilder).should().buildGitTreeEntries(request, newSha); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| then(gitHubApiClient).should().createTree(request, "tree", entries); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| then(gitHubApiClient).should(never()).commitAndPush(any(), any(), any()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Test | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @DisplayName("SHA 변경 & 트리 SHA 변경 시 커밋 + 푸시") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void blobAndTreeChanged_commitAndPush() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // given | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String oldSha = "old"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String newSha = "new"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| given(blobCalculator.calculateBlobSha(request.sourceCode())).willReturn(newSha); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| given(gitHubApiClient.fetchSourceBlobSha(request)).willReturn(Optional.of(oldSha)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| given(gitHubApiClient.fetchCommitContext(request)).willReturn(ctx); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| List<Map<String,Object>> entries = List.of(Map.of()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| given(templateBuilder.buildGitTreeEntries(request, newSha)).willReturn(entries); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| given(ctx.baseTreeSha()).willReturn("tree"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| given(ctx.headCommitSha()).willReturn("head"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String newTree = "newTree"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| given(gitHubApiClient.createTree(request, "tree", entries)).willReturn(newTree); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // when | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gitHubClientImpl.commitAndPushToRepo(request); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| then(gitHubApiClient).should().commitAndPush(request, "head", newTree); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+100
to
+120
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 모킹 설정의 일관성을 개선해주세요. 세 번째 테스트에서 다음 diff를 적용하여 모킹 설정을 완성해주세요: // given
String oldSha = "old";
String newSha = "new";
+ given(request.sourceCode()).willReturn(sourceCode);
given(blobCalculator.calculateBlobSha(request.sourceCode())).willReturn(newSha);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 어라 빼먹어도 잘 돌아가던데... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @thezz9 맞습니다, 테스트가 실제로 통과할 수 있어요. 아마도 Mockito가 모킹되지 않은 메서드 호출에 대해 기본값(null 등)을 반환하거나, 하지만 일관된 모킹을 유지하는 것이 좋은 이유는:
현재는 우연히 동작하는 상황이지만, 나중에 🧠 Learnings used |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
IllegalStateException 매핑의 근거 확인 필요
IllegalStateException이COMPILE_TIMEOUT으로 매핑되는 것이 추가되었습니다. WebClient가 어떤 상황에서 이 예외를 던지는지 명확히 문서화하거나 주석을 추가하면 좋겠습니다.WebClient가 IllegalStateException을 던지는 시나리오를 확인해주세요:
🌐 Web query:
💡 Result:
Spring WebFlux’s WebClient can throw a
java.lang.IllegalStateExceptionin a few key scenarios. The two main cases supported by your search results are:1. Blocking Calls Inside Non-blocking (Reactive) Threads
You call
block(),blockFirst(), orblockLast()on a reactive publisher (like aMonoorFlux) returned by WebClient, but do so within a thread handled by the Reactor Netty event loop (such asreactor-http-nio-*orreactor-http-epoll-*). These threads must remain non-blocking for the scalability of the reactive model.The Reactor framework enforces non-blocking behavior; blocking calls in these threads can stall the entire event loop, hurting performance and defeating the purpose of using reactive programming.
Use only non-blocking operations. If you must block (e.g., for legacy interop), switch to a thread designed for blocking (such as those from
Schedulers.boundedElastic()), never the event loop threads[1][4][6].2. Only One Subscriber for Response Body
IllegalStateException: Only one connection receive subscriber allowedis thrown if you try to subscribe more than once to the same response body, such as with a POST request using certain content types (e.g.,application/x-www-form-urlencoded), or by calling a terminal operation on the same response multiple times.The HTTP response body in WebClient can only be read once. Attempting a second subscription is an illegal state[7].
3. Decoder Not Found for Response Type
WebClient throws
IllegalStateExceptionif it cannot find a suitable decoder to convert the HTTP response into the requested Java type.If you attempt to extract a response body as a specific object or data structure but no compatible decoder is registered, this exception is thrown[2].
Summary Table
block()or similar on a Netty/reactor event loop threadIllegalStateException: block()/blockFirst()/blockLast() are blocking, which is not supported in thread ...IllegalStateException: Only one connection receive subscriber allowedIllegalStateException: No suitable decoder foundBest Practices:
boundedElastic[1][4][6].If you experience
IllegalStateExceptionwith WebClient, first check if any of these conditions are true in your application logic.Citations:
IllegalStateException 매핑 재검토 및 구체화 필요
WebClient가 던지는
IllegalStateException은 다음과 같이 다양한 원인으로 발생합니다.위 모든 경우를
COMPILE_TIMEOUT으로 매핑하면 실제 컴파일 타임아웃 외의 이슈가 가려질 수 있습니다.따라서 아래 중 하나를 적용해 매핑 조건을 구체화하세요:
ex.getMessage()에"block()/blockFirst()/blockLast()"문구가 포함된 경우만COMPILE_TIMEOUT으로 매핑IllegalStateException매핑 제거대상 위치:
🤖 Prompt for AI Agents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RetryExhaustedException으로 넘어오는데 이게 package-private 클래스라 import 할 수가 없어서 부모 클래스인 IllegalStateException 를 쓴 거야.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
@thezz9 완전히 이해했습니다!
RetryExhaustedException이 package-private 클래스라서 직접 import할 수 없어서 부모 클래스인IllegalStateException를 사용하신 거군요.이는 Reactor 라이브러리의 API 설계 제한으로 인한 합리적인 해결책입니다. 다만 이런 배경을 모르는 다른 개발자들을 위해 주석으로 설명을 추가하시면 좋을 것 같습니다:
이전 리뷰 코멘트는 철회하겠습니다. 감사합니다! 🙏
🌐 Web query:
💡 Result:
The
RetryExhaustedExceptionis a class that is often found in retry mechanism libraries or frameworks, such as those used in networking or task execution. You are asking about its package-private parent class, which isIllegalStateException.However,
IllegalStateExceptionitself is a public class in Java and not package-private. It is part of thejava.langpackage and is a subclass ofRuntimeException.If
RetryExhaustedExceptionhas a package-private parent class (a class with no public or protected modifier, thus only accessible within its own package), that means there is a custom exception class thatRetryExhaustedExceptionextends, which itself might extendIllegalStateException.Could you please specify the library or framework you are referring to or provide more context? This will help me provide a more accurate explanation or code example.
🌐 Web query:
💡 Result:
When using Project Reactor's
retryWhenoperator, if all retries are exhausted, the framework throws a RetryExhaustedException that wraps the last error encountered during the retry sequence[1][3][4]. This exception helps you programmatically distinguish between a failure due to retry exhaustion and other types of errors.Package-private Visibility of RetryExhaustedException
RetryExhaustedException(sometimes known in older versions asRetry.RetryExhaustedException).reactor-extra,RetryExhaustedExceptionis a public class extendingRuntimeException[10]. Its constructors are all public, which means you can catch and use this exception freely if you have direct access to the reactor-extra dependency.RetryExhaustedExceptionas a public class in the core package documentation[9]. Instead, if you useretryWhenwith aRetryspec and do not customize the exhaustion behavior, the framework will throw a package-private exception internally. This means:reactor.util.retry(or internal) package.onRetryExhaustedThrowmethod to throw your own exception[1][3][7].Why package-private?
What to do if you need custom handling?
onRetryExhaustedThrow, supplying your own (public) exception type[1][3][7]:This approach guarantees that your code can handle the exhausted scenario with an exception type you control and expose publicly[1][3].
Summary Table: Retry Exhaustion Exception Handling
Key Point:
If you depend on exception type in application code, always customize via
onRetryExhaustedThrowto avoid issues with package-private visibility and ensure your code remains maintainable and robust[1][3][7].Citations:
@thezz9 맞습니다! 확인 결과 reactor-core에서 재시도가 모두 실패했을 때 던지는 예외는 실제로 package-private이어서 직접 import하거나 catch할 수 없네요.
현재 접근 방식이 합리적인 해결책입니다. 다만 두 가지 개선 방안을 제안드립니다:
방안 1: 현재 방식 + 주석 추가 (간단함)
방안 2: onRetryExhaustedThrow 사용 (더 명확함)
현재 방식도 충분히 좋으니 주석만 추가하셔도 됩니다!
✏️ Learnings added
🧠 Learnings used