From b7a95be7310d9bf858477d0439ef9b4aac3934dd Mon Sep 17 00:00:00 2001 From: Viktor Lofgren Date: Wed, 1 May 2024 14:27:41 +0200 Subject: [PATCH] (search) Create a small mocking framework for running the search service in isolation. --- .../service/module/DatabaseModule.java | 2 +- .../search-service/build.gradle | 10 ++ .../paperdoll/SearchServicePaperDoll.java | 167 ++++++++++++++++++ 3 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 code/services-application/search-service/test/nu/marginalia/search/paperdoll/SearchServicePaperDoll.java diff --git a/code/common/service/java/nu/marginalia/service/module/DatabaseModule.java b/code/common/service/java/nu/marginalia/service/module/DatabaseModule.java index 15a70e57..659b4e5f 100644 --- a/code/common/service/java/nu/marginalia/service/module/DatabaseModule.java +++ b/code/common/service/java/nu/marginalia/service/module/DatabaseModule.java @@ -12,6 +12,7 @@ import org.mariadb.jdbc.Driver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.sql.DataSource; import java.io.FileInputStream; import java.io.IOException; import java.nio.file.Files; @@ -46,7 +47,6 @@ public class DatabaseModule extends AbstractModule { new Flyway(config.getConfiguration()).migrate(); } } - } private Properties loadDbProperties() { diff --git a/code/services-application/search-service/build.gradle b/code/services-application/search-service/build.gradle index 5fae4e58..f502e66f 100644 --- a/code/services-application/search-service/build.gradle +++ b/code/services-application/search-service/build.gradle @@ -73,5 +73,15 @@ dependencies { testImplementation libs.bundles.junit testImplementation libs.mockito + testImplementation platform('org.testcontainers:testcontainers-bom:1.17.4') + testImplementation 'org.testcontainers:mariadb:1.17.4' + testImplementation 'org.testcontainers:junit-jupiter:1.17.4' + testImplementation project(':code:libraries:test-helpers') } +tasks.register('paperDoll', Test) { + useJUnitPlatform { + includeTags "paperdoll" + } + jvmArgs = [ '-DrunPaperDoll=true', '--enable-preview' ] +} diff --git a/code/services-application/search-service/test/nu/marginalia/search/paperdoll/SearchServicePaperDoll.java b/code/services-application/search-service/test/nu/marginalia/search/paperdoll/SearchServicePaperDoll.java new file mode 100644 index 00000000..8963d39b --- /dev/null +++ b/code/services-application/search-service/test/nu/marginalia/search/paperdoll/SearchServicePaperDoll.java @@ -0,0 +1,167 @@ +package nu.marginalia.search.paperdoll; + +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import lombok.SneakyThrows; +import nu.marginalia.api.searchquery.QueryClient; +import nu.marginalia.api.searchquery.model.query.QueryResponse; +import nu.marginalia.api.searchquery.model.query.SearchQuery; +import nu.marginalia.api.searchquery.model.query.SearchSpecification; +import nu.marginalia.api.searchquery.model.results.DecoratedSearchResultItem; +import nu.marginalia.api.searchquery.model.results.ResultRankingParameters; +import nu.marginalia.api.searchquery.model.results.SearchResultItem; +import nu.marginalia.index.query.limit.QueryLimits; +import nu.marginalia.index.query.limit.QueryStrategy; +import nu.marginalia.index.query.limit.SpecificationLimit; +import nu.marginalia.model.EdgeUrl; +import nu.marginalia.model.crawl.HtmlFeature; +import nu.marginalia.search.SearchModule; +import nu.marginalia.search.SearchService; +import nu.marginalia.service.ServiceId; +import nu.marginalia.service.discovery.ServiceRegistryIf; +import nu.marginalia.service.discovery.property.ServiceEndpoint; +import nu.marginalia.service.module.ServiceConfigurationModule; +import nu.marginalia.test.TestMigrationLoader; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + + +/** This class is a special test class that sets up a search service + * and registers some search results, without actually starting the rest + * of the environment. This is used to test the search service in isolation + * when working on the frontend. + *

+ * It's not actually a test, but it's in the test directory because it's + * using test related classes. + *

+ * When using gradle, run ./gradlew paperDoll --info to run this test, + * the system will wait for you to kill the process to stop the test, + * and the UI is available at port 9999. + */ +@Testcontainers +@Tag("paperdoll") +public class SearchServicePaperDoll extends AbstractModule { + + @Container + static MariaDBContainer mariaDBContainer = new MariaDBContainer<>("mariadb") + .withDatabaseName("WMSA_prod") + .withUsername("wmsa") + .withPassword("wmsa") + .withNetworkAliases("mariadb"); + + private static HikariDataSource dataSource; + + private static List results = new ArrayList<>(); + private static QueryResponse searchResponse; + + @SneakyThrows + void registerSearchResult( + String url, + String title, + String description, + Collection features, + double quality, + double score, + long positions) + { + results.add(new DecoratedSearchResultItem( + new SearchResultItem(url.hashCode(), 2, 3, false), + new EdgeUrl(url), + title, + description, + quality, + "HTML5", + HtmlFeature.encode(features), + null, + url.hashCode(), + 400, + positions, + score, + null) + ); + } + + @BeforeAll + public static void setup() throws URISyntaxException { + if (!Boolean.getBoolean("runPaperDoll")) { + return; + } + + HikariConfig config = new HikariConfig(); + config.setJdbcUrl(mariaDBContainer.getJdbcUrl()); + config.setUsername("wmsa"); + config.setPassword("wmsa"); + + dataSource = new HikariDataSource(config); + + TestMigrationLoader.flywayMigration(dataSource); + + System.setProperty("service-name", "search"); + System.setProperty("search.websiteUrl", "http://localhost:9999/"); + + searchResponse = new QueryResponse( + new SearchSpecification(new SearchQuery(), List.of(), "", "test", + SpecificationLimit.none(), + SpecificationLimit.none(), + SpecificationLimit.none(), + SpecificationLimit.none(), + new QueryLimits(10, 20, 3, 4), + QueryStrategy.AUTO, + ResultRankingParameters.sensibleDefaults() + ), + results, + List.of(), + List.of(), + null + ); + } + + @Test + public void run() { + if (!Boolean.getBoolean("runPaperDoll")) { + return; + } + + var injector = Guice.createInjector( + new ServiceConfigurationModule(ServiceId.Search), + new SearchModule(), + this); + + injector.getInstance(SearchService.class); + + registerSearchResult("https://www.example.com/foo", "Foo", "Lorem ipsum dolor sit amet", Set.of(), 0.5, 0.5, ~0L); + registerSearchResult("https://www.example2.com/bar", "Bar", "Some text goes here", Set.of(), 0.5, 0.5, 1L); + + for (;;); + } + + @SneakyThrows + public void configure() { + var serviceRegistry = Mockito.mock(ServiceRegistryIf.class); + when(serviceRegistry.registerService(any(), any(), any())).thenReturn(new ServiceEndpoint("localhost", 9999)); + + bind(ServiceRegistryIf.class).toInstance(serviceRegistry); + bind(HikariDataSource.class).toInstance(dataSource); + + var qsMock = Mockito.mock(QueryClient.class); + when(qsMock.search(any())).thenReturn(searchResponse); + bind(QueryClient.class).toInstance(qsMock); + } + +}