From 63c4a26fa55580419a6135ee53abc4b2aa6bcfb0 Mon Sep 17 00:00:00 2001 From: seungyeop-lee Date: Thu, 23 May 2024 09:58:20 +0900 Subject: [PATCH] =?UTF-8?q?chore:=20meilisearch=20testcontainer=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#60?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/api/devhelper/InitService.java | 38 +----------- .../server/api/devhelper/TestTermsLoader.java | 52 ++++++++++++++++ .../search/DemoTermSearchService.java | 7 ++- .../search/MeilisearchProperties.java | 15 +++++ .../outbound/search/MeilisearchService.java | 15 +---- .../vook/server/api/testhelper/ApiTest.java | 20 +++++- .../api/testhelper/MeilisearchContainer.java | 34 ++++++++++ .../web/routes/demo/DemoWebServiceTest.java | 62 +++++++++++++++++++ api/src/test/resources/application.yml | 3 - 9 files changed, 192 insertions(+), 54 deletions(-) create mode 100644 api/src/main/java/vook/server/api/devhelper/TestTermsLoader.java create mode 100644 api/src/main/java/vook/server/api/outbound/search/MeilisearchProperties.java create mode 100644 api/src/test/java/vook/server/api/testhelper/MeilisearchContainer.java create mode 100644 api/src/test/java/vook/server/api/web/routes/demo/DemoWebServiceTest.java diff --git a/api/src/main/java/vook/server/api/devhelper/InitService.java b/api/src/main/java/vook/server/api/devhelper/InitService.java index e23e20c..76bd72c 100644 --- a/api/src/main/java/vook/server/api/devhelper/InitService.java +++ b/api/src/main/java/vook/server/api/devhelper/InitService.java @@ -17,7 +17,6 @@ import java.io.IOException; import java.io.InputStream; -import java.util.Arrays; import java.util.List; @Service @@ -35,6 +34,7 @@ public class InitService { private final UserRepository userRepository; private final TermsRepository termsRepository; private final DemoTermSearchService searchService; + private final TestTermsLoader testTermsLoader; public void init() { deleteAll(); @@ -43,7 +43,7 @@ public void init() { termsRepository.save(Terms.of("개인정보 수집 이용 약관", loadContents("classpath:init/개인정보_수집_이용_약관.txt"), true)); termsRepository.save(Terms.of("마케팅 메일 수신 약관", loadContents("classpath:init/마케팅_메일_수신_약관.txt"), false)); - List devTerms = getTerms("classpath:init/개발.tsv"); + List devTerms = testTermsLoader.getTerms("classpath:init/개발.tsv"); demoTermRepository.saveAll(devTerms); searchService.init(); @@ -76,38 +76,4 @@ private String loadContents(String location) { throw new RuntimeException(e); } } - - private List getTerms(String location) { - try { - // file로 바로 접근 할 경우, IDE에서는 접근 가능하나, jar로 패키징 후 실행 시에는 접근 불가능 - // ref) https://velog.io/@haron/트러블슈팅-Spring-IDE-에서-되는데-배포하면-안-돼요 - InputStream tsvFileInputStream = resourceLoader.getResource(location).getInputStream(); - CsvReader tsvReader = new CsvReader("\t"); - List rawTerms = tsvReader.readValue(tsvFileInputStream, RawTerm.class); - return toTerms(rawTerms); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - private static List toTerms(List rawTerms) { - return rawTerms.stream() - .map(RawTerm::toTerm) - .toList(); - } - - public static class RawTerm { - private String term; - private String synonyms; - private String meaning; - - public DemoTerm toTerm() { - DemoTerm term = DemoTerm.forCreateOf(this.term, this.meaning); - String[] synonymArray = this.synonyms.split("//n"); - Arrays.stream(synonymArray) - .map(String::trim) - .forEach(term::addSynonym); - return term; - } - } } diff --git a/api/src/main/java/vook/server/api/devhelper/TestTermsLoader.java b/api/src/main/java/vook/server/api/devhelper/TestTermsLoader.java new file mode 100644 index 0000000..4cc9b9d --- /dev/null +++ b/api/src/main/java/vook/server/api/devhelper/TestTermsLoader.java @@ -0,0 +1,52 @@ +package vook.server.api.devhelper; + +import lombok.RequiredArgsConstructor; +import org.springframework.core.io.ResourceLoader; +import org.springframework.stereotype.Component; +import vook.server.api.model.demo.DemoTerm; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.List; + +@Component +@RequiredArgsConstructor +public class TestTermsLoader { + + private final ResourceLoader resourceLoader; + + public List getTerms(String location) { + try { + // file로 바로 접근 할 경우, IDE에서는 접근 가능하나, jar로 패키징 후 실행 시에는 접근 불가능 + // ref) https://velog.io/@haron/트러블슈팅-Spring-IDE-에서-되는데-배포하면-안-돼요 + InputStream tsvFileInputStream = resourceLoader.getResource(location).getInputStream(); + CsvReader tsvReader = new CsvReader("\t"); + List rawTerms = tsvReader.readValue(tsvFileInputStream, RawTerm.class); + return toTerms(rawTerms); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + static List toTerms(List rawTerms) { + return rawTerms.stream() + .map(RawTerm::toTerm) + .toList(); + } + + static class RawTerm { + private String term; + private String synonyms; + private String meaning; + + public DemoTerm toTerm() { + DemoTerm term = DemoTerm.forCreateOf(this.term, this.meaning); + String[] synonymArray = this.synonyms.split("//n"); + Arrays.stream(synonymArray) + .map(String::trim) + .forEach(term::addSynonym); + return term; + } + } +} diff --git a/api/src/main/java/vook/server/api/outbound/search/DemoTermSearchService.java b/api/src/main/java/vook/server/api/outbound/search/DemoTermSearchService.java index c75fbe5..86a8d71 100644 --- a/api/src/main/java/vook/server/api/outbound/search/DemoTermSearchService.java +++ b/api/src/main/java/vook/server/api/outbound/search/DemoTermSearchService.java @@ -9,7 +9,6 @@ import com.meilisearch.sdk.model.TypoTolerance; import lombok.AllArgsConstructor; import lombok.Getter; -import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import vook.server.api.model.demo.DemoTerm; import vook.server.api.model.demo.DemoTermSynonym; @@ -19,13 +18,17 @@ import java.util.stream.Collectors; @Service -@RequiredArgsConstructor public class DemoTermSearchService extends MeilisearchService { private static final String DEMO_TERMS_INDEX_UID = "demo-terms"; private final ObjectMapper objectMapper; + public DemoTermSearchService(MeilisearchProperties properties, ObjectMapper objectMapper) { + super(properties); + this.objectMapper = objectMapper; + } + public void clearAll() { clearAll(DEMO_TERMS_INDEX_UID); } diff --git a/api/src/main/java/vook/server/api/outbound/search/MeilisearchProperties.java b/api/src/main/java/vook/server/api/outbound/search/MeilisearchProperties.java new file mode 100644 index 0000000..f54a2cc --- /dev/null +++ b/api/src/main/java/vook/server/api/outbound/search/MeilisearchProperties.java @@ -0,0 +1,15 @@ +package vook.server.api.outbound.search; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Setter +@Getter +@Component +@ConfigurationProperties(prefix = "service.meilisearch") +public class MeilisearchProperties { + private String host; + private String apiKey; +} diff --git a/api/src/main/java/vook/server/api/outbound/search/MeilisearchService.java b/api/src/main/java/vook/server/api/outbound/search/MeilisearchService.java index c23d55d..8354cbb 100644 --- a/api/src/main/java/vook/server/api/outbound/search/MeilisearchService.java +++ b/api/src/main/java/vook/server/api/outbound/search/MeilisearchService.java @@ -5,24 +5,15 @@ import com.meilisearch.sdk.Index; import com.meilisearch.sdk.model.IndexesQuery; import com.meilisearch.sdk.model.Results; -import jakarta.annotation.PostConstruct; -import org.springframework.beans.factory.annotation.Value; import java.util.Arrays; public abstract class MeilisearchService { - @Value("${service.meilisearch.host:}") - protected String host; + protected final Client client; - @Value("${service.meilisearch.apiKey:}") - protected String apiKey; - - protected Client client; - - @PostConstruct - public void postConstruct() { - this.client = new Client(new Config(host, apiKey)); + protected MeilisearchService(MeilisearchProperties properties) { + this.client = new Client(new Config(properties.getHost(), properties.getApiKey())); } protected void clearAll(String uidPrefix) { diff --git a/api/src/test/java/vook/server/api/testhelper/ApiTest.java b/api/src/test/java/vook/server/api/testhelper/ApiTest.java index 6616f0e..689c31d 100644 --- a/api/src/test/java/vook/server/api/testhelper/ApiTest.java +++ b/api/src/test/java/vook/server/api/testhelper/ApiTest.java @@ -5,7 +5,10 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.testcontainers.containers.MariaDBContainer; +import vook.server.api.outbound.search.MeilisearchProperties; import java.util.Map; import java.util.TimeZone; @@ -19,19 +22,34 @@ public abstract class ApiTest { protected TestRestTemplate rest; @ServiceConnection - protected static final MariaDBContainer mariaDBContainer = new MariaDBContainer<>("mariadb:10.11") + protected static final MariaDBContainer mariaDBContainer = new MariaDBContainer<>("mariadb:10.11") .withDatabaseName("example") .withUsername("user") .withPassword("userPw") .withConfigurationOverride("db/conf") .withTmpFs(Map.of("/var/lib/mysql", "rw")); + protected static final MeilisearchContainer meilisearchContainer = new MeilisearchContainer("getmeili/meilisearch:v1.8.0"); + static { mariaDBContainer.start(); + meilisearchContainer.start(); } @BeforeEach void init() { TimeZone.setDefault(TimeZone.getTimeZone(DEFAULT_TIME_ZONE)); } + + @Configuration + public static class TestConfig { + + @Bean + public MeilisearchProperties meilisearchProperties() { + MeilisearchProperties meilisearchProperties = new MeilisearchProperties(); + meilisearchProperties.setHost(meilisearchContainer.getHostUrl()); + meilisearchProperties.setApiKey(meilisearchContainer.getMasterKey()); + return meilisearchProperties; + } + } } diff --git a/api/src/test/java/vook/server/api/testhelper/MeilisearchContainer.java b/api/src/test/java/vook/server/api/testhelper/MeilisearchContainer.java new file mode 100644 index 0000000..a2c36e6 --- /dev/null +++ b/api/src/test/java/vook/server/api/testhelper/MeilisearchContainer.java @@ -0,0 +1,34 @@ +package vook.server.api.testhelper; + +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.utility.DockerImageName; + +public class MeilisearchContainer extends GenericContainer { + + private static final String DEFAULT_IMAGE_NAME = "getmeili/meilisearch:latest"; + + public MeilisearchContainer() { + this(DEFAULT_IMAGE_NAME); + } + + public MeilisearchContainer(String dockerImageName) { + this(DockerImageName.parse(dockerImageName)); + } + + public MeilisearchContainer(DockerImageName dockerImageName) { + super(dockerImageName); + this.addExposedPort(7700); + this.addEnv("MEILI_MASTER_KEY", "aMasterKey"); + } + + public String getMasterKey() { + return this.getEnvMap().get("MEILI_MASTER_KEY"); + } + + /** + * Meilisearch에 접속하기 위한 URL을 반환한다. + */ + public String getHostUrl() { + return "http://" + getHost() + ":" + getMappedPort(7700); + } +} diff --git a/api/src/test/java/vook/server/api/web/routes/demo/DemoWebServiceTest.java b/api/src/test/java/vook/server/api/web/routes/demo/DemoWebServiceTest.java new file mode 100644 index 0000000..adf5009 --- /dev/null +++ b/api/src/test/java/vook/server/api/web/routes/demo/DemoWebServiceTest.java @@ -0,0 +1,62 @@ +package vook.server.api.web.routes.demo; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; +import vook.server.api.app.demo.repo.DemoTermRepository; +import vook.server.api.devhelper.TestTermsLoader; +import vook.server.api.model.demo.DemoTerm; +import vook.server.api.outbound.search.DemoTermSearchService; +import vook.server.api.testhelper.ApiTest; +import vook.server.api.web.routes.demo.reqres.SearchTermRequest; +import vook.server.api.web.routes.demo.reqres.SearchTermResponse; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@Transactional +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class DemoWebServiceTest extends ApiTest { + + @Autowired + private DemoWebService demoWebService; + + @Autowired + private TestTermsLoader testTermsLoader; + @Autowired + private DemoTermRepository demoTermRepository; + @Autowired + private DemoTermSearchService demoTermSearchService; + + @BeforeAll + void beforeAll() { + List terms = testTermsLoader.getTerms("classpath:init/개발.tsv"); + demoTermRepository.saveAll(terms); + demoTermSearchService.init(); + demoTermSearchService.addTerms(terms); + } + + @AfterAll + void afterAll() { + demoTermSearchService.clearAll(); + } + + @Test + void searchTerm() { + SearchTermRequest searchTermRequest = new SearchTermRequest(); + searchTermRequest.setQuery("하이브리드앱"); + searchTermRequest.setWithFormat(false); + searchTermRequest.setHighlightPreTag(""); + searchTermRequest.setHighlightPostTag(""); + + SearchTermResponse searchTermResponse = demoWebService.searchTerm(searchTermRequest); + + assertThat(searchTermResponse).isNotNull(); + assertThat(searchTermResponse.getQuery()).isEqualTo("하이브리드앱"); + assertThat(searchTermResponse.getHits()).isNotEmpty(); + } +} diff --git a/api/src/test/resources/application.yml b/api/src/test/resources/application.yml index 9f47baf..b8191a6 100644 --- a/api/src/test/resources/application.yml +++ b/api/src/test/resources/application.yml @@ -30,9 +30,6 @@ logging: orm: jdbc: TRACE service: - meilisearch: - host: http://localhost:7700 - apiKey: aSampleMasterKey jwt: secret: vmfhaltmskdlstkfkdgodyroqkfwkdba oauth2: