diff --git a/apps/opik-backend/src/main/java/com/comet/opik/api/FeedbackScoreNames.java b/apps/opik-backend/src/main/java/com/comet/opik/api/FeedbackScoreNames.java new file mode 100644 index 000000000..6213038cf --- /dev/null +++ b/apps/opik-backend/src/main/java/com/comet/opik/api/FeedbackScoreNames.java @@ -0,0 +1,17 @@ +package com.comet.opik.api; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Builder; + +import java.util.List; + +@Builder(toBuilder = true) +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public record FeedbackScoreNames(List scores) { + + public record ScoreName(String name) { + } +} diff --git a/apps/opik-backend/src/main/java/com/comet/opik/api/resources/v1/priv/FeedbackScoreResource.java b/apps/opik-backend/src/main/java/com/comet/opik/api/resources/v1/priv/FeedbackScoreResource.java new file mode 100644 index 000000000..d06a3cc9e --- /dev/null +++ b/apps/opik-backend/src/main/java/com/comet/opik/api/resources/v1/priv/FeedbackScoreResource.java @@ -0,0 +1,71 @@ +package com.comet.opik.api.resources.v1.priv; + +import com.codahale.metrics.annotation.Timed; +import com.comet.opik.api.FeedbackDefinition; +import com.comet.opik.api.FeedbackScoreNames; +import com.comet.opik.domain.FeedbackScoreService; +import com.comet.opik.infrastructure.auth.RequestContext; +import com.fasterxml.jackson.annotation.JsonView; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Inject; +import jakarta.inject.Provider; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.util.UUID; + +import static com.comet.opik.api.FeedbackScoreNames.ScoreName; +import static com.comet.opik.utils.AsyncUtils.setRequestContext; + +@Path("/v1/private/feedback-scores") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@Timed +@Slf4j +@RequiredArgsConstructor(onConstructor_ = @Inject) +@Tag(name = "Feedback-scores", description = "Feedback scores related resources") +public class FeedbackScoreResource { + + private final @NonNull FeedbackScoreService feedbackScoreService; + private final @NonNull Provider requestContext; + + @GET + @Path("/names") + @Operation(operationId = "findFeedbackScoreNames", summary = "Find Feedback Score names", description = "Find Feedback Score names", responses = { + @ApiResponse(responseCode = "200", description = "Feedback Scores resource", content = @Content(array = @ArraySchema(schema = @Schema(implementation = String.class)))) + }) + @JsonView({FeedbackDefinition.View.Public.class}) + public Response findFeedbackScoreNames( + @QueryParam("project_id") UUID projectId, + @QueryParam("with_experiments_only") boolean withExperimentsOnly) { + + String workspaceId = requestContext.get().getWorkspaceId(); + + log.info("Find feedback score names by project_id '{}' and with_experiments_only '{}', on workspaceId '{}'", + projectId, withExperimentsOnly, workspaceId); + FeedbackScoreNames feedbackScoreNames = feedbackScoreService + .getFeedbackScoreNames(projectId, withExperimentsOnly) + .map(names -> names.stream().map(ScoreName::new).toList()) + .map(FeedbackScoreNames::new) + .contextWrite(ctx -> setRequestContext(ctx, requestContext)) + .block(); + log.info("Found feedback score names by project_id '{}' and with_experiments_only '{}', on workspaceId '{}'", + projectId, withExperimentsOnly, workspaceId); + + return Response.ok(feedbackScoreNames).build(); + } + +} diff --git a/apps/opik-backend/src/main/java/com/comet/opik/domain/FeedbackScoreDAO.java b/apps/opik-backend/src/main/java/com/comet/opik/domain/FeedbackScoreDAO.java index c5918fb6e..35b8bc5a8 100644 --- a/apps/opik-backend/src/main/java/com/comet/opik/domain/FeedbackScoreDAO.java +++ b/apps/opik-backend/src/main/java/com/comet/opik/domain/FeedbackScoreDAO.java @@ -66,6 +66,8 @@ Mono scoreEntity(EntityType entityType, UUID entityId, FeedbackScore score Mono deleteByEntityIds(EntityType entityType, Set entityIds); Mono scoreBatchOf(EntityType entityType, List scores); + + Mono> getFeedbackScoreNames(UUID projectId, boolean withExperimentsOnly); } @Singleton @@ -153,6 +155,47 @@ AND entity_id IN ( ; """; + private static final String SELECT_FEEDBACK_SCORE_NAMES = """ + SELECT + distinct name + FROM ( + SELECT + name + FROM feedback_scores + WHERE workspace_id = :workspace_id + + AND project_id = :project_id + + + AND entity_id IN ( + SELECT + trace_id + FROM ( + SELECT + id + FROM experiments + WHERE workspace_id = :workspace_id + ORDER BY id DESC, last_updated_at DESC + LIMIT 1 BY id + ) AS e + INNER JOIN ( + SELECT + experiment_id, + trace_id + FROM experiment_items + WHERE workspace_id = :workspace_id + ORDER BY id DESC, last_updated_at DESC + LIMIT 1 BY id + ) ei ON e.id = ei.experiment_id + ) + + AND entity_type = 'trace' + ORDER BY entity_id DESC, last_updated_at DESC + LIMIT 1 BY entity_id, name + ) AS names + ; + """; + private final @NonNull TransactionTemplateAsync asyncTemplate; @Override @@ -318,6 +361,40 @@ public Mono deleteByEntityIds( }; } + @Override + @WithSpan + public Mono> getFeedbackScoreNames(UUID projectId, boolean withExperimentsOnly) { + return asyncTemplate.nonTransaction(connection -> { + + ST template = new ST(SELECT_FEEDBACK_SCORE_NAMES); + + bindTemplateParam(projectId, withExperimentsOnly, template); + + var statement = connection.createStatement(template.render()); + + bindStatementParam(projectId, statement); + + return makeMonoContextAware(bindWorkspaceIdToMono(statement)) + .flatMapMany(result -> result.map((row, rowMetadata) -> row.get("name", String.class))) + .distinct() + .collect(Collectors.toList()); + }); + } + + private void bindStatementParam(UUID projectId, Statement statement) { + if (projectId != null) { + statement.bind("project_id", projectId); + } + } + + private void bindTemplateParam(UUID projectId, boolean withExperimentsOnly, ST template) { + if (projectId != null) { + template.add("project_id", projectId); + } + + template.add("with_experiments_only", withExperimentsOnly); + } + private Mono cascadeSpanDelete(Set traceIds, Connection connection) { log.info("Deleting feedback scores by span entityId, traceIds count '{}'", traceIds.size()); var statement = connection.createStatement(DELETE_SPANS_CASCADE_FEEDBACK_SCORE) diff --git a/apps/opik-backend/src/main/java/com/comet/opik/domain/FeedbackScoreService.java b/apps/opik-backend/src/main/java/com/comet/opik/domain/FeedbackScoreService.java index 572110027..e67cedfae 100644 --- a/apps/opik-backend/src/main/java/com/comet/opik/domain/FeedbackScoreService.java +++ b/apps/opik-backend/src/main/java/com/comet/opik/domain/FeedbackScoreService.java @@ -44,6 +44,8 @@ public interface FeedbackScoreService { Mono deleteSpanScore(UUID id, String tag); Mono deleteTraceScore(UUID id, String tag); + + Mono> getFeedbackScoreNames(UUID projectId, boolean withExperimentsOnly); } @Slf4j @@ -228,6 +230,11 @@ public Mono deleteTraceScore(UUID id, String name) { return dao.deleteScoreFrom(EntityType.TRACE, id, name); } + @Override + public Mono> getFeedbackScoreNames(UUID projectId, boolean withExperimentsOnly) { + return dao.getFeedbackScoreNames(projectId, withExperimentsOnly); + } + private Mono failWithNotFound(String errorMessage) { log.info(errorMessage); return Mono.error(new NotFoundException(Response.status(404) diff --git a/apps/opik-backend/src/test/java/com/comet/opik/api/resources/utils/resources/ExperimentResourceClient.java b/apps/opik-backend/src/test/java/com/comet/opik/api/resources/utils/resources/ExperimentResourceClient.java new file mode 100644 index 000000000..6b0f46fe8 --- /dev/null +++ b/apps/opik-backend/src/test/java/com/comet/opik/api/resources/utils/resources/ExperimentResourceClient.java @@ -0,0 +1,56 @@ +package com.comet.opik.api.resources.utils.resources; + +import com.comet.opik.api.Experiment; +import com.comet.opik.api.ExperimentItem; +import com.comet.opik.api.ExperimentItemsBatch; +import com.comet.opik.api.resources.utils.TestUtils; +import com.comet.opik.infrastructure.auth.RequestContext; +import jakarta.ws.rs.client.Entity; +import lombok.RequiredArgsConstructor; +import org.apache.http.HttpStatus; +import org.testcontainers.shaded.com.google.common.net.HttpHeaders; +import ru.vyarus.dropwizard.guice.test.ClientSupport; +import uk.co.jemos.podam.api.PodamFactory; + +import java.util.Set; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + +@RequiredArgsConstructor +public class ExperimentResourceClient { + + private static final String RESOURCE_PATH = "%s/v1/private/experiments"; + + private final ClientSupport client; + private final String baseURI; + private final PodamFactory podamFactory; + + public UUID createExperiment(String apiKey, String workspaceName) { + var experiment = podamFactory.manufacturePojo(Experiment.class).toBuilder() + .promptVersion(null) + .build(); + + try (var response = client.target(RESOURCE_PATH.formatted(baseURI)) + .request() + .header(HttpHeaders.AUTHORIZATION, apiKey) + .header(RequestContext.WORKSPACE_HEADER, workspaceName) + .post(Entity.json(experiment))) { + + assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_CREATED); + return TestUtils.getIdFromLocation(response.getLocation()); + } + } + + public void createExperimentItem(Set experimentItems, String apiKey, String workspaceName) { + try (var response = client.target(RESOURCE_PATH.formatted(baseURI)) + .path("items") + .request() + .header(HttpHeaders.AUTHORIZATION, apiKey) + .header(RequestContext.WORKSPACE_HEADER, workspaceName) + .post(Entity.json(new ExperimentItemsBatch(experimentItems)))) { + + assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_NO_CONTENT); + } + } +} diff --git a/apps/opik-backend/src/test/java/com/comet/opik/api/resources/utils/resources/ProjectResourceClient.java b/apps/opik-backend/src/test/java/com/comet/opik/api/resources/utils/resources/ProjectResourceClient.java new file mode 100644 index 000000000..866126ffd --- /dev/null +++ b/apps/opik-backend/src/test/java/com/comet/opik/api/resources/utils/resources/ProjectResourceClient.java @@ -0,0 +1,58 @@ +package com.comet.opik.api.resources.utils.resources; + +import com.comet.opik.api.Project; +import com.comet.opik.api.resources.utils.TestUtils; +import com.comet.opik.infrastructure.auth.RequestContext; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.core.HttpHeaders; +import lombok.RequiredArgsConstructor; +import org.apache.hc.core5.http.HttpStatus; +import ru.vyarus.dropwizard.guice.test.ClientSupport; +import uk.co.jemos.podam.api.PodamFactory; + +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + +@RequiredArgsConstructor +public class ProjectResourceClient { + + private static final String RESOURCE_PATH = "%s/v1/private/projects"; + + private final ClientSupport client; + private final String baseURI; + private final PodamFactory podamFactory; + + public UUID createProject(String projectName, String apiKey, String workspaceName) { + + var project = podamFactory.manufacturePojo(Project.class).toBuilder() + .name(projectName) + .build(); + + try (var response = client.target(RESOURCE_PATH.formatted(baseURI)) + .request() + .header(HttpHeaders.AUTHORIZATION, apiKey) + .header(RequestContext.WORKSPACE_HEADER, workspaceName) + .post(Entity.json(project))) { + + assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_CREATED); + + return TestUtils.getIdFromLocation(response.getLocation()); + } + } + + public Project getProject(UUID projectId, String apiKey, String workspaceName) { + + try (var response = client.target(RESOURCE_PATH.formatted(baseURI) + "/" + projectId) + .request() + .header(HttpHeaders.AUTHORIZATION, apiKey) + .header(RequestContext.WORKSPACE_HEADER, workspaceName) + .get()) { + + assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_OK); + + return response.readEntity(Project.class); + } + } + +} diff --git a/apps/opik-backend/src/test/java/com/comet/opik/api/resources/utils/resources/TraceResourceClient.java b/apps/opik-backend/src/test/java/com/comet/opik/api/resources/utils/resources/TraceResourceClient.java new file mode 100644 index 000000000..2b7b834cc --- /dev/null +++ b/apps/opik-backend/src/test/java/com/comet/opik/api/resources/utils/resources/TraceResourceClient.java @@ -0,0 +1,49 @@ +package com.comet.opik.api.resources.utils.resources; + +import com.comet.opik.api.FeedbackScoreBatch; +import com.comet.opik.api.FeedbackScoreBatchItem; +import com.comet.opik.api.Trace; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.core.HttpHeaders; +import lombok.RequiredArgsConstructor; +import org.apache.http.HttpStatus; +import ru.vyarus.dropwizard.guice.test.ClientSupport; + +import java.util.List; + +import static com.comet.opik.infrastructure.auth.RequestContext.WORKSPACE_HEADER; +import static org.assertj.core.api.Assertions.assertThat; + +@RequiredArgsConstructor +public class TraceResourceClient { + + private static final String RESOURCE_PATH = "%s/v1/private/traces"; + + private final ClientSupport client; + private final String baseURI; + + public void createTrace(Trace trace, String apiKey, String workspaceName) { + try (var response = client.target(RESOURCE_PATH.formatted(baseURI)) + .request() + .header(HttpHeaders.AUTHORIZATION, apiKey) + .header(WORKSPACE_HEADER, workspaceName) + .post(Entity.json(trace))) { + + assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_CREATED); + } + } + + public void feedbackScore(List score, String apiKey, String workspaceName) { + + try (var response = client.target(RESOURCE_PATH.formatted(baseURI)) + .path("feedback-scores") + .request() + .header(HttpHeaders.AUTHORIZATION, apiKey) + .header(WORKSPACE_HEADER, workspaceName) + .put(Entity.json(new FeedbackScoreBatch(score)))) { + + assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_NO_CONTENT); + } + } + +} diff --git a/apps/opik-backend/src/test/java/com/comet/opik/api/resources/v1/internal/UsageResourceTest.java b/apps/opik-backend/src/test/java/com/comet/opik/api/resources/v1/internal/UsageResourceTest.java index 73dba207f..77405234e 100644 --- a/apps/opik-backend/src/test/java/com/comet/opik/api/resources/v1/internal/UsageResourceTest.java +++ b/apps/opik-backend/src/test/java/com/comet/opik/api/resources/v1/internal/UsageResourceTest.java @@ -30,7 +30,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; import org.testcontainers.clickhouse.ClickHouseContainer; import org.testcontainers.containers.MySQLContainer; -import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.lifecycle.Startables; import reactor.core.publisher.Mono; import ru.vyarus.dropwizard.guice.test.ClientSupport; import ru.vyarus.dropwizard.guice.test.jupiter.ext.TestDropwizardAppExtension; @@ -52,7 +52,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.testcontainers.shaded.org.awaitility.Awaitility.await; -@Testcontainers(parallel = true) @DisplayName("Usage Resource Test") @TestInstance(TestInstance.Lifecycle.PER_CLASS) @Slf4j @@ -77,9 +76,7 @@ public class UsageResourceTest { private static final WireMockUtils.WireMockRuntime wireMock; static { - MYSQL_CONTAINER.start(); - CLICK_HOUSE_CONTAINER.start(); - REDIS.start(); + Startables.deepStart(REDIS, MYSQL_CONTAINER, CLICK_HOUSE_CONTAINER).join(); wireMock = WireMockUtils.startWireMock(); diff --git a/apps/opik-backend/src/test/java/com/comet/opik/api/resources/v1/priv/FeedbackDefinitionResourceTest.java b/apps/opik-backend/src/test/java/com/comet/opik/api/resources/v1/priv/FeedbackDefinitionResourceTest.java index f43234f1c..14d9a80b4 100644 --- a/apps/opik-backend/src/test/java/com/comet/opik/api/resources/v1/priv/FeedbackDefinitionResourceTest.java +++ b/apps/opik-backend/src/test/java/com/comet/opik/api/resources/v1/priv/FeedbackDefinitionResourceTest.java @@ -32,7 +32,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.testcontainers.containers.MySQLContainer; -import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.lifecycle.Startables; import ru.vyarus.dropwizard.guice.test.ClientSupport; import ru.vyarus.dropwizard.guice.test.jupiter.ext.TestDropwizardAppExtension; import uk.co.jemos.podam.api.PodamFactory; @@ -63,7 +63,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.params.provider.Arguments.arguments; -@Testcontainers(parallel = true) @TestInstance(TestInstance.Lifecycle.PER_CLASS) @DisplayName("Feedback Resource Test") class FeedbackDefinitionResourceTest { @@ -88,8 +87,7 @@ class FeedbackDefinitionResourceTest { private static final WireMockUtils.WireMockRuntime wireMock; static { - MYSQL.start(); - REDIS.start(); + Startables.deepStart(REDIS, MYSQL).join(); wireMock = WireMockUtils.startWireMock(); diff --git a/apps/opik-backend/src/test/java/com/comet/opik/api/resources/v1/priv/FeedbackScoreResourceTest.java b/apps/opik-backend/src/test/java/com/comet/opik/api/resources/v1/priv/FeedbackScoreResourceTest.java new file mode 100644 index 000000000..25002b558 --- /dev/null +++ b/apps/opik-backend/src/test/java/com/comet/opik/api/resources/v1/priv/FeedbackScoreResourceTest.java @@ -0,0 +1,254 @@ +package com.comet.opik.api.resources.v1.priv; + +import com.comet.opik.api.ExperimentItem; +import com.comet.opik.api.FeedbackScoreBatchItem; +import com.comet.opik.api.FeedbackScoreNames; +import com.comet.opik.api.Project; +import com.comet.opik.api.Trace; +import com.comet.opik.api.resources.utils.AuthTestUtils; +import com.comet.opik.api.resources.utils.ClickHouseContainerUtils; +import com.comet.opik.api.resources.utils.ClientSupportUtils; +import com.comet.opik.api.resources.utils.MigrationUtils; +import com.comet.opik.api.resources.utils.MySQLContainerUtils; +import com.comet.opik.api.resources.utils.RedisContainerUtils; +import com.comet.opik.api.resources.utils.TestDropwizardAppExtensionUtils; +import com.comet.opik.api.resources.utils.WireMockUtils; +import com.comet.opik.api.resources.utils.resources.ExperimentResourceClient; +import com.comet.opik.api.resources.utils.resources.ProjectResourceClient; +import com.comet.opik.api.resources.utils.resources.TraceResourceClient; +import com.comet.opik.podam.PodamFactoryUtils; +import com.redis.testcontainers.RedisContainer; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.HttpHeaders; +import org.apache.http.HttpStatus; +import org.jdbi.v3.core.Jdbi; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.testcontainers.clickhouse.ClickHouseContainer; +import org.testcontainers.containers.MySQLContainer; +import org.testcontainers.lifecycle.Startables; +import ru.vyarus.dropwizard.guice.test.ClientSupport; +import ru.vyarus.dropwizard.guice.test.jupiter.ext.TestDropwizardAppExtension; +import uk.co.jemos.podam.api.PodamFactory; + +import java.sql.SQLException; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import static com.comet.opik.api.resources.utils.ClickHouseContainerUtils.DATABASE_NAME; +import static com.comet.opik.api.resources.utils.MigrationUtils.CLICKHOUSE_CHANGELOG_FILE; +import static com.comet.opik.api.resources.utils.TestDropwizardAppExtensionUtils.newTestDropwizardAppExtension; +import static com.comet.opik.infrastructure.auth.RequestContext.WORKSPACE_HEADER; +import static org.assertj.core.api.Assertions.assertThat; + +@DisplayName("Feedback Scores Resource") +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class FeedbackScoreResourceTest { + + private static final RedisContainer REDIS = RedisContainerUtils.newRedisContainer(); + private static final MySQLContainer MY_SQL_CONTAINER = MySQLContainerUtils.newMySQLContainer(); + private static final ClickHouseContainer CLICK_HOUSE_CONTAINER = ClickHouseContainerUtils.newClickHouseContainer(); + + @RegisterExtension + private static final TestDropwizardAppExtension app; + + private static final WireMockUtils.WireMockRuntime wireMock; + + private static final TestDropwizardAppExtensionUtils.AppContextConfig contextConfig; + private static final String RESOURCE_PATH = "%s/v1/private/feedback-scores"; + + static { + Startables.deepStart(REDIS, MY_SQL_CONTAINER, CLICK_HOUSE_CONTAINER).join(); + + wireMock = WireMockUtils.startWireMock(); + + var databaseAnalyticsFactory = ClickHouseContainerUtils.newDatabaseAnalyticsFactory( + CLICK_HOUSE_CONTAINER, DATABASE_NAME); + + contextConfig = TestDropwizardAppExtensionUtils.AppContextConfig.builder() + .jdbcUrl(MY_SQL_CONTAINER.getJdbcUrl()) + .databaseAnalyticsFactory(databaseAnalyticsFactory) + .runtimeInfo(wireMock.runtimeInfo()) + .redisUrl(REDIS.getRedisURI()) + .cacheTtlInSeconds(null) + .build(); + + app = newTestDropwizardAppExtension(contextConfig); + } + private final PodamFactory podamFactory = PodamFactoryUtils.newPodamFactory(); + + private String baseURI; + private ClientSupport client; + private ProjectResourceClient projectResourceClient; + private ExperimentResourceClient experimentResourceClient; + private TraceResourceClient traceResourceClient; + + @BeforeAll + void beforeAll(ClientSupport client, Jdbi jdbi) throws SQLException { + MigrationUtils.runDbMigration(jdbi, MySQLContainerUtils.migrationParameters()); + + try (var connection = CLICK_HOUSE_CONTAINER.createConnection("")) { + MigrationUtils.runDbMigration(connection, CLICKHOUSE_CHANGELOG_FILE, + ClickHouseContainerUtils.migrationParameters()); + } + + baseURI = "http://localhost:%d".formatted(client.getPort()); + this.client = client; + + ClientSupportUtils.config(client); + this.projectResourceClient = new ProjectResourceClient(client, baseURI, podamFactory); + this.experimentResourceClient = new ExperimentResourceClient(client, baseURI, podamFactory); + this.traceResourceClient = new TraceResourceClient(client, baseURI); + } + + private static void mockTargetWorkspace(String apiKey, String workspaceName, String workspaceId) { + AuthTestUtils.mockTargetWorkspace(wireMock.server(), apiKey, workspaceName, workspaceId, workspaceId); + } + + @Nested + @DisplayName("Get Feedback Score names") + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + class GetFeedbackScoreNames { + + @ParameterizedTest + @CsvSource({ + "true, false", + "false, true", + "true, true", + "false, false" + }) + @DisplayName("when get feedback score names, then return feedback score names") + void getFeedbackScoreNames__whenGetFeedbackScoreNames__thenReturnFeedbackScoreNames( + boolean userProjectId, + boolean withExperimentsOnly) { + // given + var apiKey = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + + mockTargetWorkspace(apiKey, workspaceName, workspaceId); + + // when + String projectName = UUID.randomUUID().toString(); + + UUID projectId = projectResourceClient.createProject(projectName, apiKey, workspaceName); + Project project = projectResourceClient.getProject(projectId, apiKey, workspaceName); + + List names = PodamFactoryUtils.manufacturePojoList(podamFactory, String.class); + List otherNames = PodamFactoryUtils.manufacturePojoList(podamFactory, String.class); + + // Create multiple values feedback scores + List multipleValuesFeedbackScores = names.subList(0, names.size() - 1); + + List> multipleValuesFeedbackScoreList = createMultiValueScores( + multipleValuesFeedbackScores, project, apiKey, workspaceName); + + List> singleValueScores = createMultiValueScores(List.of(names.getLast()), + project, apiKey, workspaceName); + + createExperimentsItems(apiKey, workspaceName, multipleValuesFeedbackScoreList, singleValueScores); + + // Create unexpected feedback scores + var unexpectedProject = podamFactory.manufacturePojo(Project.class); + + List> unexpectedScores = createMultiValueScores(otherNames, unexpectedProject, + apiKey, workspaceName); + + if (!withExperimentsOnly) { + createExperimentsItems(apiKey, workspaceName, unexpectedScores, List.of()); + } + + fetchAndAssertResponse(userProjectId, withExperimentsOnly, names, otherNames, projectId, apiKey, + workspaceName); + } + } + + private void fetchAndAssertResponse(boolean userProjectId, boolean withExperimentsOnly, List names, + List otherNames, UUID projectId, String apiKey, String workspaceName) { + WebTarget webTarget = client.target(RESOURCE_PATH.formatted(baseURI)).path("/names"); + + if (userProjectId) { + webTarget = webTarget.queryParam("project_id", projectId); + } + + if (withExperimentsOnly) { + webTarget = webTarget.queryParam("with_experiments_only", withExperimentsOnly); + } + + List expectedNames = (withExperimentsOnly || userProjectId) + ? names + : Stream.of(names, otherNames).flatMap(List::stream).toList(); + + try (var actualResponse = webTarget + .request() + .header(HttpHeaders.AUTHORIZATION, apiKey) + .header(WORKSPACE_HEADER, workspaceName) + .get()) { + + // then + assertThat(actualResponse.getStatusInfo().getStatusCode()).isEqualTo(HttpStatus.SC_OK); + var actualEntity = actualResponse.readEntity(FeedbackScoreNames.class); + + assertThat(actualEntity.scores()).hasSize(expectedNames.size()); + assertThat(actualEntity + .scores() + .stream() + .map(FeedbackScoreNames.ScoreName::name) + .toList()).containsExactlyInAnyOrderElementsOf(expectedNames); + } + } + + private List> createMultiValueScores(List multipleValuesFeedbackScores, + Project project, String apiKey, String workspaceName) { + return IntStream.range(0, multipleValuesFeedbackScores.size()) + .mapToObj(i -> { + + Trace trace = podamFactory.manufacturePojo(Trace.class).toBuilder() + .name(project.name()) + .build(); + + traceResourceClient.createTrace(trace, apiKey, workspaceName); + + List scores = multipleValuesFeedbackScores.stream() + .map(name -> podamFactory.manufacturePojo(FeedbackScoreBatchItem.class).toBuilder() + .name(name) + .projectName(project.name()) + .id(trace.id()) + .build()) + .toList(); + + traceResourceClient.feedbackScore(scores, apiKey, workspaceName); + + return scores; + }).toList(); + } + + private void createExperimentsItems(String apiKey, String workspaceName, + List> multipleValuesFeedbackScoreList, + List> singleValueScores) { + + UUID experimentId = experimentResourceClient.createExperiment(apiKey, workspaceName); + + Stream.of(multipleValuesFeedbackScoreList, singleValueScores) + .flatMap(List::stream) + .flatMap(List::stream) + .map(FeedbackScoreBatchItem::id) + .distinct() + .forEach(traceId -> { + var experimentItem = podamFactory.manufacturePojo(ExperimentItem.class).toBuilder() + .traceId(traceId) + .experimentId(experimentId) + .build(); + + experimentResourceClient.createExperimentItem(Set.of(experimentItem), apiKey, workspaceName); + }); + } +} \ No newline at end of file diff --git a/apps/opik-backend/src/test/java/com/comet/opik/api/resources/v1/priv/SpansResourceTest.java b/apps/opik-backend/src/test/java/com/comet/opik/api/resources/v1/priv/SpansResourceTest.java index 622e524e0..40c6d39ff 100644 --- a/apps/opik-backend/src/test/java/com/comet/opik/api/resources/v1/priv/SpansResourceTest.java +++ b/apps/opik-backend/src/test/java/com/comet/opik/api/resources/v1/priv/SpansResourceTest.java @@ -57,7 +57,7 @@ import org.junit.jupiter.params.provider.MethodSource; import org.testcontainers.clickhouse.ClickHouseContainer; import org.testcontainers.containers.MySQLContainer; -import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.lifecycle.Startables; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import ru.vyarus.dropwizard.guice.test.ClientSupport; @@ -98,7 +98,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.params.provider.Arguments.arguments; -@Testcontainers(parallel = true) @TestInstance(TestInstance.Lifecycle.PER_CLASS) class SpansResourceTest { @@ -125,9 +124,7 @@ class SpansResourceTest { public static final String TEST_WORKSPACE = UUID.randomUUID().toString(); static { - MY_SQL_CONTAINER.start(); - CLICK_HOUSE_CONTAINER.start(); - REDIS.start(); + Startables.deepStart(REDIS, MY_SQL_CONTAINER, CLICK_HOUSE_CONTAINER).join(); wireMock = WireMockUtils.startWireMock(); @@ -3099,7 +3096,7 @@ private void getAndAssertPage( assertThat(actualPage.size()).isEqualTo(expectedSpans.size()); assertThat(actualPage.total()).isEqualTo(expectedTotal); - assertThat(actualSpans.size()).isEqualTo(expectedSpans.size()); + assertThat(actualSpans).hasSize(expectedSpans.size()); assertThat(actualSpans) .usingRecursiveFieldByFieldElementComparatorIgnoringFields(IGNORED_FIELDS) .containsExactlyElementsOf(expectedSpans); @@ -3442,8 +3439,8 @@ void batch__whenCreateSpansUsageWithNullValue__thenReturnNoContent() { body.get("spans").forEach(span -> { var usageNode = span.get("usage"); - if (usageNode instanceof ObjectNode) { - ((ObjectNode) usageNode).set("secondKey", NullNode.getInstance()); + if (usageNode instanceof ObjectNode usageObject) { + usageObject.set("secondKey", NullNode.getInstance()); } }); diff --git a/apps/opik-backend/src/test/java/com/comet/opik/api/resources/v1/priv/TracesResourceTest.java b/apps/opik-backend/src/test/java/com/comet/opik/api/resources/v1/priv/TracesResourceTest.java index e382f95b8..18e55f23a 100644 --- a/apps/opik-backend/src/test/java/com/comet/opik/api/resources/v1/priv/TracesResourceTest.java +++ b/apps/opik-backend/src/test/java/com/comet/opik/api/resources/v1/priv/TracesResourceTest.java @@ -57,7 +57,7 @@ import org.junit.jupiter.params.provider.MethodSource; import org.testcontainers.clickhouse.ClickHouseContainer; import org.testcontainers.containers.MySQLContainer; -import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.lifecycle.Startables; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import ru.vyarus.dropwizard.guice.test.ClientSupport; @@ -97,7 +97,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.params.provider.Arguments.arguments; -@Testcontainers(parallel = true) @DisplayName("Traces Resource Test") @TestInstance(TestInstance.Lifecycle.PER_CLASS) class TracesResourceTest { @@ -128,9 +127,7 @@ class TracesResourceTest { private static final WireMockUtils.WireMockRuntime wireMock; static { - MYSQL_CONTAINER.start(); - CLICK_HOUSE_CONTAINER.start(); - REDIS.start(); + Startables.deepStart(REDIS, MYSQL_CONTAINER, CLICK_HOUSE_CONTAINER).join(); wireMock = WireMockUtils.startWireMock(); diff --git a/apps/opik-backend/src/test/java/com/comet/opik/infrastructure/HealthCheckIntegrationTest.java b/apps/opik-backend/src/test/java/com/comet/opik/infrastructure/HealthCheckIntegrationTest.java index 3b34889e3..0a44b0a3d 100644 --- a/apps/opik-backend/src/test/java/com/comet/opik/infrastructure/HealthCheckIntegrationTest.java +++ b/apps/opik-backend/src/test/java/com/comet/opik/infrastructure/HealthCheckIntegrationTest.java @@ -13,7 +13,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; import org.testcontainers.clickhouse.ClickHouseContainer; import org.testcontainers.containers.MySQLContainer; -import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.lifecycle.Startables; import ru.vyarus.dropwizard.guice.test.ClientSupport; import ru.vyarus.dropwizard.guice.test.jupiter.ext.TestDropwizardAppExtension; @@ -22,7 +22,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; -@Testcontainers(parallel = true) @TestInstance(TestInstance.Lifecycle.PER_CLASS) class HealthCheckIntegrationTest { @@ -42,9 +41,7 @@ record HealthCheckResponse(String name, boolean healthy, boolean critical, Strin } static { - MYSQL.start(); - CLICKHOUSE.start(); - REDIS.start(); + Startables.deepStart(MYSQL, CLICKHOUSE, REDIS).join(); var databaseAnalyticsFactory = ClickHouseContainerUtils.newDatabaseAnalyticsFactory(CLICKHOUSE, ClickHouseContainerUtils.DATABASE_NAME); diff --git a/apps/opik-backend/src/test/java/com/comet/opik/infrastructure/auth/AuthModuleCache2E2Test.java b/apps/opik-backend/src/test/java/com/comet/opik/infrastructure/auth/AuthModuleCache2E2Test.java index 38cefcf32..e78098848 100644 --- a/apps/opik-backend/src/test/java/com/comet/opik/infrastructure/auth/AuthModuleCache2E2Test.java +++ b/apps/opik-backend/src/test/java/com/comet/opik/infrastructure/auth/AuthModuleCache2E2Test.java @@ -21,7 +21,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; import org.testcontainers.clickhouse.ClickHouseContainer; import org.testcontainers.containers.MySQLContainer; -import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.lifecycle.Startables; import reactor.core.publisher.Mono; import ru.vyarus.dropwizard.guice.test.ClientSupport; import ru.vyarus.dropwizard.guice.test.jupiter.ext.TestDropwizardAppExtension; @@ -33,7 +33,6 @@ import static com.comet.opik.infrastructure.auth.RequestContext.WORKSPACE_HEADER; import static org.assertj.core.api.Assertions.assertThat; -@Testcontainers(parallel = true) @TestInstance(TestInstance.Lifecycle.PER_CLASS) class AuthModuleCache2E2Test { @@ -60,9 +59,7 @@ class AuthModuleCache2E2Test { private static final String TEST_WORKSPACE = UUID.randomUUID().toString(); static { - MYSQL.start(); - CLICKHOUSE.start(); - REDIS.start(); + Startables.deepStart(REDIS, MYSQL, CLICKHOUSE).join(); wireMock = WireMockUtils.startWireMock(); diff --git a/apps/opik-backend/src/test/java/com/comet/opik/infrastructure/auth/AuthModuleNoAuthIntegrationTest.java b/apps/opik-backend/src/test/java/com/comet/opik/infrastructure/auth/AuthModuleNoAuthIntegrationTest.java index 13a3c19df..4bf1b0bbd 100644 --- a/apps/opik-backend/src/test/java/com/comet/opik/infrastructure/auth/AuthModuleNoAuthIntegrationTest.java +++ b/apps/opik-backend/src/test/java/com/comet/opik/infrastructure/auth/AuthModuleNoAuthIntegrationTest.java @@ -16,7 +16,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; import org.testcontainers.clickhouse.ClickHouseContainer; import org.testcontainers.containers.MySQLContainer; -import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.lifecycle.Startables; import ru.vyarus.dropwizard.guice.test.ClientSupport; import ru.vyarus.dropwizard.guice.test.jupiter.ext.TestDropwizardAppExtension; @@ -28,7 +28,6 @@ import static com.comet.opik.infrastructure.auth.RequestContext.WORKSPACE_HEADER; import static org.assertj.core.api.Assertions.assertThat; -@Testcontainers(parallel = true) @TestInstance(TestInstance.Lifecycle.PER_CLASS) class AuthModuleNoAuthIntegrationTest { @@ -44,9 +43,7 @@ class AuthModuleNoAuthIntegrationTest { private static final TestDropwizardAppExtension app; static { - MYSQL.start(); - CLICKHOUSE.start(); - REDIS.start(); + Startables.deepStart(MYSQL, CLICKHOUSE, REDIS).join(); var databaseAnalyticsFactory = ClickHouseContainerUtils.newDatabaseAnalyticsFactory(CLICKHOUSE, ClickHouseContainerUtils.DATABASE_NAME); diff --git a/apps/opik-backend/src/test/java/com/comet/opik/infrastructure/aws/rds/MysqlRdsIamE2eTest.java b/apps/opik-backend/src/test/java/com/comet/opik/infrastructure/aws/rds/MysqlRdsIamE2eTest.java index efa023d52..41342f966 100644 --- a/apps/opik-backend/src/test/java/com/comet/opik/infrastructure/aws/rds/MysqlRdsIamE2eTest.java +++ b/apps/opik-backend/src/test/java/com/comet/opik/infrastructure/aws/rds/MysqlRdsIamE2eTest.java @@ -18,7 +18,7 @@ import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.extension.RegisterExtension; import org.testcontainers.clickhouse.ClickHouseContainer; -import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.lifecycle.Startables; import ru.vyarus.dropwizard.guice.test.ClientSupport; import ru.vyarus.dropwizard.guice.test.jupiter.ext.TestDropwizardAppExtension; @@ -29,7 +29,6 @@ import static org.assertj.core.api.Assertions.assertThat; @Disabled -@Testcontainers(parallel = true) @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class MysqlRdsIamE2eTest { @@ -55,8 +54,7 @@ public class MysqlRdsIamE2eTest { private static final String TEST_WORKSPACE = "default"; static { - CLICKHOUSE.start(); - REDIS.start(); + Startables.deepStart(CLICKHOUSE, REDIS).join(); var databaseAnalyticsFactory = ClickHouseContainerUtils.newDatabaseAnalyticsFactory(CLICKHOUSE, ClickHouseContainerUtils.DATABASE_NAME); diff --git a/apps/opik-backend/src/test/java/com/comet/opik/infrastructure/bi/OpikGuiceyLifecycleEventListenerTest.java b/apps/opik-backend/src/test/java/com/comet/opik/infrastructure/bi/OpikGuiceyLifecycleEventListenerTest.java index aea69da61..e78e91048 100644 --- a/apps/opik-backend/src/test/java/com/comet/opik/infrastructure/bi/OpikGuiceyLifecycleEventListenerTest.java +++ b/apps/opik-backend/src/test/java/com/comet/opik/infrastructure/bi/OpikGuiceyLifecycleEventListenerTest.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; import org.testcontainers.clickhouse.ClickHouseContainer; import org.testcontainers.containers.MySQLContainer; +import org.testcontainers.lifecycle.Startables; import ru.vyarus.dropwizard.guice.module.lifecycle.GuiceyLifecycle; import ru.vyarus.dropwizard.guice.test.jupiter.ext.TestDropwizardAppExtension; @@ -71,9 +72,7 @@ class FirstStartupTest { private static final WireMockUtils.WireMockRuntime wireMock; static { - MYSQL_CONTAINER.start(); - CLICK_HOUSE_CONTAINER.start(); - REDIS.start(); + Startables.deepStart(MYSQL_CONTAINER, CLICK_HOUSE_CONTAINER, REDIS).join(); wireMock = WireMockUtils.startWireMock(); @@ -134,9 +133,7 @@ class SecondStartupTest { private static final WireMockUtils.WireMockRuntime wireMock; static { - MYSQL_CONTAINER.start(); - CLICK_HOUSE_CONTAINER.start(); - REDIS.start(); + Startables.deepStart(MYSQL_CONTAINER, CLICK_HOUSE_CONTAINER, REDIS).join(); wireMock = WireMockUtils.startWireMock(); diff --git a/apps/opik-backend/src/test/java/com/comet/opik/infrastructure/health/IsAliveE2ETest.java b/apps/opik-backend/src/test/java/com/comet/opik/infrastructure/health/IsAliveE2ETest.java index 0c2b291ff..6eafb6076 100644 --- a/apps/opik-backend/src/test/java/com/comet/opik/infrastructure/health/IsAliveE2ETest.java +++ b/apps/opik-backend/src/test/java/com/comet/opik/infrastructure/health/IsAliveE2ETest.java @@ -14,13 +14,12 @@ import org.junit.jupiter.api.extension.RegisterExtension; import org.testcontainers.clickhouse.ClickHouseContainer; import org.testcontainers.containers.MySQLContainer; -import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.lifecycle.Startables; import ru.vyarus.dropwizard.guice.test.ClientSupport; import ru.vyarus.dropwizard.guice.test.jupiter.ext.TestDropwizardAppExtension; import static com.comet.opik.api.resources.utils.ClickHouseContainerUtils.DATABASE_NAME; -@Testcontainers(parallel = true) @TestInstance(TestInstance.Lifecycle.PER_CLASS) @DisplayName("Is Alive Resource Test") class IsAliveE2ETest { @@ -37,9 +36,7 @@ class IsAliveE2ETest { private static final TestDropwizardAppExtension app; static { - MYSQL.start(); - CLICKHOUSE.start(); - REDIS.start(); + Startables.deepStart(MYSQL, CLICKHOUSE, REDIS).join(); var databaseAnalyticsFactory = ClickHouseContainerUtils.newDatabaseAnalyticsFactory( CLICKHOUSE, DATABASE_NAME); diff --git a/apps/opik-backend/src/test/java/com/comet/opik/infrastructure/http/cors/CorsDisabledE2ETest.java b/apps/opik-backend/src/test/java/com/comet/opik/infrastructure/http/cors/CorsDisabledE2ETest.java index c46f415ac..ffe65bc29 100644 --- a/apps/opik-backend/src/test/java/com/comet/opik/infrastructure/http/cors/CorsDisabledE2ETest.java +++ b/apps/opik-backend/src/test/java/com/comet/opik/infrastructure/http/cors/CorsDisabledE2ETest.java @@ -14,14 +14,13 @@ import org.junit.jupiter.api.extension.RegisterExtension; import org.testcontainers.clickhouse.ClickHouseContainer; import org.testcontainers.containers.MySQLContainer; -import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.lifecycle.Startables; import ru.vyarus.dropwizard.guice.test.ClientSupport; import ru.vyarus.dropwizard.guice.test.jupiter.ext.TestDropwizardAppExtension; import static com.comet.opik.api.resources.utils.ClickHouseContainerUtils.DATABASE_NAME; import static org.assertj.core.api.Assertions.assertThat; -@Testcontainers(parallel = true) @TestInstance(TestInstance.Lifecycle.PER_CLASS) @DisplayName("CORS headers logic test when CORS config is disabled") class CorsDisabledE2ETest { @@ -35,9 +34,7 @@ class CorsDisabledE2ETest { private static final TestDropwizardAppExtension app; static { - MYSQL.start(); - CLICKHOUSE.start(); - REDIS.start(); + Startables.deepStart(MYSQL, CLICKHOUSE, REDIS).join(); var databaseAnalyticsFactory = ClickHouseContainerUtils.newDatabaseAnalyticsFactory( CLICKHOUSE, DATABASE_NAME); diff --git a/apps/opik-backend/src/test/java/com/comet/opik/infrastructure/http/cors/CorsEnabledE2ETest.java b/apps/opik-backend/src/test/java/com/comet/opik/infrastructure/http/cors/CorsEnabledE2ETest.java index bbf2b967b..d350dd749 100644 --- a/apps/opik-backend/src/test/java/com/comet/opik/infrastructure/http/cors/CorsEnabledE2ETest.java +++ b/apps/opik-backend/src/test/java/com/comet/opik/infrastructure/http/cors/CorsEnabledE2ETest.java @@ -16,14 +16,13 @@ import org.junit.jupiter.api.extension.RegisterExtension; import org.testcontainers.clickhouse.ClickHouseContainer; import org.testcontainers.containers.MySQLContainer; -import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.lifecycle.Startables; import ru.vyarus.dropwizard.guice.test.ClientSupport; import ru.vyarus.dropwizard.guice.test.jupiter.ext.TestDropwizardAppExtension; import static com.comet.opik.api.resources.utils.ClickHouseContainerUtils.DATABASE_NAME; import static org.assertj.core.api.Assertions.assertThat; -@Testcontainers(parallel = true) @TestInstance(TestInstance.Lifecycle.PER_CLASS) @DisplayName("CORS headers logic test when CORS config is enabled") class CorsEnabledE2ETest { @@ -37,9 +36,7 @@ class CorsEnabledE2ETest { private static final TestDropwizardAppExtension app; static { - MYSQL.start(); - CLICKHOUSE.start(); - REDIS.start(); + Startables.deepStart(MYSQL, CLICKHOUSE, REDIS).join(); var databaseAnalyticsFactory = ClickHouseContainerUtils.newDatabaseAnalyticsFactory( CLICKHOUSE, DATABASE_NAME); diff --git a/apps/opik-backend/src/test/java/com/comet/opik/infrastructure/ratelimit/RateLimitE2ETest.java b/apps/opik-backend/src/test/java/com/comet/opik/infrastructure/ratelimit/RateLimitE2ETest.java index 53b8d75e7..3ef70cb8d 100644 --- a/apps/opik-backend/src/test/java/com/comet/opik/infrastructure/ratelimit/RateLimitE2ETest.java +++ b/apps/opik-backend/src/test/java/com/comet/opik/infrastructure/ratelimit/RateLimitE2ETest.java @@ -48,7 +48,7 @@ import org.junit.jupiter.params.provider.MethodSource; import org.testcontainers.clickhouse.ClickHouseContainer; import org.testcontainers.containers.MySQLContainer; -import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.lifecycle.Startables; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import ru.vyarus.dropwizard.guice.test.ClientSupport; @@ -76,7 +76,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; -@Testcontainers(parallel = true) @TestInstance(TestInstance.Lifecycle.PER_CLASS) @DisplayName("Rate limit Resource Test") class RateLimitE2ETest { @@ -113,9 +112,7 @@ public Response test(@RequestBody String test, @QueryParam("time") Integer time) } static { - MYSQL.start(); - CLICKHOUSE.start(); - REDIS.start(); + Startables.deepStart(MYSQL, CLICKHOUSE, REDIS).join(); wireMock = WireMockUtils.startWireMock();