Skip to content

Commit 3d9a1de

Browse files
claude[bot]github-actions[bot]claude
authored
Maintenance: SimpleKoreanEmbeddingService - Improve dependency injection and add comprehensive tests (#100)
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Claude <[email protected]>
1 parent cdb88e7 commit 3d9a1de

File tree

2 files changed

+169
-4
lines changed

2 files changed

+169
-4
lines changed

src/main/java/spring/memewikibe/infrastructure/ai/SimpleKoreanEmbeddingService.java

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,41 @@
11
package spring.memewikibe.infrastructure.ai;
22

33
import lombok.RequiredArgsConstructor;
4+
import org.springframework.beans.factory.annotation.Qualifier;
45
import org.springframework.stereotype.Service;
56

67
/**
7-
* Default stub implementation for KoreanEmbeddingService.
8-
* For now, it delegates to the existing EmbeddingService implementation (Vertex/Default).
9-
* Later, replace this with a NAVER embedding client and keep the interface stable.
8+
* Adapter implementation for KoreanEmbeddingService that delegates to the primary EmbeddingService.
9+
*
10+
* <p>This implementation serves as a transitional adapter while Korean-specific embedding
11+
* capabilities are being developed. It currently delegates to VertexAiEmbeddingService
12+
* (or DefaultEmbeddingService as fallback) for generating embeddings.
13+
*
14+
* <p><strong>Future Enhancement:</strong> This implementation will be replaced with a
15+
* NAVER AI Studio embedding client optimized for Korean text. The KoreanEmbeddingService
16+
* interface will remain stable to avoid impacting dependent code.
17+
*
18+
* @see KoreanEmbeddingService
19+
* @see EmbeddingService
1020
*/
1121
@Service
1222
@RequiredArgsConstructor
1323
public class SimpleKoreanEmbeddingService implements KoreanEmbeddingService {
1424

15-
private final EmbeddingService delegate; // existing embedding provider bean
25+
/**
26+
* The primary embedding service to delegate to.
27+
* Explicitly qualified to avoid ambiguity with multiple EmbeddingService implementations.
28+
*/
29+
@Qualifier("vertexAiEmbeddingService")
30+
private final EmbeddingService delegate;
1631

32+
/**
33+
* Generates an embedding vector for the given Korean text.
34+
*
35+
* @param text the input text to embed (may be null or blank)
36+
* @return a float array representing the embedding vector
37+
* @throws IllegalArgumentException if text processing fails
38+
*/
1739
@Override
1840
public float[] embed(String text) {
1941
return delegate.embed(text);
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package spring.memewikibe.infrastructure.ai;
2+
3+
import org.junit.jupiter.api.BeforeEach;
4+
import org.junit.jupiter.api.DisplayName;
5+
import org.junit.jupiter.api.Test;
6+
import org.junit.jupiter.api.extension.ExtendWith;
7+
import org.mockito.Mock;
8+
import org.mockito.junit.jupiter.MockitoExtension;
9+
import spring.memewikibe.annotation.UnitTest;
10+
11+
import static org.assertj.core.api.Assertions.assertThat;
12+
import static org.mockito.Mockito.verify;
13+
import static org.mockito.Mockito.when;
14+
15+
@UnitTest
16+
@ExtendWith(MockitoExtension.class)
17+
@DisplayName("SimpleKoreanEmbeddingService 단위 테스트")
18+
class SimpleKoreanEmbeddingServiceTest {
19+
20+
@Mock
21+
private EmbeddingService mockEmbeddingService;
22+
23+
private SimpleKoreanEmbeddingService sut;
24+
25+
@BeforeEach
26+
void setUp() {
27+
sut = new SimpleKoreanEmbeddingService(mockEmbeddingService);
28+
}
29+
30+
@Test
31+
@DisplayName("embed: 정상적인 한글 텍스트를 임베딩 벡터로 변환")
32+
void embed_succeeds_withValidKoreanText() {
33+
// given
34+
String koreanText = "안녕하세요 밈 위키입니다";
35+
float[] expectedEmbedding = new float[]{0.1f, 0.2f, 0.3f};
36+
when(mockEmbeddingService.embed(koreanText)).thenReturn(expectedEmbedding);
37+
38+
// when
39+
float[] result = sut.embed(koreanText);
40+
41+
// then
42+
assertThat(result).isEqualTo(expectedEmbedding);
43+
verify(mockEmbeddingService).embed(koreanText);
44+
}
45+
46+
@Test
47+
@DisplayName("embed: null 텍스트를 delegate에 전달")
48+
void embed_delegatesNullText_toUnderlyingService() {
49+
// given
50+
String nullText = null;
51+
float[] expectedEmbedding = new float[]{0.0f, 0.0f, 0.0f};
52+
when(mockEmbeddingService.embed(nullText)).thenReturn(expectedEmbedding);
53+
54+
// when
55+
float[] result = sut.embed(nullText);
56+
57+
// then
58+
assertThat(result).isEqualTo(expectedEmbedding);
59+
verify(mockEmbeddingService).embed(nullText);
60+
}
61+
62+
@Test
63+
@DisplayName("embed: 빈 문자열을 delegate에 전달")
64+
void embed_delegatesEmptyString_toUnderlyingService() {
65+
// given
66+
String emptyText = "";
67+
float[] expectedEmbedding = new float[]{0.0f, 0.0f, 0.0f};
68+
when(mockEmbeddingService.embed(emptyText)).thenReturn(expectedEmbedding);
69+
70+
// when
71+
float[] result = sut.embed(emptyText);
72+
73+
// then
74+
assertThat(result).isEqualTo(expectedEmbedding);
75+
verify(mockEmbeddingService).embed(emptyText);
76+
}
77+
78+
@Test
79+
@DisplayName("embed: 공백만 있는 문자열을 delegate에 전달")
80+
void embed_delegatesBlankString_toUnderlyingService() {
81+
// given
82+
String blankText = " ";
83+
float[] expectedEmbedding = new float[]{0.0f, 0.0f, 0.0f};
84+
when(mockEmbeddingService.embed(blankText)).thenReturn(expectedEmbedding);
85+
86+
// when
87+
float[] result = sut.embed(blankText);
88+
89+
// then
90+
assertThat(result).isEqualTo(expectedEmbedding);
91+
verify(mockEmbeddingService).embed(blankText);
92+
}
93+
94+
@Test
95+
@DisplayName("embed: 긴 한글 텍스트를 임베딩 벡터로 변환")
96+
void embed_succeeds_withLongKoreanText() {
97+
// given
98+
String longText = "밈(Meme)은 문화적 정보나 아이디어가 사람들 사이에서 전파되는 것을 설명하는 개념입니다. " +
99+
"인터넷 밈은 일반적으로 이미지, 동영상, 해시태그 등의 형태로 소셜 미디어를 통해 빠르게 확산됩니다.";
100+
float[] expectedEmbedding = new float[1536]; // typical embedding dimension
101+
when(mockEmbeddingService.embed(longText)).thenReturn(expectedEmbedding);
102+
103+
// when
104+
float[] result = sut.embed(longText);
105+
106+
// then
107+
assertThat(result).isEqualTo(expectedEmbedding);
108+
assertThat(result.length).isEqualTo(1536);
109+
verify(mockEmbeddingService).embed(longText);
110+
}
111+
112+
@Test
113+
@DisplayName("embed: 혼합된 한글/영어 텍스트를 처리")
114+
void embed_succeeds_withMixedKoreanEnglishText() {
115+
// given
116+
String mixedText = "Meme은 밈입니다 #trending #인기밈";
117+
float[] expectedEmbedding = new float[]{0.5f, 0.6f, 0.7f};
118+
when(mockEmbeddingService.embed(mixedText)).thenReturn(expectedEmbedding);
119+
120+
// when
121+
float[] result = sut.embed(mixedText);
122+
123+
// then
124+
assertThat(result).isEqualTo(expectedEmbedding);
125+
verify(mockEmbeddingService).embed(mixedText);
126+
}
127+
128+
@Test
129+
@DisplayName("embed: 특수문자가 포함된 텍스트를 처리")
130+
void embed_succeeds_withSpecialCharacters() {
131+
// given
132+
String textWithSpecialChars = "안녕하세요! 😀 #밈 @사용자 https://example.com";
133+
float[] expectedEmbedding = new float[]{0.8f, 0.9f, 1.0f};
134+
when(mockEmbeddingService.embed(textWithSpecialChars)).thenReturn(expectedEmbedding);
135+
136+
// when
137+
float[] result = sut.embed(textWithSpecialChars);
138+
139+
// then
140+
assertThat(result).isEqualTo(expectedEmbedding);
141+
verify(mockEmbeddingService).embed(textWithSpecialChars);
142+
}
143+
}

0 commit comments

Comments
 (0)