From 0abc325a8e651ee33b34b64b1e16b843d58e1c29 Mon Sep 17 00:00:00 2001 From: Andres Cruz Date: Thu, 12 Sep 2024 12:44:53 +0200 Subject: [PATCH] OPIK-72 Add batch remove traces endpoint --- .../java/com/comet/opik/api/SpanBatch.java | 2 + .../java/com/comet/opik/api/TraceBatch.java | 2 + .../java/com/comet/opik/api/TracesDelete.java | 17 + ...TraceResource.java => TracesResource.java} | 17 +- .../comet/opik/domain/FeedbackScoreDAO.java | 54 +- .../opik/domain/FeedbackScoreMapper.java | 6 + .../java/com/comet/opik/domain/SpanDAO.java | 29 +- .../java/com/comet/opik/domain/TraceDAO.java | 43 +- .../com/comet/opik/domain/TraceService.java | 25 +- .../resources/v1/priv/SpansResourceTest.java | 2 +- .../resources/v1/priv/TracesResourceTest.java | 1297 ++++++++++------- .../comet/opik/podam/BigDecimalStrategy.java | 34 + .../comet/opik/podam/PodamFactoryUtils.java | 6 +- 13 files changed, 979 insertions(+), 555 deletions(-) create mode 100644 apps/opik-backend/src/main/java/com/comet/opik/api/TracesDelete.java rename apps/opik-backend/src/main/java/com/comet/opik/api/resources/v1/priv/{TraceResource.java => TracesResource.java} (93%) create mode 100644 apps/opik-backend/src/test/java/com/comet/opik/podam/BigDecimalStrategy.java diff --git a/apps/opik-backend/src/main/java/com/comet/opik/api/SpanBatch.java b/apps/opik-backend/src/main/java/com/comet/opik/api/SpanBatch.java index 57435ebf26..02727bec49 100644 --- a/apps/opik-backend/src/main/java/com/comet/opik/api/SpanBatch.java +++ b/apps/opik-backend/src/main/java/com/comet/opik/api/SpanBatch.java @@ -4,9 +4,11 @@ import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; +import lombok.Builder; import java.util.List; +@Builder(toBuilder = true) public record SpanBatch(@NotNull @Size(min = 1, max = 1000) @JsonView( { Span.View.Write.class}) @Valid List spans){ } diff --git a/apps/opik-backend/src/main/java/com/comet/opik/api/TraceBatch.java b/apps/opik-backend/src/main/java/com/comet/opik/api/TraceBatch.java index ac5c164940..0765a89712 100644 --- a/apps/opik-backend/src/main/java/com/comet/opik/api/TraceBatch.java +++ b/apps/opik-backend/src/main/java/com/comet/opik/api/TraceBatch.java @@ -4,9 +4,11 @@ import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; +import lombok.Builder; import java.util.List; +@Builder(toBuilder = true) public record TraceBatch(@NotNull @Size(min = 1, max = 1000) @JsonView( { Trace.View.Write.class}) @Valid List traces){ } diff --git a/apps/opik-backend/src/main/java/com/comet/opik/api/TracesDelete.java b/apps/opik-backend/src/main/java/com/comet/opik/api/TracesDelete.java new file mode 100644 index 0000000000..d5f721d266 --- /dev/null +++ b/apps/opik-backend/src/main/java/com/comet/opik/api/TracesDelete.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 jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Builder; + +import java.util.Set; +import java.util.UUID; + +@Builder(toBuilder = true) +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public record TracesDelete(@NotNull @Size(min = 1, max = 1000) Set ids) { +} diff --git a/apps/opik-backend/src/main/java/com/comet/opik/api/resources/v1/priv/TraceResource.java b/apps/opik-backend/src/main/java/com/comet/opik/api/resources/v1/priv/TracesResource.java similarity index 93% rename from apps/opik-backend/src/main/java/com/comet/opik/api/resources/v1/priv/TraceResource.java rename to apps/opik-backend/src/main/java/com/comet/opik/api/resources/v1/priv/TracesResource.java index 31bf42715c..dc91804022 100644 --- a/apps/opik-backend/src/main/java/com/comet/opik/api/resources/v1/priv/TraceResource.java +++ b/apps/opik-backend/src/main/java/com/comet/opik/api/resources/v1/priv/TracesResource.java @@ -9,6 +9,7 @@ import com.comet.opik.api.TraceBatch; import com.comet.opik.api.TraceSearchCriteria; import com.comet.opik.api.TraceUpdate; +import com.comet.opik.api.TracesDelete; import com.comet.opik.api.filter.FiltersFactory; import com.comet.opik.api.filter.TraceFilter; import com.comet.opik.domain.FeedbackScoreService; @@ -60,7 +61,7 @@ @Slf4j @RequiredArgsConstructor(onConstructor_ = @jakarta.inject.Inject) @Tag(name = "Traces", description = "Trace related resources") -public class TraceResource { +public class TracesResource { private final @NonNull TraceService service; private final @NonNull FeedbackScoreService feedbackScoreService; @@ -206,6 +207,20 @@ public Response deleteById(@PathParam("id") UUID id) { return Response.noContent().build(); } + @POST + @Path("/delete") + @Operation(operationId = "deleteTraces", summary = "Delete traces", description = "Delete traces", responses = { + @ApiResponse(responseCode = "204", description = "No Content")}) + public Response deleteTraces( + @RequestBody(content = @Content(schema = @Schema(implementation = TracesDelete.class))) @NotNull @Valid TracesDelete request) { + log.info("Deleting traces, count '{}'", request.ids().size()); + service.delete(request.ids()) + .contextWrite(ctx -> setRequestContext(ctx, requestContext)) + .block(); + log.info("Deleted traces, count '{}'", request.ids().size()); + return Response.noContent().build(); + } + @PUT @Path("/{id}/feedback-scores") @Operation(operationId = "addTraceFeedbackScore", summary = "Add trace feedback score", description = "Add trace feedback score", responses = { 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 d3f2e4d738..e63db2530f 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 @@ -3,16 +3,18 @@ import com.comet.opik.api.FeedbackScore; import com.comet.opik.api.FeedbackScoreBatchItem; import com.comet.opik.api.ScoreSource; +import com.google.common.base.Preconditions; import com.google.inject.ImplementedBy; import io.r2dbc.spi.Connection; import io.r2dbc.spi.Result; import io.r2dbc.spi.Row; -import io.r2dbc.spi.Statement; import jakarta.inject.Inject; import jakarta.inject.Singleton; import lombok.Getter; import lombok.NonNull; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.stringtemplate.v4.ST; import reactor.core.publisher.Flux; @@ -24,6 +26,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; @@ -52,14 +55,16 @@ enum EntityType { Mono deleteScoreFrom(EntityType entityType, UUID id, String name, Connection connection); - Mono deleteByEntityId(EntityType entityType, UUID id, Connection connection); + Mono deleteByEntityId(EntityType entityType, UUID entityId, Connection connection); - Mono scoreBatchOf(EntityType entityType, List scores, Connection connection); + Mono deleteByEntityIds(EntityType entityType, Set entityIds, Connection connection); + Mono scoreBatchOf(EntityType entityType, List scores, Connection connection); } @Singleton @RequiredArgsConstructor(onConstructor_ = @Inject) +@Slf4j class FeedbackScoreDAOImpl implements FeedbackScoreDAO { record FeedbackScoreDto(UUID entityId, FeedbackScore score) { @@ -230,21 +235,21 @@ LEFT JOIN ( ; """; - private static final String DELETE_SPAN_CASCADE_FEEDBACK_SCORE = """ + private static final String DELETE_SPANS_CASCADE_FEEDBACK_SCORE = """ DELETE FROM feedback_scores WHERE entity_type = 'span' AND entity_id IN ( SELECT id FROM spans - WHERE trace_id = :trace_id + WHERE trace_id IN :trace_ids ) AND workspace_id = :workspace_id ; """; - private static final String DELETE_FEEDBACK_SCORE_BY_ENTITY_ID = """ + private static final String DELETE_FEEDBACK_SCORE_BY_ENTITY_IDS = """ DELETE FROM feedback_scores - WHERE entity_id = :entity_id + WHERE entity_id IN :entity_ids AND entity_type = :entity_type AND workspace_id = :workspace_id ; @@ -410,30 +415,39 @@ public Mono deleteScoreFrom(EntityType entityType, UUID id, String name, C } @Override - public Mono deleteByEntityId(@NonNull EntityType entityType, @NonNull UUID id, - @NonNull Connection connection) { + public Mono deleteByEntityId( + @NonNull EntityType entityType, @NonNull UUID entityId, @NonNull Connection connection) { + return deleteByEntityIds(entityType, Set.of(entityId), connection); + } + + @Override + public Mono deleteByEntityIds( + @NonNull EntityType entityType, Set entityIds, @NonNull Connection connection) { + Preconditions.checkArgument( + CollectionUtils.isNotEmpty(entityIds), "Argument 'entityIds' must not be empty"); + log.info("Deleting feedback scores for entityType '{}', entityIds count '{}'", entityType, entityIds.size()); return switch (entityType) { - case TRACE -> cascadeSpanDelete(id, connection) + case TRACE -> cascadeSpanDelete(entityIds, connection) .flatMap(result -> Mono.from(result.getRowsUpdated())) - .then(Mono.defer(() -> deleteScoresByEntityId(entityType, id, connection))) + .then(Mono.defer(() -> deleteScoresByEntityIds(entityType, entityIds, connection))) .then(); - case SPAN -> deleteScoresByEntityId(entityType, id, connection) + case SPAN -> deleteScoresByEntityIds(entityType, entityIds, connection) .then(); }; } - private Mono cascadeSpanDelete(UUID id, Connection connection) { - var statement = connection.createStatement(DELETE_SPAN_CASCADE_FEEDBACK_SCORE) - .bind("trace_id", id); - + 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) + .bind("trace_ids", traceIds.toArray(UUID[]::new)); return makeMonoContextAware(bindWorkspaceIdToMono(statement)); } - private Mono deleteScoresByEntityId(EntityType entityType, UUID id, Connection connection) { - Statement statement = connection.createStatement(DELETE_FEEDBACK_SCORE_BY_ENTITY_ID) - .bind("entity_id", id) + private Mono deleteScoresByEntityIds(EntityType entityType, Set entityIds, Connection connection) { + log.info("Deleting feedback scores by entityType '{}', entityIds count '{}'", entityType, entityIds.size()); + var statement = connection.createStatement(DELETE_FEEDBACK_SCORE_BY_ENTITY_IDS) + .bind("entity_ids", entityIds.toArray(UUID[]::new)) .bind("entity_type", entityType.getType()); - return makeMonoContextAware(bindWorkspaceIdToMono(statement)) .flatMap(result -> Mono.from(result.getRowsUpdated())); } diff --git a/apps/opik-backend/src/main/java/com/comet/opik/domain/FeedbackScoreMapper.java b/apps/opik-backend/src/main/java/com/comet/opik/domain/FeedbackScoreMapper.java index a6b88270e6..8add7adb4c 100644 --- a/apps/opik-backend/src/main/java/com/comet/opik/domain/FeedbackScoreMapper.java +++ b/apps/opik-backend/src/main/java/com/comet/opik/domain/FeedbackScoreMapper.java @@ -3,12 +3,18 @@ import com.comet.opik.api.FeedbackScore; import com.comet.opik.api.FeedbackScoreBatchItem; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import org.mapstruct.factory.Mappers; +import java.util.UUID; + @Mapper public interface FeedbackScoreMapper { FeedbackScoreMapper INSTANCE = Mappers.getMapper(FeedbackScoreMapper.class); FeedbackScore toFeedbackScore(FeedbackScoreBatchItem feedbackScoreBatchItem); + + @Mapping(target = "id", source = "entityId") + FeedbackScoreBatchItem toFeedbackScoreBatchItem(UUID entityId, String projectName, FeedbackScore feedbackScore); } diff --git a/apps/opik-backend/src/main/java/com/comet/opik/domain/SpanDAO.java b/apps/opik-backend/src/main/java/com/comet/opik/domain/SpanDAO.java index 0a6a9daa4e..c532dd4de1 100644 --- a/apps/opik-backend/src/main/java/com/comet/opik/domain/SpanDAO.java +++ b/apps/opik-backend/src/main/java/com/comet/opik/domain/SpanDAO.java @@ -19,6 +19,7 @@ import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; import org.reactivestreams.Publisher; import org.stringtemplate.v4.ST; import reactor.core.publisher.Mono; @@ -96,7 +97,7 @@ INSERT INTO spans( * This query handles the insertion of a new span into the database in two cases: * 1. When the span does not exist in the database. * 2. When the span exists in the database but the provided span has different values for the fields such as end_time, input, output, metadata and tags. - * **/ + **/ //TODO: refactor to implement proper conflict resolution private static final String INSERT = """ INSERT INTO spans( @@ -268,14 +269,13 @@ INSERT INTO spans ( /** * This query is used when updates are processed before inserts, and the span does not exist in the database. - * + *

* The query will insert/update a new span with the provided values such as end_time, input, output, metadata, tags etc. * In case the values are not provided, the query will use the default values such value are interpreted in other queries as null. - * + *

* This happens because the query is used in a patch endpoint which allows partial updates, so the query will update only the provided fields. * The remaining fields will be updated/inserted once the POST arrives with the all mandatory fields to create the trace. - * - * */ + */ //TODO: refactor to implement proper conflict resolution private static final String PARTIAL_INSERT = """ INSERT INTO spans( @@ -466,8 +466,8 @@ AND id in ( ; """; - private static final String DELETE_BY_TRACE_ID = """ - DELETE FROM spans WHERE trace_id = :trace_id AND workspace_id = :workspace_id; + private static final String DELETE_BY_TRACE_IDS = """ + DELETE FROM spans WHERE trace_id IN :trace_ids AND workspace_id = :workspace_id; """; private static final String SELECT_SPAN_ID_AND_WORKSPACE = """ @@ -745,15 +745,20 @@ private Publisher getById(UUID id, Connection connection) { @Trace(dispatcher = true) public Mono deleteByTraceId(@NonNull UUID traceId, @NonNull Connection connection) { - Statement statement = connection.createStatement(DELETE_BY_TRACE_ID) - .bind("trace_id", traceId); - - Segment segment = startSegment("spans", "Clickhouse", "delete_by_trace_id"); + return deleteByTraceIds(Set.of(traceId), connection); + } + @Trace(dispatcher = true) + public Mono deleteByTraceIds(Set traceIds, @NonNull Connection connection) { + Preconditions.checkArgument( + CollectionUtils.isNotEmpty(traceIds), "Argument 'traceIds' must not be empty"); + log.info("Deleting spans by traceIds, count '{}'", traceIds.size()); + var statement = connection.createStatement(DELETE_BY_TRACE_IDS) + .bind("trace_ids", traceIds); + var segment = startSegment("spans", "Clickhouse", "delete_by_trace_id"); return makeMonoContextAware(bindWorkspaceIdToMono(statement)) .doFinally(signalType -> segment.end()) .then(); - } private Publisher mapToDto(Result result) { diff --git a/apps/opik-backend/src/main/java/com/comet/opik/domain/TraceDAO.java b/apps/opik-backend/src/main/java/com/comet/opik/domain/TraceDAO.java index 5a054f3245..e31135a3c7 100644 --- a/apps/opik-backend/src/main/java/com/comet/opik/domain/TraceDAO.java +++ b/apps/opik-backend/src/main/java/com/comet/opik/domain/TraceDAO.java @@ -19,6 +19,7 @@ import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; import org.reactivestreams.Publisher; import org.stringtemplate.v4.ST; import reactor.core.publisher.Flux; @@ -52,12 +53,13 @@ interface TraceDAO { Mono delete(UUID id, Connection connection); + Mono delete(Set ids, Connection connection); + Mono findById(UUID id, Connection connection); Mono find(int size, int page, TraceSearchCriteria traceSearchCriteria, Connection connection); - Mono partialInsert(UUID projectId, TraceUpdate traceUpdate, UUID traceId, - Connection connection); + Mono partialInsert(UUID projectId, TraceUpdate traceUpdate, UUID traceId, Connection connection); Mono> getTraceWorkspace(Set traceIds, Connection connection); @@ -108,7 +110,7 @@ INSERT INTO traces( * This query handles the insertion of a new trace into the database in two cases: * 1. When the trace does not exist in the database. * 2. When the trace exists in the database but the provided trace has different values for the fields such as end_time, input, output, metadata and tags. - * **/ + **/ //TODO: refactor to implement proper conflict resolution private static final String INSERT = """ INSERT INTO traces( @@ -307,7 +309,7 @@ AND id in ( private static final String DELETE_BY_ID = """ DELETE FROM traces - WHERE id = :id + WHERE id IN :ids AND workspace_id = :workspace_id ; """; @@ -323,15 +325,14 @@ AND id in ( """; /** - * This query is used when updates are processed before inserts, and the trace does not exist in the database. - * - * The query will insert/update a new trace with the provided values such as end_time, input, output, metadata and tags. - * In case the values are not provided, the query will use the default values such value are interpreted in other queries as null. - * - * This happens because the query is used in a patch endpoint which allows partial updates, so the query will update only the provided fields. - * The remaining fields will be updated/inserted once the POST arrives with the all mandatory fields to create the trace. - * - * */ + * This query is used when updates are processed before inserts, and the trace does not exist in the database. + *

+ * The query will insert/update a new trace with the provided values such as end_time, input, output, metadata and tags. + * In case the values are not provided, the query will use the default values such value are interpreted in other queries as null. + *

+ * This happens because the query is used in a patch endpoint which allows partial updates, so the query will update only the provided fields. + * The remaining fields will be updated/inserted once the POST arrives with the all mandatory fields to create the trace. + */ //TODO: refactor to implement proper conflict resolution private static final String INSERT_UPDATE = """ INSERT INTO traces ( @@ -505,8 +506,7 @@ private Mono update(UUID id, TraceUpdate traceUpdate, Connecti .doFinally(signalType -> endSegment(segment)); } - private Statement createUpdateStatement(UUID id, TraceUpdate traceUpdate, Connection connection, - String sql) { + private Statement createUpdateStatement(UUID id, TraceUpdate traceUpdate, Connection connection, String sql) { Statement statement = connection.createStatement(sql); bindUpdateParams(traceUpdate, statement); @@ -566,11 +566,16 @@ private Flux getById(UUID id, Connection connection) { @Override @com.newrelic.api.agent.Trace(dispatcher = true) public Mono delete(@NonNull UUID id, @NonNull Connection connection) { - var statement = connection.createStatement(DELETE_BY_ID) - .bind("id", id); - - Segment segment = startSegment("traces", "Clickhouse", "delete"); + return delete(Set.of(id), connection); + } + @Override + public Mono delete(Set ids, @NonNull Connection connection) { + Preconditions.checkArgument(CollectionUtils.isNotEmpty(ids), "Argument 'ids' must not be empty"); + log.info("Deleting traces, count '{}'", ids.size()); + var statement = connection.createStatement(DELETE_BY_ID) + .bind("ids", ids.toArray(UUID[]::new)); + var segment = startSegment("traces", "Clickhouse", "delete"); return makeMonoContextAware(bindWorkspaceIdToMono(statement)) .doFinally(signalType -> endSegment(segment)) .then(); diff --git a/apps/opik-backend/src/main/java/com/comet/opik/domain/TraceService.java b/apps/opik-backend/src/main/java/com/comet/opik/domain/TraceService.java index 1b3b70a556..956b01eb36 100644 --- a/apps/opik-backend/src/main/java/com/comet/opik/domain/TraceService.java +++ b/apps/opik-backend/src/main/java/com/comet/opik/domain/TraceService.java @@ -23,6 +23,7 @@ import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; @@ -50,6 +51,8 @@ public interface TraceService { Mono delete(UUID id); + Mono delete(Set ids); + Mono find(int page, int size, TraceSearchCriteria criteria); Mono validateTraceWorkspace(String workspaceId, Set traceIds); @@ -162,11 +165,10 @@ private Mono getProjectById(TraceUpdate traceUpdate) { } private Mono getOrCreateProject(String projectName) { - return AsyncUtils.makeMonoContextAware((userName, workspaceName, workspaceId) -> { - return Mono.fromCallable(() -> projectService.getOrCreate(workspaceId, projectName, userName)) - .onErrorResume(e -> handleProjectCreationError(e, projectName, workspaceId)) - .subscribeOn(Schedulers.boundedElastic()); - }); + return AsyncUtils.makeMonoContextAware((userName, workspaceName, workspaceId) -> Mono + .fromCallable(() -> projectService.getOrCreate(workspaceId, projectName, userName)) + .onErrorResume(e -> handleProjectCreationError(e, projectName, workspaceId)) + .subscribeOn(Schedulers.boundedElastic())); } private Mono insertTrace(Trace newTrace, Project project, UUID id, Trace existingTrace) { @@ -272,6 +274,7 @@ public Mono get(@NonNull UUID id) { @Override @com.newrelic.api.agent.Trace(dispatcher = true) public Mono delete(@NonNull UUID id) { + log.info("Deleting trace by id '{}'", id); return lockService.executeWithLock( new LockService.Lock(id, TRACE_KEY), Mono.defer(() -> template @@ -282,6 +285,18 @@ public Mono delete(@NonNull UUID id) { .then(Mono.defer(() -> template.nonTransaction(connection -> dao.delete(id, connection)))))); } + @Override + @com.newrelic.api.agent.Trace(dispatcher = true) + public Mono delete(Set ids) { + Preconditions.checkArgument(CollectionUtils.isNotEmpty(ids), "Argument 'ids' must not be empty"); + log.info("Deleting traces, count '{}'", ids.size()); + return template + .nonTransaction(connection -> feedbackScoreDAO.deleteByEntityIds(EntityType.TRACE, ids, connection)) + .then(Mono + .defer(() -> template.nonTransaction(connection -> spanDAO.deleteByTraceIds(ids, connection)))) + .then(Mono.defer(() -> template.nonTransaction(connection -> dao.delete(ids, connection)))); + } + @Override @com.newrelic.api.agent.Trace(dispatcher = true) public Mono find(int page, int size, @NonNull TraceSearchCriteria criteria) { 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 028c1c3c75..ecc0b11279 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 @@ -3252,7 +3252,7 @@ void batch__whenSendingMultipleSpansWithSameId__thenReturn422() { .request() .header(HttpHeaders.AUTHORIZATION, API_KEY) .header(WORKSPACE_HEADER, TEST_WORKSPACE) - .post(Entity.json(new SpanBatch(expectedSpans1)))) { + .post(Entity.json(SpanBatch.builder().spans(expectedSpans1).build()))) { assertThat(actualResponse.getStatusInfo().getStatusCode()).isEqualTo(422); assertThat(actualResponse.hasEntity()).isTrue(); 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 aa5e155592..8bdf9fb926 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 @@ -6,9 +6,12 @@ import com.comet.opik.api.FeedbackScoreBatchItem; import com.comet.opik.api.Project; import com.comet.opik.api.ScoreSource; +import com.comet.opik.api.Span; +import com.comet.opik.api.SpanBatch; import com.comet.opik.api.Trace; import com.comet.opik.api.TraceBatch; import com.comet.opik.api.TraceUpdate; +import com.comet.opik.api.TracesDelete; import com.comet.opik.api.error.ErrorMessage; import com.comet.opik.api.filter.Filter; import com.comet.opik.api.filter.Operator; @@ -24,6 +27,8 @@ import com.comet.opik.api.resources.utils.TestDropwizardAppExtensionUtils; import com.comet.opik.api.resources.utils.TestUtils; import com.comet.opik.api.resources.utils.WireMockUtils; +import com.comet.opik.domain.FeedbackScoreMapper; +import com.comet.opik.domain.SpanType; import com.comet.opik.infrastructure.auth.RequestContext; import com.comet.opik.podam.PodamFactoryUtils; import com.comet.opik.utils.JsonUtils; @@ -94,16 +99,19 @@ @TestInstance(TestInstance.Lifecycle.PER_CLASS) class TracesResourceTest { - public static final String URL_PATTERN = "http://.*/v1/private/traces/.{8}-.{4}-.{4}-.{4}-.{12}"; public static final String URL_TEMPLATE = "%s/v1/private/traces"; - public static final String[] IGNORED_FIELDS = {"projectId", "projectName", "id", "createdAt", "lastUpdatedAt", - "createdBy", "lastUpdatedBy"}; - public static final String[] IGNORED_FIELDS_LIST = {"projectId", "projectName", "createdAt", + private static final String URL_TEMPLATE_SPANS = "%s/v1/private/spans"; + private static final String[] IGNORED_FIELDS_TRACES = {"projectId", "projectName", "createdAt", + "lastUpdatedAt", "feedbackScores", "createdBy", "lastUpdatedBy"}; + private static final String[] IGNORED_FIELDS_SPANS = {"projectId", "projectName", "createdAt", "lastUpdatedAt", "feedbackScores", "createdBy", "lastUpdatedBy"}; + private static final String[] IGNORED_FIELDS_SCORES = {"projectId", "projectName", "id", "createdAt", + "lastUpdatedAt", + "createdBy", "lastUpdatedBy"}; private static final String API_KEY = UUID.randomUUID().toString(); - public static final String USER = UUID.randomUUID().toString(); - public static final String WORKSPACE_ID = UUID.randomUUID().toString(); + private static final String USER = UUID.randomUUID().toString(); + private static final String WORKSPACE_ID = UUID.randomUUID().toString(); private static final String TEST_WORKSPACE = UUID.randomUUID().toString(); private static final RedisContainer REDIS = RedisContainerUtils.newRedisContainer(); @@ -228,8 +236,8 @@ void setUp() { @DisplayName("create trace, when api key is present, then return proper response") void create__whenApiKeyIsPresent__thenReturnProperResponse(String apiKey, boolean expected) { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); mockTargetWorkspace(okApikey, workspaceName, workspaceId); @@ -262,8 +270,8 @@ void create__whenApiKeyIsPresent__thenReturnProperResponse(String apiKey, boolea @DisplayName("update trace, when api key is present, then return proper response") void update__whenApiKeyIsPresent__thenReturnProperResponse(String apiKey, boolean expected) { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); mockTargetWorkspace(okApikey, workspaceName, workspaceId); @@ -298,7 +306,7 @@ void update__whenApiKeyIsPresent__thenReturnProperResponse(String apiKey, boolea @DisplayName("delete trace, when api key is present, then return proper response") void delete__whenApiKeyIsPresent__thenReturnProperResponse(String apiKey, boolean expected) { - String workspaceName = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); mockTargetWorkspace(okApikey, workspaceName, WORKSPACE_ID); @@ -341,7 +349,7 @@ void get__whenApiKeyIsPresent__thenReturnProperResponse(String apiKey, boolean e .build()) .toList(); - traces.forEach(trace -> TracesResourceTest.this.create(trace, okApikey, workspaceName)); + traces.forEach(trace -> create(trace, okApikey, workspaceName)); try (var actualResponse = client.target(URL_TEMPLATE.formatted(baseURI)) .queryParam("project_name", DEFAULT_PROJECT) @@ -369,7 +377,7 @@ void get__whenApiKeyIsPresent__thenReturnProperResponse(String apiKey, boolean e @DisplayName("Trace feedback, when api key is present, then return proper response") void feedback__whenApiKeyIsPresent__thenReturnProperResponse(String apiKey, boolean expected) { - String workspaceName = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); mockTargetWorkspace(okApikey, workspaceName, WORKSPACE_ID); var trace = factory.manufacturePojo(Trace.class) @@ -406,10 +414,10 @@ void feedback__whenApiKeyIsPresent__thenReturnProperResponse(String apiKey, bool @MethodSource("credentials") @DisplayName("delete feedback, when api key is present, then return proper response") void deleteFeedback__whenApiKeyIsPresent__thenReturnProperResponse(String apiKey, boolean expected) { - Trace trace = factory.manufacturePojo(Trace.class); + var trace = factory.manufacturePojo(Trace.class); - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); mockTargetWorkspace(okApikey, workspaceName, workspaceId); @@ -442,8 +450,8 @@ void deleteFeedback__whenApiKeyIsPresent__thenReturnProperResponse(String apiKey @DisplayName("Trace feedback batch, when api key is present, then return proper response") void feedbackBatch__whenApiKeyIsPresent__thenReturnProperResponse(String apiKey, boolean expected) { - Trace trace = factory.manufacturePojo(Trace.class); - String workspaceName = UUID.randomUUID().toString(); + var trace = factory.manufacturePojo(Trace.class); + var workspaceName = UUID.randomUUID().toString(); mockTargetWorkspace(okApikey, workspaceName, WORKSPACE_ID); @@ -606,7 +614,7 @@ void delete__whenSessionTokenIsPresent__thenReturnProperResponse(String sessionT void get__whenSessionTokenIsPresent__thenReturnProperResponse(String sessionToken, boolean expected, String workspaceName) { - String projectName = UUID.randomUUID().toString(); + var projectName = UUID.randomUUID().toString(); mockTargetWorkspace(API_KEY, workspaceName, WORKSPACE_ID); @@ -619,7 +627,7 @@ void get__whenSessionTokenIsPresent__thenReturnProperResponse(String sessionToke .build()) .toList(); - traces.forEach(trace -> TracesResourceTest.this.create(trace, API_KEY, workspaceName)); + traces.forEach(trace -> create(trace, API_KEY, workspaceName)); try (var actualResponse = client.target(URL_TEMPLATE.formatted(baseURI)) .queryParam("project_name", projectName) @@ -685,7 +693,7 @@ void feedback__whenSessionTokenIsPresent__thenReturnProperResponse(String sessio @DisplayName("delete feedback, when session token is present, then return proper response") void deleteFeedback__whenSessionTokenIsPresent__thenReturnProperResponse(String sessionToken, boolean expected, String workspaceName) { - Trace trace = factory.manufacturePojo(Trace.class); + var trace = factory.manufacturePojo(Trace.class); mockTargetWorkspace(API_KEY, workspaceName, WORKSPACE_ID); @@ -720,7 +728,7 @@ void deleteFeedback__whenSessionTokenIsPresent__thenReturnProperResponse(String void feedbackBatch__whenSessionTokenIsPresent__thenReturnProperResponse(String sessionToken, boolean expected, String workspaceName) { - Trace trace = factory.manufacturePojo(Trace.class); + var trace = factory.manufacturePojo(Trace.class); mockTargetWorkspace(API_KEY, workspaceName, WORKSPACE_ID); @@ -790,9 +798,9 @@ void getByProjectName__whenProjectNameAndIdAreNull__thenReturnBadRequest() { @DisplayName("when project name is not empty, then return traces by project name") void getByProjectName__whenProjectNameIsNotEmpty__thenReturnTracesByProjectName() { - String projectName = UUID.randomUUID().toString(); - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var projectName = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -832,9 +840,9 @@ void getByProjectName__whenProjectNameIsNotEmpty__thenReturnTracesByProjectName( @DisplayName("when project id is not empty, then return traces by project id") void getByProjectName__whenProjectIdIsNotEmpty__thenReturnTracesByProjectId() { - String workspaceName = UUID.randomUUID().toString(); - String projectName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var projectName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -908,8 +916,8 @@ void getByProjectName__whenFilterWorkspaceName__thenReturnTracesFiltered() { .build()) .toList(); - traces1.forEach(trace -> TracesResourceTest.this.create(trace, apiKey1, workspaceName1)); - traces2.forEach(trace -> TracesResourceTest.this.create(trace, apiKey2, workspaceName2)); + traces1.forEach(trace -> create(trace, apiKey1, workspaceName1)); + traces2.forEach(trace -> create(trace, apiKey2, workspaceName2)); getAndAssertPage(1, traces2.size() + traces1.size(), projectName1, List.of(), traces1.reversed(), traces2.reversed(), workspaceName1, apiKey1); @@ -920,8 +928,8 @@ void getByProjectName__whenFilterWorkspaceName__thenReturnTracesFiltered() { @Test void getByProjectName__whenFilterIdAndNameEqual__thenReturnTracesFiltered() { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -935,12 +943,12 @@ void getByProjectName__whenFilterIdAndNameEqual__thenReturnTracesFiltered() { .feedbackScores(null) .build()) .collect(Collectors.toCollection(ArrayList::new)); - traces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + traces.forEach(trace -> create(trace, apiKey, workspaceName)); var expectedTraces = List.of(traces.getFirst()); var unexpectedTraces = List.of(factory.manufacturePojo(Trace.class).toBuilder() .projectId(null) .build()); - unexpectedTraces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + unexpectedTraces.forEach(trace -> create(trace, apiKey, workspaceName)); var filters = List.of( TraceFilter.builder() @@ -958,8 +966,8 @@ void getByProjectName__whenFilterIdAndNameEqual__thenReturnTracesFiltered() { @Test void getByProjectName__whenFilterNameEqual__thenReturnTracesFiltered() { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -973,12 +981,12 @@ void getByProjectName__whenFilterNameEqual__thenReturnTracesFiltered() { .feedbackScores(null) .build()) .collect(Collectors.toCollection(ArrayList::new)); - traces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + traces.forEach(trace -> create(trace, apiKey, workspaceName)); var expectedTraces = List.of(traces.getFirst()); var unexpectedTraces = List.of(factory.manufacturePojo(Trace.class).toBuilder() .projectId(null) .build()); - unexpectedTraces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + unexpectedTraces.forEach(trace -> create(trace, apiKey, workspaceName)); var filters = List.of(TraceFilter.builder() .field(TraceField.NAME) @@ -990,8 +998,8 @@ void getByProjectName__whenFilterNameEqual__thenReturnTracesFiltered() { @Test void getByProjectName__whenFilterNameStartsWith__thenReturnTracesFiltered() { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -1005,12 +1013,12 @@ void getByProjectName__whenFilterNameStartsWith__thenReturnTracesFiltered() { .feedbackScores(null) .build()) .collect(Collectors.toCollection(ArrayList::new)); - traces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + traces.forEach(trace -> create(trace, apiKey, workspaceName)); var expectedTraces = List.of(traces.getFirst()); var unexpectedTraces = List.of(factory.manufacturePojo(Trace.class).toBuilder() .projectId(null) .build()); - unexpectedTraces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + unexpectedTraces.forEach(trace -> create(trace, apiKey, workspaceName)); var filters = List.of(TraceFilter.builder() .field(TraceField.NAME) @@ -1022,8 +1030,8 @@ void getByProjectName__whenFilterNameStartsWith__thenReturnTracesFiltered() { @Test void getByProjectName__whenFilterNameEndsWith__thenReturnTracesFiltered() { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -1037,12 +1045,12 @@ void getByProjectName__whenFilterNameEndsWith__thenReturnTracesFiltered() { .feedbackScores(null) .build()) .collect(Collectors.toCollection(ArrayList::new)); - traces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + traces.forEach(trace -> create(trace, apiKey, workspaceName)); var expectedTraces = List.of(traces.getFirst()); var unexpectedTraces = List.of(factory.manufacturePojo(Trace.class).toBuilder() .projectId(null) .build()); - unexpectedTraces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + unexpectedTraces.forEach(trace -> create(trace, apiKey, workspaceName)); var filters = List.of(TraceFilter.builder() .field(TraceField.NAME) @@ -1054,8 +1062,8 @@ void getByProjectName__whenFilterNameEndsWith__thenReturnTracesFiltered() { @Test void getByProjectName__whenFilterNameContains__thenReturnTracesFiltered() { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -1069,12 +1077,12 @@ void getByProjectName__whenFilterNameContains__thenReturnTracesFiltered() { .feedbackScores(null) .build()) .collect(Collectors.toCollection(ArrayList::new)); - traces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + traces.forEach(trace -> create(trace, apiKey, workspaceName)); var expectedTraces = List.of(traces.getFirst()); var unexpectedTraces = List.of(factory.manufacturePojo(Trace.class).toBuilder() .projectId(null) .build()); - unexpectedTraces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + unexpectedTraces.forEach(trace -> create(trace, apiKey, workspaceName)); var filters = List.of(TraceFilter.builder() .field(TraceField.NAME) @@ -1086,8 +1094,8 @@ void getByProjectName__whenFilterNameContains__thenReturnTracesFiltered() { @Test void getByProjectName__whenFilterNameNotContains__thenReturnTracesFiltered() { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -1106,12 +1114,12 @@ void getByProjectName__whenFilterNameNotContains__thenReturnTracesFiltered() { traces.set(0, traces.getFirst().toBuilder() .name(generator.generate().toString()) .build()); - traces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + traces.forEach(trace -> create(trace, apiKey, workspaceName)); var expectedTraces = List.of(traces.getFirst()); var unexpectedTraces = List.of(factory.manufacturePojo(Trace.class).toBuilder() .projectId(null) .build()); - unexpectedTraces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + unexpectedTraces.forEach(trace -> create(trace, apiKey, workspaceName)); var filters = List.of(TraceFilter.builder() .field(TraceField.NAME) @@ -1123,8 +1131,8 @@ void getByProjectName__whenFilterNameNotContains__thenReturnTracesFiltered() { @Test void getByProjectName__whenFilterStartTimeEqual__thenReturnTracesFiltered() { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -1138,12 +1146,12 @@ void getByProjectName__whenFilterStartTimeEqual__thenReturnTracesFiltered() { .feedbackScores(null) .build()) .collect(Collectors.toCollection(ArrayList::new)); - traces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + traces.forEach(trace -> create(trace, apiKey, workspaceName)); var expectedTraces = List.of(traces.getFirst()); var unexpectedTraces = List.of(factory.manufacturePojo(Trace.class).toBuilder() .projectId(null) .build()); - unexpectedTraces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + unexpectedTraces.forEach(trace -> create(trace, apiKey, workspaceName)); var filters = List.of(TraceFilter.builder() .field(TraceField.START_TIME) @@ -1155,8 +1163,8 @@ void getByProjectName__whenFilterStartTimeEqual__thenReturnTracesFiltered() { @Test void getByProjectName__whenFilterStartTimeGreaterThan__thenReturnTracesFiltered() { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -1174,12 +1182,12 @@ void getByProjectName__whenFilterStartTimeGreaterThan__thenReturnTracesFiltered( traces.set(0, traces.getFirst().toBuilder() .startTime(Instant.now().plusSeconds(60 * 5)) .build()); - traces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + traces.forEach(trace -> create(trace, apiKey, workspaceName)); var expectedTraces = List.of(traces.getFirst()); var unexpectedTraces = List.of(factory.manufacturePojo(Trace.class).toBuilder() .projectId(null) .build()); - unexpectedTraces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + unexpectedTraces.forEach(trace -> create(trace, apiKey, workspaceName)); var filters = List.of(TraceFilter.builder() .field(TraceField.START_TIME) @@ -1191,8 +1199,8 @@ void getByProjectName__whenFilterStartTimeGreaterThan__thenReturnTracesFiltered( @Test void getByProjectName__whenFilterStartTimeGreaterThanEqual__thenReturnTracesFiltered() { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -1210,12 +1218,12 @@ void getByProjectName__whenFilterStartTimeGreaterThanEqual__thenReturnTracesFilt traces.set(0, traces.getFirst().toBuilder() .startTime(Instant.now().plusSeconds(60 * 5)) .build()); - traces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + traces.forEach(trace -> create(trace, apiKey, workspaceName)); var expectedTraces = List.of(traces.getFirst()); var unexpectedTraces = List.of(factory.manufacturePojo(Trace.class).toBuilder() .projectId(null) .build()); - unexpectedTraces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + unexpectedTraces.forEach(trace -> create(trace, apiKey, workspaceName)); var filters = List.of(TraceFilter.builder() .field(TraceField.START_TIME) @@ -1227,8 +1235,8 @@ void getByProjectName__whenFilterStartTimeGreaterThanEqual__thenReturnTracesFilt @Test void getByProjectName__whenFilterStartTimeLessThan__thenReturnTracesFiltered() { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -1246,12 +1254,12 @@ void getByProjectName__whenFilterStartTimeLessThan__thenReturnTracesFiltered() { traces.set(0, traces.getFirst().toBuilder() .startTime(Instant.now().minusSeconds(60 * 5)) .build()); - traces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + traces.forEach(trace -> create(trace, apiKey, workspaceName)); var expectedTraces = List.of(traces.getFirst()); var unexpectedTraces = List.of(factory.manufacturePojo(Trace.class).toBuilder() .projectId(null) .build()); - unexpectedTraces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + unexpectedTraces.forEach(trace -> create(trace, apiKey, workspaceName)); var filters = List.of(TraceFilter.builder() .field(TraceField.START_TIME) @@ -1263,8 +1271,8 @@ void getByProjectName__whenFilterStartTimeLessThan__thenReturnTracesFiltered() { @Test void getByProjectName__whenFilterStartTimeLessThanEqual__thenReturnTracesFiltered() { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -1282,12 +1290,12 @@ void getByProjectName__whenFilterStartTimeLessThanEqual__thenReturnTracesFiltere traces.set(0, traces.getFirst().toBuilder() .startTime(Instant.now().minusSeconds(60 * 5)) .build()); - traces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + traces.forEach(trace -> create(trace, apiKey, workspaceName)); var expectedTraces = List.of(traces.getFirst()); var unexpectedTraces = List.of(factory.manufacturePojo(Trace.class).toBuilder() .projectId(null) .build()); - unexpectedTraces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + unexpectedTraces.forEach(trace -> create(trace, apiKey, workspaceName)); var filters = List.of(TraceFilter.builder() .field(TraceField.START_TIME) @@ -1299,8 +1307,8 @@ void getByProjectName__whenFilterStartTimeLessThanEqual__thenReturnTracesFiltere @Test void getByProjectName__whenFilterEndTimeEqual__thenReturnTracesFiltered() { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -1314,12 +1322,12 @@ void getByProjectName__whenFilterEndTimeEqual__thenReturnTracesFiltered() { .feedbackScores(null) .build()) .collect(Collectors.toCollection(ArrayList::new)); - traces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + traces.forEach(trace -> create(trace, apiKey, workspaceName)); var expectedTraces = List.of(traces.getFirst()); var unexpectedTraces = List.of(factory.manufacturePojo(Trace.class).toBuilder() .projectId(null) .build()); - unexpectedTraces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + unexpectedTraces.forEach(trace -> create(trace, apiKey, workspaceName)); var filters = List.of(TraceFilter.builder() .field(TraceField.END_TIME) @@ -1331,8 +1339,8 @@ void getByProjectName__whenFilterEndTimeEqual__thenReturnTracesFiltered() { @Test void getByProjectName__whenFilterInputEqual__thenReturnTracesFiltered() { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -1346,12 +1354,12 @@ void getByProjectName__whenFilterInputEqual__thenReturnTracesFiltered() { .feedbackScores(null) .build()) .collect(Collectors.toCollection(ArrayList::new)); - traces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + traces.forEach(trace -> create(trace, apiKey, workspaceName)); var expectedTraces = List.of(traces.getFirst()); var unexpectedTraces = List.of(factory.manufacturePojo(Trace.class).toBuilder() .projectId(null) .build()); - unexpectedTraces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + unexpectedTraces.forEach(trace -> create(trace, apiKey, workspaceName)); var filters = List.of(TraceFilter.builder() .field(TraceField.INPUT) @@ -1363,8 +1371,8 @@ void getByProjectName__whenFilterInputEqual__thenReturnTracesFiltered() { @Test void getByProjectName__whenFilterOutputEqual__thenReturnTracesFiltered() { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -1378,12 +1386,12 @@ void getByProjectName__whenFilterOutputEqual__thenReturnTracesFiltered() { .feedbackScores(null) .build()) .collect(Collectors.toCollection(ArrayList::new)); - traces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + traces.forEach(trace -> create(trace, apiKey, workspaceName)); var expectedTraces = List.of(traces.getFirst()); var unexpectedTraces = List.of(factory.manufacturePojo(Trace.class).toBuilder() .projectId(null) .build()); - unexpectedTraces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + unexpectedTraces.forEach(trace -> create(trace, apiKey, workspaceName)); var filters = List.of(TraceFilter.builder() .field(TraceField.OUTPUT) @@ -1395,8 +1403,8 @@ void getByProjectName__whenFilterOutputEqual__thenReturnTracesFiltered() { @Test void getByProjectName__whenFilterMetadataEqualString__thenReturnTracesFiltered() { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -1416,12 +1424,12 @@ void getByProjectName__whenFilterMetadataEqualString__thenReturnTracesFiltered() .metadata(JsonUtils.getJsonNodeFromString("{\"model\":[{\"year\":2024,\"version\":\"OpenAI, " + "Chat-GPT 4.0\"}]}")) .build()); - traces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + traces.forEach(trace -> create(trace, apiKey, workspaceName)); var expectedTraces = List.of(traces.getFirst()); var unexpectedTraces = List.of(factory.manufacturePojo(Trace.class).toBuilder() .projectId(null) .build()); - unexpectedTraces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + unexpectedTraces.forEach(trace -> create(trace, apiKey, workspaceName)); var filters = List.of(TraceFilter.builder() .field(TraceField.METADATA) @@ -1434,8 +1442,8 @@ void getByProjectName__whenFilterMetadataEqualString__thenReturnTracesFiltered() @Test void getByProjectName__whenFilterMetadataEqualNumber__thenReturnTracesFiltered() { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -1454,12 +1462,12 @@ void getByProjectName__whenFilterMetadataEqualNumber__thenReturnTracesFiltered() .metadata(JsonUtils.getJsonNodeFromString("{\"model\":[{\"year\":2023,\"version\":\"OpenAI, " + "Chat-GPT 4.0\"}]}")) .build()); - traces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + traces.forEach(trace -> create(trace, apiKey, workspaceName)); var expectedTraces = List.of(traces.getFirst()); var unexpectedTraces = List.of(factory.manufacturePojo(Trace.class).toBuilder() .projectId(null) .build()); - unexpectedTraces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + unexpectedTraces.forEach(trace -> create(trace, apiKey, workspaceName)); var filters = List.of(TraceFilter.builder() .field(TraceField.METADATA) @@ -1472,8 +1480,8 @@ void getByProjectName__whenFilterMetadataEqualNumber__thenReturnTracesFiltered() @Test void getByProjectName__whenFilterMetadataEqualBoolean__thenReturnTracesFiltered() { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -1494,12 +1502,12 @@ void getByProjectName__whenFilterMetadataEqualBoolean__thenReturnTracesFiltered( .metadata(JsonUtils.getJsonNodeFromString("{\"model\":[{\"year\":true,\"version\":\"OpenAI, " + "Chat-GPT 4.0\"}]}")) .build()); - traces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + traces.forEach(trace -> create(trace, apiKey, workspaceName)); var expectedTraces = List.of(traces.getFirst()); var unexpectedTraces = List.of(factory.manufacturePojo(Trace.class).toBuilder() .projectId(null) .build()); - unexpectedTraces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + unexpectedTraces.forEach(trace -> create(trace, apiKey, workspaceName)); var filters = List.of(TraceFilter.builder() .field(TraceField.METADATA) @@ -1512,8 +1520,8 @@ void getByProjectName__whenFilterMetadataEqualBoolean__thenReturnTracesFiltered( @Test void getByProjectName__whenFilterMetadataEqualNull__thenReturnTracesFiltered() { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -1533,12 +1541,12 @@ void getByProjectName__whenFilterMetadataEqualNull__thenReturnTracesFiltered() { .metadata(JsonUtils.getJsonNodeFromString("{\"model\":[{\"year\":null,\"version\":\"OpenAI, " + "Chat-GPT 4.0\"}]}")) .build()); - traces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + traces.forEach(trace -> create(trace, apiKey, workspaceName)); var expectedTraces = List.of(traces.getFirst()); var unexpectedTraces = List.of(factory.manufacturePojo(Trace.class).toBuilder() .projectId(null) .build()); - unexpectedTraces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + unexpectedTraces.forEach(trace -> create(trace, apiKey, workspaceName)); var filters = List.of(TraceFilter.builder() .field(TraceField.METADATA) @@ -1551,8 +1559,8 @@ void getByProjectName__whenFilterMetadataEqualNull__thenReturnTracesFiltered() { @Test void getByProjectName__whenFilterMetadataContainsString__thenReturnTracesFiltered() { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -1572,12 +1580,12 @@ void getByProjectName__whenFilterMetadataContainsString__thenReturnTracesFiltere .metadata(JsonUtils.getJsonNodeFromString("{\"model\":[{\"year\":2024,\"version\":\"OpenAI, " + "Chat-GPT 4.0\"}]}")) .build()); - traces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + traces.forEach(trace -> create(trace, apiKey, workspaceName)); var expectedTraces = List.of(traces.getFirst()); var unexpectedTraces = List.of(factory.manufacturePojo(Trace.class).toBuilder() .projectId(null) .build()); - unexpectedTraces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + unexpectedTraces.forEach(trace -> create(trace, apiKey, workspaceName)); var filters = List.of(TraceFilter.builder() .field(TraceField.METADATA) @@ -1590,8 +1598,8 @@ void getByProjectName__whenFilterMetadataContainsString__thenReturnTracesFiltere @Test void getByProjectName__whenFilterMetadataContainsNumber__thenReturnTracesFiltered() { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -1611,12 +1619,12 @@ void getByProjectName__whenFilterMetadataContainsNumber__thenReturnTracesFiltere .metadata(JsonUtils.getJsonNodeFromString("{\"model\":[{\"year\":2023,\"version\":\"OpenAI, " + "Chat-GPT 4.0\"}]}")) .build()); - traces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + traces.forEach(trace -> create(trace, apiKey, workspaceName)); var expectedTraces = List.of(traces.getFirst()); var unexpectedTraces = List.of(factory.manufacturePojo(Trace.class).toBuilder() .projectId(null) .build()); - unexpectedTraces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + unexpectedTraces.forEach(trace -> create(trace, apiKey, workspaceName)); var filters = List.of(TraceFilter.builder() .field(TraceField.METADATA) @@ -1629,8 +1637,8 @@ void getByProjectName__whenFilterMetadataContainsNumber__thenReturnTracesFiltere @Test void getByProjectName__whenFilterMetadataContainsBoolean__thenReturnTracesFiltered() { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -1651,12 +1659,12 @@ void getByProjectName__whenFilterMetadataContainsBoolean__thenReturnTracesFilter .metadata(JsonUtils.getJsonNodeFromString("{\"model\":[{\"year\":true,\"version\":\"OpenAI, " + "Chat-GPT 4.0\"}]}")) .build()); - traces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + traces.forEach(trace -> create(trace, apiKey, workspaceName)); var expectedTraces = List.of(traces.getFirst()); var unexpectedTraces = List.of(factory.manufacturePojo(Trace.class).toBuilder() .projectId(null) .build()); - unexpectedTraces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + unexpectedTraces.forEach(trace -> create(trace, apiKey, workspaceName)); var filters = List.of(TraceFilter.builder() .field(TraceField.METADATA) @@ -1669,8 +1677,8 @@ void getByProjectName__whenFilterMetadataContainsBoolean__thenReturnTracesFilter @Test void getByProjectName__whenFilterMetadataContainsNull__thenReturnTracesFiltered() { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -1690,12 +1698,12 @@ void getByProjectName__whenFilterMetadataContainsNull__thenReturnTracesFiltered( .metadata(JsonUtils.getJsonNodeFromString("{\"model\":[{\"year\":null,\"version\":\"OpenAI, " + "Chat-GPT 4.0\"}]}")) .build()); - traces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + traces.forEach(trace -> create(trace, apiKey, workspaceName)); var expectedTraces = List.of(traces.getFirst()); var unexpectedTraces = List.of(factory.manufacturePojo(Trace.class).toBuilder() .projectId(null) .build()); - unexpectedTraces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + unexpectedTraces.forEach(trace -> create(trace, apiKey, workspaceName)); var filters = List.of(TraceFilter.builder() .field(TraceField.METADATA) @@ -1708,8 +1716,8 @@ void getByProjectName__whenFilterMetadataContainsNull__thenReturnTracesFiltered( @Test void getByProjectName__whenFilterMetadataGreaterThanNumber__thenReturnTracesFiltered() { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -1729,12 +1737,12 @@ void getByProjectName__whenFilterMetadataGreaterThanNumber__thenReturnTracesFilt .metadata(JsonUtils.getJsonNodeFromString("{\"model\":[{\"year\":2024,\"version\":\"OpenAI, " + "Chat-GPT 4.0\"}]}")) .build()); - traces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + traces.forEach(trace -> create(trace, apiKey, workspaceName)); var expectedTraces = List.of(traces.getFirst()); var unexpectedTraces = List.of(factory.manufacturePojo(Trace.class).toBuilder() .projectId(null) .build()); - unexpectedTraces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + unexpectedTraces.forEach(trace -> create(trace, apiKey, workspaceName)); var filters = List.of(TraceFilter.builder() .field(TraceField.METADATA) @@ -1747,8 +1755,8 @@ void getByProjectName__whenFilterMetadataGreaterThanNumber__thenReturnTracesFilt @Test void getByProjectName__whenFilterMetadataGreaterThanString__thenReturnTracesFiltered() { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -1765,12 +1773,12 @@ void getByProjectName__whenFilterMetadataGreaterThanString__thenReturnTracesFilt .feedbackScores(null) .build()) .collect(Collectors.toCollection(ArrayList::new)); - traces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + traces.forEach(trace -> create(trace, apiKey, workspaceName)); var expectedTraces = List.of(); var unexpectedTraces = List.of(factory.manufacturePojo(Trace.class).toBuilder() .projectId(null) .build()); - unexpectedTraces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + unexpectedTraces.forEach(trace -> create(trace, apiKey, workspaceName)); var filters = List.of(TraceFilter.builder() .field(TraceField.METADATA) @@ -1783,8 +1791,8 @@ void getByProjectName__whenFilterMetadataGreaterThanString__thenReturnTracesFilt @Test void getByProjectName__whenFilterMetadataGreaterThanBoolean__thenReturnTracesFiltered() { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -1801,12 +1809,12 @@ void getByProjectName__whenFilterMetadataGreaterThanBoolean__thenReturnTracesFil .feedbackScores(null) .build()) .collect(Collectors.toCollection(ArrayList::new)); - traces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + traces.forEach(trace -> create(trace, apiKey, workspaceName)); var expectedTraces = List.of(); var unexpectedTraces = List.of(factory.manufacturePojo(Trace.class).toBuilder() .projectId(null) .build()); - unexpectedTraces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + unexpectedTraces.forEach(trace -> create(trace, apiKey, workspaceName)); var filters = List.of(TraceFilter.builder() .field(TraceField.METADATA) @@ -1819,8 +1827,8 @@ void getByProjectName__whenFilterMetadataGreaterThanBoolean__thenReturnTracesFil @Test void getByProjectName__whenFilterMetadataGreaterThanNull__thenReturnTracesFiltered() { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -1837,12 +1845,12 @@ void getByProjectName__whenFilterMetadataGreaterThanNull__thenReturnTracesFilter .feedbackScores(null) .build()) .collect(Collectors.toCollection(ArrayList::new)); - traces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + traces.forEach(trace -> create(trace, apiKey, workspaceName)); var expectedTraces = List.of(); var unexpectedTraces = List.of(factory.manufacturePojo(Trace.class).toBuilder() .projectId(null) .build()); - unexpectedTraces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + unexpectedTraces.forEach(trace -> create(trace, apiKey, workspaceName)); var filters = List.of(TraceFilter.builder() .field(TraceField.METADATA) @@ -1855,8 +1863,8 @@ void getByProjectName__whenFilterMetadataGreaterThanNull__thenReturnTracesFilter @Test void getByProjectName__whenFilterMetadataLessThanNumber__thenReturnTracesFiltered() { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -1876,12 +1884,12 @@ void getByProjectName__whenFilterMetadataLessThanNumber__thenReturnTracesFiltere .metadata(JsonUtils.getJsonNodeFromString("{\"model\":[{\"year\":2024,\"version\":\"OpenAI, " + "Chat-GPT 4.0\"}]}")) .build()); - traces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + traces.forEach(trace -> create(trace, apiKey, workspaceName)); var expectedTraces = List.of(traces.getFirst()); var unexpectedTraces = List.of(factory.manufacturePojo(Trace.class).toBuilder() .projectId(null) .build()); - unexpectedTraces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + unexpectedTraces.forEach(trace -> create(trace, apiKey, workspaceName)); var filters = List.of(TraceFilter.builder() .field(TraceField.METADATA) @@ -1894,8 +1902,8 @@ void getByProjectName__whenFilterMetadataLessThanNumber__thenReturnTracesFiltere @Test void getByProjectName__whenFilterMetadataLessThanString__thenReturnTracesFiltered() { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -1912,12 +1920,12 @@ void getByProjectName__whenFilterMetadataLessThanString__thenReturnTracesFiltere .feedbackScores(null) .build()) .collect(Collectors.toCollection(ArrayList::new)); - traces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + traces.forEach(trace -> create(trace, apiKey, workspaceName)); var expectedTraces = List.of(); var unexpectedTraces = List.of(factory.manufacturePojo(Trace.class).toBuilder() .projectId(null) .build()); - unexpectedTraces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + unexpectedTraces.forEach(trace -> create(trace, apiKey, workspaceName)); var filters = List.of(TraceFilter.builder() .field(TraceField.METADATA) @@ -1930,8 +1938,8 @@ void getByProjectName__whenFilterMetadataLessThanString__thenReturnTracesFiltere @Test void getByProjectName__whenFilterMetadataLessThanBoolean__thenReturnTracesFiltered() { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -1948,12 +1956,12 @@ void getByProjectName__whenFilterMetadataLessThanBoolean__thenReturnTracesFilter .feedbackScores(null) .build()) .collect(Collectors.toCollection(ArrayList::new)); - traces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + traces.forEach(trace -> create(trace, apiKey, workspaceName)); var expectedTraces = List.of(); var unexpectedTraces = List.of(factory.manufacturePojo(Trace.class).toBuilder() .projectId(null) .build()); - unexpectedTraces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + unexpectedTraces.forEach(trace -> create(trace, apiKey, workspaceName)); var filters = List.of(TraceFilter.builder() .field(TraceField.METADATA) @@ -1966,8 +1974,8 @@ void getByProjectName__whenFilterMetadataLessThanBoolean__thenReturnTracesFilter @Test void getByProjectName__whenFilterMetadataLessThanNull__thenReturnTracesFiltered() { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -1984,12 +1992,12 @@ void getByProjectName__whenFilterMetadataLessThanNull__thenReturnTracesFiltered( .feedbackScores(null) .build()) .collect(Collectors.toCollection(ArrayList::new)); - traces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + traces.forEach(trace -> create(trace, apiKey, workspaceName)); var expectedTraces = List.of(); var unexpectedTraces = List.of(factory.manufacturePojo(Trace.class).toBuilder() .projectId(null) .build()); - unexpectedTraces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + unexpectedTraces.forEach(trace -> create(trace, apiKey, workspaceName)); var filters = List.of(TraceFilter.builder() .field(TraceField.METADATA) @@ -2002,8 +2010,8 @@ void getByProjectName__whenFilterMetadataLessThanNull__thenReturnTracesFiltered( @Test void getByProjectName__whenFilterTagsContains__thenReturnTracesFiltered() { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -2017,12 +2025,12 @@ void getByProjectName__whenFilterTagsContains__thenReturnTracesFiltered() { .feedbackScores(null) .build()) .collect(Collectors.toCollection(ArrayList::new)); - traces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + traces.forEach(trace -> create(trace, apiKey, workspaceName)); var expectedTraces = List.of(traces.getFirst()); var unexpectedTraces = List.of(factory.manufacturePojo(Trace.class).toBuilder() .projectId(null) .build()); - unexpectedTraces.forEach(trace -> TracesResourceTest.this.create(trace, apiKey, workspaceName)); + unexpectedTraces.forEach(trace -> create(trace, apiKey, workspaceName)); var filters = List.of(TraceFilter.builder() .field(TraceField.TAGS) @@ -2038,8 +2046,8 @@ void getByProjectName__whenFilterTagsContains__thenReturnTracesFiltered() { @Test void getByProjectName__whenFilterFeedbackScoresEqual__thenReturnTracesFiltered() { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -2061,14 +2069,14 @@ void getByProjectName__whenFilterFeedbackScoresEqual__thenReturnTracesFiltered() .feedbackScores( updateFeedbackScore(traces.get(1).feedbackScores(), traces.getFirst().feedbackScores(), 2)) .build()); - traces.forEach(trace1 -> TracesResourceTest.this.create(trace1, apiKey, workspaceName)); + traces.forEach(trace1 -> create(trace1, apiKey, workspaceName)); traces.forEach(trace -> trace.feedbackScores() .forEach(feedbackScore -> create(trace.id(), feedbackScore, workspaceName, apiKey))); var expectedTraces = List.of(traces.getFirst()); var unexpectedTraces = List.of(factory.manufacturePojo(Trace.class).toBuilder() .projectId(null) .build()); - unexpectedTraces.forEach(trace1 -> TracesResourceTest.this.create(trace1, apiKey, workspaceName)); + unexpectedTraces.forEach(trace1 -> create(trace1, apiKey, workspaceName)); unexpectedTraces.forEach( trace -> trace.feedbackScores() .forEach(feedbackScore -> create(trace.id(), feedbackScore, workspaceName, apiKey))); @@ -2091,8 +2099,8 @@ void getByProjectName__whenFilterFeedbackScoresEqual__thenReturnTracesFiltered() @Test void getByProjectName__whenFilterFeedbackScoresGreaterThan__thenReturnTracesFiltered() { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -2113,14 +2121,14 @@ void getByProjectName__whenFilterFeedbackScoresGreaterThan__thenReturnTracesFilt traces.set(0, traces.getFirst().toBuilder() .feedbackScores(updateFeedbackScore(traces.getFirst().feedbackScores(), 2, 2345.6789)) .build()); - traces.forEach(trace1 -> TracesResourceTest.this.create(trace1, apiKey, workspaceName)); + traces.forEach(trace1 -> create(trace1, apiKey, workspaceName)); traces.forEach(trace -> trace.feedbackScores() .forEach(feedbackScore -> create(trace.id(), feedbackScore, workspaceName, apiKey))); var expectedTraces = List.of(traces.getFirst()); var unexpectedTraces = List.of(factory.manufacturePojo(Trace.class).toBuilder() .projectId(null) .build()); - unexpectedTraces.forEach(trace1 -> TracesResourceTest.this.create(trace1, apiKey, workspaceName)); + unexpectedTraces.forEach(trace1 -> create(trace1, apiKey, workspaceName)); unexpectedTraces.forEach( trace -> trace.feedbackScores() .forEach(feedbackScore -> create(trace.id(), feedbackScore, workspaceName, apiKey))); @@ -2142,8 +2150,8 @@ void getByProjectName__whenFilterFeedbackScoresGreaterThan__thenReturnTracesFilt @Test void getByProjectName__whenFilterFeedbackScoresGreaterThanEqual__thenReturnTracesFiltered() { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -2164,14 +2172,14 @@ void getByProjectName__whenFilterFeedbackScoresGreaterThanEqual__thenReturnTrace traces.set(0, traces.getFirst().toBuilder() .feedbackScores(updateFeedbackScore(traces.getFirst().feedbackScores(), 2, 2345.6789)) .build()); - traces.forEach(trace1 -> TracesResourceTest.this.create(trace1, apiKey, workspaceName)); + traces.forEach(trace1 -> create(trace1, apiKey, workspaceName)); traces.forEach(trace -> trace.feedbackScores() .forEach(feedbackScore -> create(trace.id(), feedbackScore, workspaceName, apiKey))); var expectedTraces = List.of(traces.getFirst()); var unexpectedTraces = List.of(factory.manufacturePojo(Trace.class).toBuilder() .projectId(null) .build()); - unexpectedTraces.forEach(trace1 -> TracesResourceTest.this.create(trace1, apiKey, workspaceName)); + unexpectedTraces.forEach(trace1 -> create(trace1, apiKey, workspaceName)); unexpectedTraces.forEach( trace -> trace.feedbackScores() .forEach(feedbackScore -> create(trace.id(), feedbackScore, workspaceName, apiKey))); @@ -2188,8 +2196,8 @@ void getByProjectName__whenFilterFeedbackScoresGreaterThanEqual__thenReturnTrace @Test void getByProjectName__whenFilterFeedbackScoresLessThan__thenReturnTracesFiltered() { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -2210,14 +2218,14 @@ void getByProjectName__whenFilterFeedbackScoresLessThan__thenReturnTracesFiltere traces.set(0, traces.getFirst().toBuilder() .feedbackScores(updateFeedbackScore(traces.getFirst().feedbackScores(), 2, 1234.5678)) .build()); - traces.forEach(trace1 -> TracesResourceTest.this.create(trace1, apiKey, workspaceName)); + traces.forEach(trace1 -> create(trace1, apiKey, workspaceName)); traces.forEach(trace -> trace.feedbackScores() .forEach(feedbackScore -> create(trace.id(), feedbackScore, workspaceName, apiKey))); var expectedTraces = List.of(traces.getFirst()); var unexpectedTraces = List.of(factory.manufacturePojo(Trace.class).toBuilder() .projectId(null) .build()); - unexpectedTraces.forEach(trace1 -> TracesResourceTest.this.create(trace1, apiKey, workspaceName)); + unexpectedTraces.forEach(trace1 -> create(trace1, apiKey, workspaceName)); unexpectedTraces.forEach( trace -> trace.feedbackScores() .forEach(feedbackScore -> create(trace.id(), feedbackScore, workspaceName, apiKey))); @@ -2235,8 +2243,8 @@ void getByProjectName__whenFilterFeedbackScoresLessThan__thenReturnTracesFiltere @Test void getByProjectName__whenFilterFeedbackScoresLessThanEqual__thenReturnTracesFiltered() { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -2257,14 +2265,14 @@ void getByProjectName__whenFilterFeedbackScoresLessThanEqual__thenReturnTracesFi traces.set(0, traces.getFirst().toBuilder() .feedbackScores(updateFeedbackScore(traces.getFirst().feedbackScores(), 2, 1234.5678)) .build()); - traces.forEach(trace1 -> TracesResourceTest.this.create(trace1, apiKey, workspaceName)); + traces.forEach(trace1 -> create(trace1, apiKey, workspaceName)); traces.forEach(trace -> trace.feedbackScores() .forEach(feedbackScore -> create(trace.id(), feedbackScore, workspaceName, apiKey))); var expectedTraces = List.of(traces.getFirst()); var unexpectedTraces = List.of(factory.manufacturePojo(Trace.class).toBuilder() .projectId(null) .build()); - unexpectedTraces.forEach(trace1 -> TracesResourceTest.this.create(trace1, apiKey, workspaceName)); + unexpectedTraces.forEach(trace1 -> create(trace1, apiKey, workspaceName)); unexpectedTraces.forEach( trace -> trace.feedbackScores() .forEach(feedbackScore -> create(trace.id(), feedbackScore, workspaceName, apiKey))); @@ -2621,8 +2629,8 @@ static Stream getByProjectName__whenFilterInvalidValueOrKeyForFieldType_ @ParameterizedTest @MethodSource void getByProjectName__whenFilterInvalidValueOrKeyForFieldType__thenReturn400(Filter filter) { - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -2651,7 +2659,6 @@ void getByProjectName__whenFilterInvalidValueOrKeyForFieldType__thenReturn400(Fi assertThat(actualError).isEqualTo(expectedError); } } - private void getAndAssertPage(String workspaceName, String projectName, List filters, List traces, List expectedTraces, List unexpectedTraces, String apiKey) { @@ -2682,17 +2689,87 @@ private void getAndAssertPage(int page, int size, String projectName, List filters, + List spans, + List expectedSpans, + List unexpectedSpans, String apiKey) { + int page = 1; + int size = spans.size() + expectedSpans.size() + unexpectedSpans.size(); + getAndAssertPageSpans( + workspaceName, + projectName, + null, + null, + null, + filters, + page, + size, + expectedSpans, + expectedSpans.size(), + unexpectedSpans, apiKey); + } + + private void getAndAssertPageSpans( + String workspaceName, + String projectName, + UUID projectId, + UUID traceId, + SpanType type, + List filters, + int page, + int size, + List expectedSpans, + int expectedTotal, + List unexpectedSpans, String apiKey) { + try (var actualResponse = client.target(URL_TEMPLATE_SPANS.formatted(baseURI)) + .queryParam("page", page) + .queryParam("size", size) + .queryParam("project_name", projectName) + .queryParam("project_id", projectId) + .queryParam("trace_id", traceId) + .queryParam("type", type) + .queryParam("filters", toURLEncodedQueryParam(filters)) + .request() + .header(HttpHeaders.AUTHORIZATION, apiKey) + .header(WORKSPACE_HEADER, workspaceName) + .get()) { + var actualPage = actualResponse.readEntity(Span.SpanPage.class); + var actualSpans = actualPage.content(); + + assertThat(actualResponse.getStatusInfo().getStatusCode()).isEqualTo(200); + + assertThat(actualPage.page()).isEqualTo(page); + assertThat(actualPage.size()).isEqualTo(expectedSpans.size()); + assertThat(actualPage.total()).isEqualTo(expectedTotal); + + assertThat(actualSpans.size()).isEqualTo(expectedSpans.size()); + assertThat(actualSpans) + .usingRecursiveFieldByFieldElementComparatorIgnoringFields(IGNORED_FIELDS_SPANS) + .containsExactlyElementsOf(expectedSpans); + assertIgnoredFieldsSpans(actualSpans, expectedSpans); + + if (!unexpectedSpans.isEmpty()) { + assertThat(actualSpans) + .usingRecursiveFieldByFieldElementComparatorIgnoringFields(IGNORED_FIELDS_SPANS) + .doesNotContainAnyElementsOf(unexpectedSpans); + } + } + } + private String toURLEncodedQueryParam(List filters) { return URLEncoder.encode(JsonUtils.writeValueAsString(filters), StandardCharsets.UTF_8); } @@ -2701,9 +2778,6 @@ private void assertIgnoredFields(List actualTraces, List expectedT for (int i = 0; i < actualTraces.size(); i++) { var actualTrace = actualTraces.get(i); var expectedTrace = expectedTraces.get(i); - var expectedFeedbackScores = expectedTrace.feedbackScores() == null - ? null - : expectedTrace.feedbackScores().reversed(); assertThat(actualTrace.projectId()).isNotNull(); assertThat(actualTrace.projectName()).isNull(); assertThat(actualTrace.createdAt()).isAfter(expectedTrace.createdAt()); @@ -2711,12 +2785,11 @@ private void assertIgnoredFields(List actualTraces, List expectedT assertThat(actualTrace.lastUpdatedBy()).isEqualTo(USER); assertThat(actualTrace.lastUpdatedBy()).isEqualTo(USER); assertThat(actualTrace.feedbackScores()) - .usingRecursiveComparison( - RecursiveComparisonConfiguration.builder() - .withComparatorForType(BigDecimal::compareTo, BigDecimal.class) - .withIgnoredFields(IGNORED_FIELDS) - .build()) - .isEqualTo(expectedFeedbackScores); + .usingRecursiveComparison() + .withComparatorForType(BigDecimal::compareTo, BigDecimal.class) + .ignoringFields(IGNORED_FIELDS_SCORES) + .ignoringCollectionOrder() + .isEqualTo(expectedTrace.feedbackScores()); if (expectedTrace.feedbackScores() != null) { actualTrace.feedbackScores().forEach(feedbackScore -> { @@ -2729,6 +2802,32 @@ private void assertIgnoredFields(List actualTraces, List expectedT } } + private void assertIgnoredFieldsSpans(List actualSpans, List expectedSpans) { + for (int i = 0; i < actualSpans.size(); i++) { + var actualSpan = actualSpans.get(i); + var expectedSpan = expectedSpans.get(i); + assertThat(actualSpan.projectId()).isNotNull(); + assertThat(actualSpan.projectName()).isNull(); + assertThat(actualSpan.createdAt()).isAfter(expectedSpan.createdAt()); + assertThat(actualSpan.lastUpdatedAt()).isAfter(expectedSpan.lastUpdatedAt()); + assertThat(actualSpan.feedbackScores()) + .usingRecursiveComparison() + .withComparatorForType(BigDecimal::compareTo, BigDecimal.class) + .ignoringFields(IGNORED_FIELDS_SCORES) + .ignoringCollectionOrder() + .isEqualTo(expectedSpan.feedbackScores()); + + if (actualSpan.feedbackScores() != null) { + actualSpan.feedbackScores().forEach(feedbackScore -> { + assertThat(feedbackScore.createdAt()).isAfter(expectedSpan.createdAt()); + assertThat(feedbackScore.lastUpdatedAt()).isAfter(expectedSpan.lastUpdatedAt()); + assertThat(feedbackScore.createdBy()).isEqualTo(USER); + assertThat(feedbackScore.lastUpdatedBy()).isEqualTo(USER); + }); + } + } + } + private List updateFeedbackScore(List feedbackScores, int index, double val) { feedbackScores.set(index, feedbackScores.get(index).toBuilder() .value(BigDecimal.valueOf(val)) @@ -2751,8 +2850,8 @@ class GetTrace { @DisplayName("Success") void getTrace() { - String projectName = generator.generate().toString(); - Trace trace = factory.manufacturePojo(Trace.class) + var projectName = generator.generate().toString(); + var trace = factory.manufacturePojo(Trace.class) .toBuilder() .id(null) .name("OpenAPI Trace") @@ -2799,20 +2898,8 @@ void getTrace() { @Test @DisplayName("when trace does not exist, then return not found") void getTrace__whenTraceDoesNotExist__thenReturnNotFound() { - - UUID id = generator.generate(); - - Response actualResponse = client.target(URL_TEMPLATE.formatted(baseURI)) - .path(id.toString()) - .request() - .header(HttpHeaders.AUTHORIZATION, API_KEY) - .header(WORKSPACE_HEADER, TEST_WORKSPACE) - .get(); - - assertThat(actualResponse.getStatusInfo().getStatusCode()).isEqualTo(404); - assertThat(actualResponse.hasEntity()).isTrue(); - assertThat(actualResponse.readEntity(ErrorMessage.class).errors()) - .allMatch(error -> Pattern.matches("Trace not found", error)); + var id = generator.generate(); + getAndAssertTraceNotFound(id, API_KEY, TEST_WORKSPACE); } } @@ -2825,8 +2912,14 @@ private UUID create(Trace trace, String apiKey, String workspaceName) { .post(Entity.json(trace))) { assertThat(actualResponse.getStatusInfo().getStatusCode()).isEqualTo(201); + assertThat(actualResponse.hasEntity()).isFalse(); - return TestUtils.getIdFromLocation(actualResponse.getLocation()); + var actualId = TestUtils.getIdFromLocation(actualResponse.getLocation()); + + if (trace.id() != null) { + assertThat(actualId).isEqualTo(trace.id()); + } + return actualId; } } @@ -2844,21 +2937,19 @@ private void create(UUID entityId, FeedbackScore score, String workspaceName, St } } - private Trace getAndAssert(Trace trace, UUID id, UUID projectId, Instant initialTime, String apiKey, - String workspaceName) { + private Trace getAndAssert(Trace trace, UUID projectId, String apiKey, String workspaceName) { - var actualResponse = getById(id, workspaceName, apiKey); + var actualResponse = getById(trace.id(), workspaceName, apiKey); var actualEntity = actualResponse.readEntity(Trace.class); assertThat(actualEntity) .usingRecursiveComparison( RecursiveComparisonConfiguration.builder() - .withIgnoredFields(IGNORED_FIELDS_LIST) + .withIgnoredFields(IGNORED_FIELDS_TRACES) .withComparatorForType(BigDecimal::compareTo, BigDecimal.class) .build()) - .isEqualTo(actualEntity); + .isEqualTo(trace); - assertThat(actualEntity.id()).isEqualTo(id); assertThat(actualEntity.name()).isEqualTo(trace.name()); assertThat(actualEntity.projectId()).isEqualTo(projectId); assertThat(actualEntity.input()).isEqualTo(trace.input()); @@ -2868,12 +2959,26 @@ private Trace getAndAssert(Trace trace, UUID id, UUID projectId, Instant initial assertThat(actualEntity.endTime()).isEqualTo(trace.endTime()); assertThat(actualEntity.startTime()).isEqualTo(trace.startTime()); - assertThat(actualEntity.createdAt()).isBetween(initialTime, Instant.now()); - assertThat(actualEntity.lastUpdatedAt()).isBetween(initialTime, Instant.now()); + assertThat(actualEntity.createdAt()).isAfter(trace.createdAt()); + assertThat(actualEntity.lastUpdatedAt()).isAfter(trace.lastUpdatedAt()); return actualEntity; } + private void getAndAssertTraceNotFound(UUID id, String apiKey, String testWorkspace) { + var actualResponse = client.target(URL_TEMPLATE.formatted(baseURI)) + .path(id.toString()) + .request() + .header(HttpHeaders.AUTHORIZATION, apiKey) + .header(WORKSPACE_HEADER, testWorkspace) + .get(); + + assertThat(actualResponse.getStatusInfo().getStatusCode()).isEqualTo(404); + assertThat(actualResponse.hasEntity()).isTrue(); + assertThat(actualResponse.readEntity(ErrorMessage.class).errors()) + .allMatch(error -> Pattern.matches("Trace not found", error)); + } + @Nested @DisplayName("Create:") @TestInstance(TestInstance.Lifecycle.PER_CLASS) @@ -2881,11 +2986,11 @@ class CreateTrace { @Test @DisplayName("Success") - void create() { + void createTrace() { - UUID id = generator.generate(); + var id = generator.generate(); - Trace trace = Trace.builder() + var trace = factory.manufacturePojo(Trace.class).toBuilder() .id(id) .name("OpenAPI traces") .projectName(DEFAULT_PROJECT) @@ -2897,8 +3002,6 @@ void create() { .tags(Set.of("tag1", "tag2")) .build(); - Instant now = Instant.now(); - try (var actualResponse = client.target(URL_TEMPLATE.formatted(baseURI)).request() .accept(MediaType.APPLICATION_JSON_TYPE) .header(HttpHeaders.AUTHORIZATION, API_KEY) @@ -2907,12 +3010,13 @@ void create() { assertThat(actualResponse.getStatusInfo().getStatusCode()).isEqualTo(201); assertThat(actualResponse.hasEntity()).isFalse(); - assertThat(actualResponse.getHeaderString("Location")).matches(Pattern.compile(URL_PATTERN)); + var actualId = TestUtils.getIdFromLocation(actualResponse.getLocation()); + assertThat(actualId).isEqualTo(id); } - UUID projectId = getProjectId(trace.projectName(), TEST_WORKSPACE, API_KEY); + var projectId = getProjectId(trace.projectName(), TEST_WORKSPACE, API_KEY); - getAndAssert(trace, id, projectId, now, API_KEY, TEST_WORKSPACE); + getAndAssert(trace, projectId, API_KEY, TEST_WORKSPACE); } @Test @@ -2923,81 +3027,49 @@ void create__whenCreatingTracesWithDifferentWorkspacesNames__thenReturnCreatedTr var trace1 = factory.manufacturePojo(Trace.class) .toBuilder() - .id(null) .projectName(DEFAULT_PROJECT) .build(); var trace2 = factory.manufacturePojo(Trace.class) .toBuilder() - .id(null) .projectName(projectName) .build(); - var createdTrace1 = Instant.now(); - UUID id1 = TracesResourceTest.this.create(trace1, API_KEY, TEST_WORKSPACE); - - var createdTrace2 = Instant.now(); - UUID id2 = TracesResourceTest.this.create(trace2, API_KEY, TEST_WORKSPACE); + create(trace1, API_KEY, TEST_WORKSPACE); + create(trace2, API_KEY, TEST_WORKSPACE); UUID projectId1 = getProjectId(DEFAULT_PROJECT, TEST_WORKSPACE, API_KEY); UUID projectId2 = getProjectId(projectName, TEST_WORKSPACE, API_KEY); - getAndAssert(trace1, id1, projectId1, createdTrace1, API_KEY, TEST_WORKSPACE); - getAndAssert(trace2, id2, projectId2, createdTrace2, API_KEY, TEST_WORKSPACE); + getAndAssert(trace1, projectId1, API_KEY, TEST_WORKSPACE); + getAndAssert(trace2, projectId2, API_KEY, TEST_WORKSPACE); } @Test - @DisplayName("when id comes from client, then accept and use id") - void create__whenIdComesFromClient__thenAcceptAndUseId() { - - var traceId = generator.generate(); - - try (var actualResponse = client.target(URL_TEMPLATE.formatted(baseURI)).request() - .accept(MediaType.APPLICATION_JSON_TYPE) - .header(HttpHeaders.AUTHORIZATION, API_KEY) - .header(WORKSPACE_HEADER, TEST_WORKSPACE) - .post(Entity.json( - Trace.builder() - .id(traceId) - .name("OpenAPI traces") - .projectName(UUID.randomUUID().toString()) - .input(JsonUtils.getJsonNodeFromString("{ \"input\": \"data\"}")) - .output(JsonUtils.getJsonNodeFromString("{ \"output\": \"data\"}")) - .endTime(Instant.now()) - .startTime(Instant.now().minusSeconds(10)) - .metadata(JsonUtils.getJsonNodeFromString("{ \"metadata\": \"data\"}")) - .tags(Set.of("tag1", "tag2")) - .build()))) { - - assertThat(actualResponse.getStatusInfo().getStatusCode()).isEqualTo(201); - - UUID actualId = TestUtils.getIdFromLocation(actualResponse.getLocation()); + void createWithMissingId() { + var trace = factory.manufacturePojo(Trace.class).toBuilder() + .id(null) + .build(); + var id = create(trace, API_KEY, TEST_WORKSPACE); - assertThat(actualId).isEqualTo(traceId); - } + trace = trace.toBuilder().id(id).build(); + var projectId = getProjectId(trace.projectName(), TEST_WORKSPACE, API_KEY); + getAndAssert(trace, projectId, API_KEY, TEST_WORKSPACE); } @Test @DisplayName("when project doesn't exist, then accept and create project") void create__whenProjectDoesNotExist__thenAcceptAndCreateProject() { - String workspaceName = generator.generate().toString(); - String projectName = generator.generate().toString(); - + var workspaceName = generator.generate().toString(); + var projectName = generator.generate().toString(); + var trace = factory.manufacturePojo(Trace.class).toBuilder() + .projectName(projectName) + .build(); try (var actualResponse = client.target(URL_TEMPLATE.formatted(baseURI)).request() .accept(MediaType.APPLICATION_JSON_TYPE) .header(HttpHeaders.AUTHORIZATION, API_KEY) .header(WORKSPACE_HEADER, TEST_WORKSPACE) - .post(Entity.json( - Trace.builder() - .name("OpenAPI traces") - .projectName(projectName) - .input(JsonUtils.getJsonNodeFromString("{ \"input\": \"data\"}")) - .output(JsonUtils.getJsonNodeFromString("{ \"output\": \"data\"}")) - .endTime(Instant.now()) - .startTime(Instant.now().minusSeconds(10)) - .metadata(JsonUtils.getJsonNodeFromString("{ \"metadata\": \"data\"}")) - .tags(Set.of("tag1", "tag2")) - .build()))) { + .post(Entity.json(trace))) { assertThat(actualResponse.getStatusInfo().getStatusCode()).isEqualTo(201); } @@ -3020,15 +3092,9 @@ void create__whenProjectNameIsNull__thenAcceptAndUseDefaultProject() { var id = generator.generate(); - Trace trace = Trace.builder() + var trace = factory.manufacturePojo(Trace.class).toBuilder() .id(id) - .name("OpenAPI traces") - .input(JsonUtils.getJsonNodeFromString("{ \"input\": \"data\"}")) - .output(JsonUtils.getJsonNodeFromString("{ \"output\": \"data\"}")) - .endTime(Instant.now()) - .startTime(Instant.now().minusSeconds(10)) - .metadata(JsonUtils.getJsonNodeFromString("{ \"metadata\": \"data\"}")) - .tags(Set.of("tag1", "tag2")) + .projectName(null) .build(); try (var actualResponse = client.target(URL_TEMPLATE.formatted(baseURI)).request() @@ -3059,9 +3125,9 @@ class BatchInsert { @Test void batch__whenCreateTraces__thenReturnNoContent() { - String projectName = UUID.randomUUID().toString(); + var projectName = UUID.randomUUID().toString(); - UUID projectId = createProject(projectName, TEST_WORKSPACE, API_KEY); + var projectId = createProject(projectName, TEST_WORKSPACE, API_KEY); var expectedTraces = IntStream.range(0, 1000) .mapToObj(i -> factory.manufacturePojo(Trace.class).toBuilder() @@ -3072,7 +3138,7 @@ void batch__whenCreateTraces__thenReturnNoContent() { .build()) .toList(); - batchCreateAndAssert(expectedTraces, API_KEY, TEST_WORKSPACE); + batchCreateTracesAndAssert(expectedTraces, API_KEY, TEST_WORKSPACE); getAndAssertPage(TEST_WORKSPACE, projectName, List.of(), List.of(), expectedTraces.reversed(), List.of(), API_KEY); @@ -3098,7 +3164,7 @@ void batch__whenSendingMultipleTracesWithSameId__thenReturn422() { .request() .header(HttpHeaders.AUTHORIZATION, API_KEY) .header(WORKSPACE_HEADER, TEST_WORKSPACE) - .post(Entity.json(new TraceBatch(traces)))) { + .post(Entity.json(TraceBatch.builder().traces(traces).build()))) { assertThat(actualResponse.getStatusInfo().getStatusCode()).isEqualTo(422); assertThat(actualResponse.hasEntity()).isTrue(); @@ -3117,7 +3183,7 @@ void batch__whenBatchIsInvalid__thenReturn422(List traces, String errorMe .request() .header(HttpHeaders.AUTHORIZATION, API_KEY) .header(WORKSPACE_HEADER, TEST_WORKSPACE) - .post(Entity.json(new TraceBatch(traces)))) { + .post(Entity.json(TraceBatch.builder().traces(traces).build()))) { assertThat(actualResponse.getStatusInfo().getStatusCode()).isEqualTo(422); assertThat(actualResponse.hasEntity()).isTrue(); @@ -3154,23 +3220,36 @@ void batch__whenSendingMultipleTracesWithNoId__thenReturnNoContent() { List expectedTraces = List.of(newTrace, expectedTrace); - batchCreateAndAssert(expectedTraces, API_KEY, TEST_WORKSPACE); + batchCreateTracesAndAssert(expectedTraces, API_KEY, TEST_WORKSPACE); } + } - private void batchCreateAndAssert(List traces, String apiKey, String workspaceName) { + private void batchCreateTracesAndAssert(List traces, String apiKey, String workspaceName) { - try (var actualResponse = client.target(URL_TEMPLATE.formatted(baseURI)) - .path("batch") - .request() - .header(HttpHeaders.AUTHORIZATION, apiKey) - .header(WORKSPACE_HEADER, workspaceName) - .post(Entity.json(new TraceBatch(traces)))) { + try (var actualResponse = client.target(URL_TEMPLATE.formatted(baseURI)) + .path("batch") + .request() + .header(HttpHeaders.AUTHORIZATION, apiKey) + .header(WORKSPACE_HEADER, workspaceName) + .post(Entity.json(TraceBatch.builder().traces(traces).build()))) { - assertThat(actualResponse.getStatusInfo().getStatusCode()).isEqualTo(204); - assertThat(actualResponse.hasEntity()).isFalse(); - } + assertThat(actualResponse.getStatusInfo().getStatusCode()).isEqualTo(204); + assertThat(actualResponse.hasEntity()).isFalse(); } + } + + private void batchCreateSpansAndAssert(List expectedSpans, String apiKey, String workspaceName) { + + try (var actualResponse = client.target(URL_TEMPLATE_SPANS.formatted(baseURI)) + .path("batch") + .request() + .header(HttpHeaders.AUTHORIZATION, apiKey) + .header(WORKSPACE_HEADER, workspaceName) + .post(Entity.json(SpanBatch.builder().spans(expectedSpans).build()))) { + assertThat(actualResponse.getStatusInfo().getStatusCode()).isEqualTo(204); + assertThat(actualResponse.hasEntity()).isFalse(); + } } @Nested @@ -3181,63 +3260,352 @@ class DeleteTrace { @Test @DisplayName("Success") void delete() { - Trace trace = factory.manufacturePojo(Trace.class) - .toBuilder() - .id(null) - .endTime(null) - .output(null) - .createdAt(null) - .lastUpdatedAt(null) - .metadata(null) - .tags(null) - .build(); + var apiKey = UUID.randomUUID().toString(); + var workspaceName = RandomStringUtils.randomAlphanumeric(10); + var workspaceId = UUID.randomUUID().toString(); + mockTargetWorkspace(apiKey, workspaceName, workspaceId); - var id = create(trace, API_KEY, TEST_WORKSPACE); + var projectName = RandomStringUtils.randomAlphanumeric(10); - try (var actualResponse = client.target(URL_TEMPLATE.formatted(baseURI)) - .path(id.toString()) - .request() - .header(HttpHeaders.AUTHORIZATION, API_KEY) - .header(WORKSPACE_HEADER, TEST_WORKSPACE) - .delete()) { + var traces = List.of(factory.manufacturePojo(Trace.class).toBuilder() + .projectName(projectName) + .build()); + batchCreateTracesAndAssert(traces, apiKey, workspaceName); - assertThat(actualResponse.getStatusInfo().getStatusCode()).isEqualTo(204); - assertThat(actualResponse.hasEntity()).isFalse(); - } - } + var spans = traces.stream() + .flatMap(trace -> PodamFactoryUtils.manufacturePojoList(factory, Span.class).stream() + .map(span -> span.toBuilder() + .projectName(projectName) + .traceId(trace.id()) + .build())) + .toList(); + batchCreateSpansAndAssert(spans, apiKey, workspaceName); - @Test - @DisplayName("when trace does not exist, then return no content") - void delete__whenTraceDoesNotExist__thenReturnNotFound() { + var traceScores = traces.stream() + .flatMap(trace -> trace.feedbackScores().stream() + .map(item -> FeedbackScoreMapper.INSTANCE.toFeedbackScoreBatchItem( + trace.id(), projectName, item))) + .toList(); + createAndAssertForTrace(FeedbackScoreBatch.builder().scores(traceScores).build(), workspaceName, apiKey); - UUID id = generator.generate(); + var spanScores = spans.stream() + .flatMap(span -> span.feedbackScores().stream() + .map(item -> FeedbackScoreMapper.INSTANCE.toFeedbackScoreBatchItem( + span.id(), projectName, item))) + .toList(); + createAndAssertForSpan(FeedbackScoreBatch.builder().scores(spanScores).build(), workspaceName, apiKey); - try (var actualResponse = client.target(URL_TEMPLATE.formatted(baseURI)).path(id.toString()) - .request() - .header(HttpHeaders.AUTHORIZATION, API_KEY) - .header(WORKSPACE_HEADER, TEST_WORKSPACE) - .delete()) { + getAndAssertPage(workspaceName, projectName, List.of(), traces, traces.reversed(), List.of(), apiKey); + getAndAssertPageSpans(workspaceName, projectName, List.of(), spans, spans.reversed(), List.of(), apiKey); - assertThat(actualResponse.getStatusInfo().getStatusCode()).isEqualTo(204); - assertThat(actualResponse.hasEntity()).isFalse(); - } + deleteAndAssert(traces.getFirst().id(), workspaceName, apiKey); + + getAndAssertPage(workspaceName, projectName, List.of(), traces, List.of(), List.of(), apiKey); + getAndAssertPageSpans(workspaceName, projectName, List.of(), spans, List.of(), List.of(), apiKey); } - } + @Test + void deleteWithoutSpansScores() { + var apiKey = UUID.randomUUID().toString(); + var workspaceName = RandomStringUtils.randomAlphanumeric(10); + var workspaceId = UUID.randomUUID().toString(); + mockTargetWorkspace(apiKey, workspaceName, workspaceId); - @Nested - @DisplayName("Update:") - @TestInstance(TestInstance.Lifecycle.PER_CLASS) - class UpdateTrace { + var projectName = RandomStringUtils.randomAlphanumeric(10); - private Trace trace; - private UUID id; + var traces = List.of(factory.manufacturePojo(Trace.class).toBuilder() + .projectName(projectName) + .build()); + batchCreateTracesAndAssert(traces, apiKey, workspaceName); + + var spans = traces.stream() + .flatMap(trace -> PodamFactoryUtils.manufacturePojoList(factory, Span.class).stream() + .map(span -> span.toBuilder() + .projectName(projectName) + .traceId(trace.id()) + .feedbackScores(null) + .build())) + .toList(); + batchCreateSpansAndAssert(spans, apiKey, workspaceName); + + var traceScores = traces.stream() + .flatMap(trace -> trace.feedbackScores().stream() + .map(item -> FeedbackScoreMapper.INSTANCE.toFeedbackScoreBatchItem( + trace.id(), projectName, item))) + .toList(); + createAndAssertForTrace(FeedbackScoreBatch.builder().scores(traceScores).build(), workspaceName, apiKey); + + getAndAssertPage(workspaceName, projectName, List.of(), traces, traces.reversed(), List.of(), apiKey); + getAndAssertPageSpans(workspaceName, projectName, List.of(), spans, spans.reversed(), List.of(), apiKey); + + deleteAndAssert(traces.getFirst().id(), workspaceName, apiKey); + + getAndAssertPage(workspaceName, projectName, List.of(), traces, List.of(), List.of(), apiKey); + getAndAssertPageSpans(workspaceName, projectName, List.of(), spans, List.of(), List.of(), apiKey); + } + + @Test + void deleteWithoutScores() { + var apiKey = UUID.randomUUID().toString(); + var workspaceName = RandomStringUtils.randomAlphanumeric(10); + var workspaceId = UUID.randomUUID().toString(); + mockTargetWorkspace(apiKey, workspaceName, workspaceId); + + var projectName = RandomStringUtils.randomAlphanumeric(10); + + var traces = List.of(factory.manufacturePojo(Trace.class).toBuilder() + .projectName(projectName) + .feedbackScores(null) + .build()); + batchCreateTracesAndAssert(traces, apiKey, workspaceName); + + var spans = traces.stream() + .flatMap(trace -> PodamFactoryUtils.manufacturePojoList(factory, Span.class).stream() + .map(span -> span.toBuilder() + .projectName(projectName) + .traceId(trace.id()) + .feedbackScores(null) + .build())) + .toList(); + batchCreateSpansAndAssert(spans, apiKey, workspaceName); + + getAndAssertPage(workspaceName, projectName, List.of(), traces, traces.reversed(), List.of(), apiKey); + getAndAssertPageSpans(workspaceName, projectName, List.of(), spans, spans.reversed(), List.of(), apiKey); + + deleteAndAssert(traces.getFirst().id(), workspaceName, apiKey); + + getAndAssertPage(workspaceName, projectName, List.of(), traces, List.of(), List.of(), apiKey); + getAndAssertPageSpans(workspaceName, projectName, List.of(), spans, List.of(), List.of(), apiKey); + } + + @Test + void deleteWithoutSpans() { + var apiKey = UUID.randomUUID().toString(); + var workspaceName = RandomStringUtils.randomAlphanumeric(10); + var workspaceId = UUID.randomUUID().toString(); + mockTargetWorkspace(apiKey, workspaceName, workspaceId); + + var projectName = RandomStringUtils.randomAlphanumeric(10); + + var traces = List.of(factory.manufacturePojo(Trace.class).toBuilder() + .projectName(projectName) + .feedbackScores(null) + .build()); + batchCreateTracesAndAssert(traces, apiKey, workspaceName); + + getAndAssertPage(workspaceName, projectName, List.of(), traces, traces.reversed(), List.of(), apiKey); + + deleteAndAssert(traces.getFirst().id(), workspaceName, apiKey); + + getAndAssertPage(workspaceName, projectName, List.of(), traces, List.of(), List.of(), apiKey); + } + + @Test + @DisplayName("when trace does not exist, then return no content") + void delete__whenTraceDoesNotExist__thenNoContent() { + var apiKey = UUID.randomUUID().toString(); + var workspaceName = RandomStringUtils.randomAlphanumeric(10); + var workspaceId = UUID.randomUUID().toString(); + mockTargetWorkspace(apiKey, workspaceName, workspaceId); + + var id = generator.generate(); + + deleteAndAssert(id, workspaceName, apiKey); + + getAndAssertTraceNotFound(id, apiKey, workspaceName); + } + } + + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + class DeleteTraces { + + @Test + void deleteTraces() { + var apiKey = UUID.randomUUID().toString(); + var workspaceName = RandomStringUtils.randomAlphanumeric(10); + var workspaceId = UUID.randomUUID().toString(); + mockTargetWorkspace(apiKey, workspaceName, workspaceId); + + var projectName = RandomStringUtils.randomAlphanumeric(10); + + var traces = PodamFactoryUtils.manufacturePojoList(factory, Trace.class).stream() + .map(trace -> trace.toBuilder() + .projectName(projectName) + .build()) + .toList(); + batchCreateTracesAndAssert(traces, apiKey, workspaceName); + + var spans = traces.stream() + .flatMap(trace -> PodamFactoryUtils.manufacturePojoList(factory, Span.class).stream() + .map(span -> span.toBuilder() + .projectName(projectName) + .traceId(trace.id()) + .build())) + .toList(); + batchCreateSpansAndAssert(spans, apiKey, workspaceName); + + var traceScores = traces.stream() + .flatMap(trace -> trace.feedbackScores().stream() + .map(item -> FeedbackScoreMapper.INSTANCE.toFeedbackScoreBatchItem( + trace.id(), projectName, item))) + .toList(); + createAndAssertForTrace(FeedbackScoreBatch.builder().scores(traceScores).build(), workspaceName, apiKey); + + var spanScores = spans.stream() + .flatMap(span -> span.feedbackScores().stream() + .map(item -> FeedbackScoreMapper.INSTANCE.toFeedbackScoreBatchItem( + span.id(), projectName, item))) + .toList(); + createAndAssertForSpan(FeedbackScoreBatch.builder().scores(spanScores).build(), workspaceName, apiKey); + + getAndAssertPage(workspaceName, projectName, List.of(), traces, traces.reversed(), List.of(), apiKey); + getAndAssertPageSpans(workspaceName, projectName, List.of(), spans, spans.reversed(), List.of(), apiKey); + + var request = TracesDelete.builder() + .ids(traces.stream().map(Trace::id).collect(Collectors.toUnmodifiableSet())) + .build(); + deleteAndAssert(request, workspaceName, apiKey); + + getAndAssertPage(workspaceName, projectName, List.of(), traces, List.of(), List.of(), apiKey); + getAndAssertPageSpans(workspaceName, projectName, List.of(), spans, List.of(), List.of(), apiKey); + } + + @Test + void deleteTracesWithoutSpansScores() { + var apiKey = UUID.randomUUID().toString(); + var workspaceName = RandomStringUtils.randomAlphanumeric(10); + var workspaceId = UUID.randomUUID().toString(); + mockTargetWorkspace(apiKey, workspaceName, workspaceId); + + var projectName = RandomStringUtils.randomAlphanumeric(10); + + var traces = PodamFactoryUtils.manufacturePojoList(factory, Trace.class).stream() + .map(trace -> trace.toBuilder() + .projectName(projectName) + .build()) + .toList(); + batchCreateTracesAndAssert(traces, apiKey, workspaceName); + + var spans = traces.stream() + .flatMap(trace -> PodamFactoryUtils.manufacturePojoList(factory, Span.class).stream() + .map(span -> span.toBuilder() + .projectName(projectName) + .traceId(trace.id()) + .feedbackScores(null) + .build())) + .toList(); + batchCreateSpansAndAssert(spans, apiKey, workspaceName); + + var traceScores = traces.stream() + .flatMap(trace -> trace.feedbackScores().stream() + .map(item -> FeedbackScoreMapper.INSTANCE.toFeedbackScoreBatchItem( + trace.id(), projectName, item))) + .toList(); + createAndAssertForTrace(FeedbackScoreBatch.builder().scores(traceScores).build(), workspaceName, apiKey); + + getAndAssertPage(workspaceName, projectName, List.of(), traces, traces.reversed(), List.of(), apiKey); + getAndAssertPageSpans(workspaceName, projectName, List.of(), spans, spans.reversed(), List.of(), apiKey); + + var request = TracesDelete.builder() + .ids(traces.stream().map(Trace::id).collect(Collectors.toUnmodifiableSet())) + .build(); + deleteAndAssert(request, workspaceName, apiKey); + + getAndAssertPage(workspaceName, projectName, List.of(), traces, List.of(), List.of(), apiKey); + getAndAssertPageSpans(workspaceName, projectName, List.of(), spans, List.of(), List.of(), apiKey); + } + + @Test + void deleteTracesWithoutScores() { + var apiKey = UUID.randomUUID().toString(); + var workspaceName = RandomStringUtils.randomAlphanumeric(10); + var workspaceId = UUID.randomUUID().toString(); + mockTargetWorkspace(apiKey, workspaceName, workspaceId); + + var projectName = RandomStringUtils.randomAlphanumeric(10); + + var traces = PodamFactoryUtils.manufacturePojoList(factory, Trace.class).stream() + .map(trace -> trace.toBuilder() + .projectName(projectName) + .feedbackScores(null) + .build()) + .toList(); + batchCreateTracesAndAssert(traces, apiKey, workspaceName); + + var spans = traces.stream() + .flatMap(trace -> PodamFactoryUtils.manufacturePojoList(factory, Span.class).stream() + .map(span -> span.toBuilder() + .projectName(projectName) + .traceId(trace.id()) + .feedbackScores(null) + .build())) + .toList(); + batchCreateSpansAndAssert(spans, apiKey, workspaceName); + + getAndAssertPage(workspaceName, projectName, List.of(), traces, traces.reversed(), List.of(), apiKey); + getAndAssertPageSpans(workspaceName, projectName, List.of(), spans, spans.reversed(), List.of(), apiKey); + + var request = TracesDelete.builder() + .ids(traces.stream().map(Trace::id).collect(Collectors.toUnmodifiableSet())) + .build(); + deleteAndAssert(request, workspaceName, apiKey); + + getAndAssertPage(workspaceName, projectName, List.of(), traces, List.of(), List.of(), apiKey); + getAndAssertPageSpans(workspaceName, projectName, List.of(), spans, List.of(), List.of(), apiKey); + } + + @Test + void deleteTracesWithoutSpans() { + var apiKey = UUID.randomUUID().toString(); + var workspaceName = RandomStringUtils.randomAlphanumeric(10); + var workspaceId = UUID.randomUUID().toString(); + mockTargetWorkspace(apiKey, workspaceName, workspaceId); + + var projectName = RandomStringUtils.randomAlphanumeric(10); + + var traces = PodamFactoryUtils.manufacturePojoList(factory, Trace.class).stream() + .map(trace -> trace.toBuilder() + .projectName(projectName) + .feedbackScores(null) + .build()) + .toList(); + batchCreateTracesAndAssert(traces, apiKey, workspaceName); + + getAndAssertPage(workspaceName, projectName, List.of(), traces, traces.reversed(), List.of(), apiKey); + + var request = TracesDelete.builder() + .ids(traces.stream().map(Trace::id).collect(Collectors.toUnmodifiableSet())) + .build(); + deleteAndAssert(request, workspaceName, apiKey); + + getAndAssertPage(workspaceName, projectName, List.of(), traces, List.of(), List.of(), apiKey); + } + + @Test + void deleteTracesWithoutTraces() { + var apiKey = UUID.randomUUID().toString(); + var workspaceName = RandomStringUtils.randomAlphanumeric(10); + var workspaceId = UUID.randomUUID().toString(); + mockTargetWorkspace(apiKey, workspaceName, workspaceId); + + var request = factory.manufacturePojo(TracesDelete.class); + deleteAndAssert(request, workspaceName, apiKey); + } + } + + @Nested + @DisplayName("Update:") + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + class UpdateTrace { + + private Trace trace; + private UUID id; @BeforeEach void setUp() { trace = factory.manufacturePojo(Trace.class) .toBuilder() - .id(null) .endTime(null) .output(null) .startTime(Instant.now().minusSeconds(10)) @@ -3311,9 +3679,7 @@ void when__traceUpdateAndInsertAreProcessedOutOfOther__thenReturnTrace() { .projectId(null) .build(); - var startCreation = Instant.now(); runPatchAndAssertStatus(id, traceUpdate, API_KEY, TEST_WORKSPACE); - var created = Instant.now(); var newTrace = factory.manufacturePojo(Trace.class).toBuilder() .projectName(traceUpdate.projectName()) @@ -3335,7 +3701,7 @@ void when__traceUpdateAndInsertAreProcessedOutOfOther__thenReturnTrace() { assertThat(actualEntity.name()).isEqualTo(newTrace.name()); assertThat(actualEntity.startTime()).isEqualTo(newTrace.startTime()); - assertThat(actualEntity.createdAt()).isBetween(startCreation, created); + assertThat(actualEntity.createdAt()).isBefore(newTrace.createdAt()); } @Test @@ -3411,8 +3777,7 @@ private void runPatchAndAssertStatus(UUID id, TraceUpdate traceUpdate3, String a @Test @DisplayName("Success") void update() { - - TraceUpdate traceUpdate = TraceUpdate.builder() + var traceUpdate = TraceUpdate.builder() .endTime(Instant.now()) .input(JsonUtils.getJsonNodeFromString("{ \"input\": \"data\"}")) .output(JsonUtils.getJsonNodeFromString("{ \"output\": \"data\"}")) @@ -3438,8 +3803,8 @@ void update() { assertThat(actualEntity.endTime()).isEqualTo(traceUpdate.endTime()); assertThat(actualEntity.startTime()).isEqualTo(trace.startTime()); - assertThat(actualEntity.createdAt().isBefore(traceUpdate.endTime())).isTrue(); - assertThat(actualEntity.lastUpdatedAt().isAfter(traceUpdate.endTime())).isTrue(); + assertThat(actualEntity.createdAt()).isAfter(trace.createdAt()); + assertThat(actualEntity.lastUpdatedAt()).isAfter(traceUpdate.endTime()); } @Test @@ -3515,7 +3880,7 @@ void update__whenTagsIsEmpty__thenAcceptUpdate() { UUID projectId = getProjectId(trace.projectName(), TEST_WORKSPACE, API_KEY); - Trace actualTrace = getAndAssert(trace, id, projectId, trace.createdAt().minusMillis(1), API_KEY, + Trace actualTrace = getAndAssert(trace, projectId, API_KEY, TEST_WORKSPACE); assertThat(actualTrace.tags()).isNull(); @@ -3536,8 +3901,8 @@ void update__whenMetadataIsEmpty__thenAcceptUpdate() { UUID projectId = getProjectId(trace.projectName(), TEST_WORKSPACE, API_KEY); - Trace actualTrace = getAndAssert(trace.toBuilder().metadata(metadata).build(), id, projectId, - trace.createdAt().minusMillis(1), API_KEY, TEST_WORKSPACE); + Trace actualTrace = getAndAssert(trace.toBuilder().metadata(metadata).build(), projectId, + API_KEY, TEST_WORKSPACE); assertThat(actualTrace.metadata()).isEqualTo(metadata); } @@ -3557,8 +3922,8 @@ void update__whenInputIsEmpty__thenAcceptUpdate() { UUID projectId = getProjectId(trace.projectName(), TEST_WORKSPACE, API_KEY); - Trace actualTrace = getAndAssert(trace.toBuilder().input(input).build(), id, projectId, - trace.createdAt().minusMillis(1), API_KEY, TEST_WORKSPACE); + Trace actualTrace = getAndAssert(trace.toBuilder().input(input).build(), projectId, + API_KEY, TEST_WORKSPACE); assertThat(actualTrace.input()).isEqualTo(input); } @@ -3578,8 +3943,8 @@ void update__whenOutputIsEmpty__thenAcceptUpdate() { UUID projectId = getProjectId(trace.projectName(), TEST_WORKSPACE, API_KEY); - Trace actualTrace = getAndAssert(trace.toBuilder().output(output).build(), id, projectId, - trace.createdAt().minusMillis(1), API_KEY, TEST_WORKSPACE); + Trace actualTrace = getAndAssert(trace.toBuilder().output(output).build(), projectId, + API_KEY, TEST_WORKSPACE); assertThat(actualTrace.output()).isEqualTo(output); } @@ -3606,7 +3971,7 @@ void update__whenUpdatingUsingProjectId__thenAcceptUpdate() { .tags(traceUpdate.tags()) .build(); - getAndAssert(updatedTrace, id, projectId, trace.createdAt().minusMillis(1), API_KEY, TEST_WORKSPACE); + getAndAssert(updatedTrace, projectId, API_KEY, TEST_WORKSPACE); } } @@ -3623,12 +3988,38 @@ private Response getById(UUID id, String workspaceName, String apiKey) { return response; } + private void deleteAndAssert(UUID id, String workspaceName, String apiKey) { + try (var actualResponse = client.target(URL_TEMPLATE.formatted(baseURI)) + .path(id.toString()) + .request() + .header(HttpHeaders.AUTHORIZATION, apiKey) + .header(WORKSPACE_HEADER, workspaceName) + .delete()) { + + assertThat(actualResponse.getStatusInfo().getStatusCode()).isEqualTo(204); + assertThat(actualResponse.hasEntity()).isFalse(); + } + } + + private void deleteAndAssert(TracesDelete request, String workspaceName, String apiKey) { + try (var actualResponse = client.target(URL_TEMPLATE.formatted(baseURI)) + .path("delete") + .request() + .header(HttpHeaders.AUTHORIZATION, apiKey) + .header(WORKSPACE_HEADER, workspaceName) + .post(Entity.json(request))) { + + assertThat(actualResponse.getStatusInfo().getStatusCode()).isEqualTo(204); + assertThat(actualResponse.hasEntity()).isFalse(); + } + } + @Nested @DisplayName("Feedback:") @TestInstance(TestInstance.Lifecycle.PER_CLASS) class TraceFeedback { - public Stream invalidRequestBodyParams() { + Stream invalidRequestBodyParams() { return Stream.of( arguments(factory.manufacturePojo(FeedbackScore.class).toBuilder().name(null).build(), "name must not be blank"), @@ -3650,7 +4041,7 @@ public Stream invalidRequestBodyParams() { @DisplayName("when trace does not exist, then return not found") void feedback__whenTraceDoesNotExist__thenReturnNotFound() { - UUID id = generator.generate(); + var id = generator.generate(); try (var actualResponse = client.target(URL_TEMPLATE.formatted(baseURI)) .path(id.toString()) @@ -3694,18 +4085,14 @@ void feedback__whenFeedbackWithoutCategoryNameOrReason__thenReturnNoContent() { var trace = factory.manufacturePojo(Trace.class) .toBuilder() - .id(null) .projectName(DEFAULT_PROJECT) .endTime(null) .output(null) - .createdAt(null) - .lastUpdatedAt(null) .metadata(null) .tags(null) .feedbackScores(null) .build(); - var now = Instant.now(); var id = create(trace, API_KEY, TEST_WORKSPACE); FeedbackScore score = factory.manufacturePojo(FeedbackScore.class).toBuilder() @@ -3718,7 +4105,7 @@ void feedback__whenFeedbackWithoutCategoryNameOrReason__thenReturnNoContent() { UUID projectId = getProjectId(trace.projectName(), TEST_WORKSPACE, API_KEY); - var actualEntity = getAndAssert(trace, id, projectId, now, API_KEY, TEST_WORKSPACE); + var actualEntity = getAndAssert(trace, projectId, API_KEY, TEST_WORKSPACE); assertThat(actualEntity.feedbackScores()).hasSize(1); @@ -3733,18 +4120,14 @@ void feedback__whenFeedbackWithCategoryNameOrReason__thenReturnNoContent() { var trace = factory.manufacturePojo(Trace.class) .toBuilder() - .id(null) .projectName(DEFAULT_PROJECT) .endTime(null) .output(null) - .createdAt(null) - .lastUpdatedAt(null) .metadata(null) .tags(null) .feedbackScores(null) .build(); - var now = Instant.now(); var id = create(trace, API_KEY, TEST_WORKSPACE); var score = factory.manufacturePojo(FeedbackScore.class).toBuilder() @@ -3755,7 +4138,7 @@ void feedback__whenFeedbackWithCategoryNameOrReason__thenReturnNoContent() { UUID projectId = getProjectId(trace.projectName(), TEST_WORKSPACE, API_KEY); - Trace actualEntity = getAndAssert(trace, id, projectId, now, API_KEY, TEST_WORKSPACE); + Trace actualEntity = getAndAssert(trace, projectId, API_KEY, TEST_WORKSPACE); assertThat(actualEntity.feedbackScores()).hasSize(1); FeedbackScore actualScore = actualEntity.feedbackScores().getFirst(); @@ -3766,22 +4149,16 @@ void feedback__whenFeedbackWithCategoryNameOrReason__thenReturnNoContent() { @Test @DisplayName("when overriding feedback value, then return no content") void feedback__whenOverridingFeedbackValue__thenReturnNoContent() { - - String workspaceName = UUID.randomUUID().toString(); var trace = factory.manufacturePojo(Trace.class) .toBuilder() .projectName(DEFAULT_PROJECT) - .id(null) .endTime(null) .output(null) - .createdAt(null) - .lastUpdatedAt(null) .metadata(null) .tags(null) .feedbackScores(null) .build(); - var now = Instant.now(); var id = create(trace, API_KEY, TEST_WORKSPACE); var score = factory.manufacturePojo(FeedbackScore.class); @@ -3792,7 +4169,7 @@ void feedback__whenOverridingFeedbackValue__thenReturnNoContent() { create(id, newScore, TEST_WORKSPACE, API_KEY); UUID projectId = getProjectId(trace.projectName(), TEST_WORKSPACE, API_KEY); - var actualEntity = getAndAssert(trace, id, projectId, now, API_KEY, TEST_WORKSPACE); + var actualEntity = getAndAssert(trace, projectId, API_KEY, TEST_WORKSPACE); assertThat(actualEntity.feedbackScores()).hasSize(1); FeedbackScore actualScore = actualEntity.feedbackScores().getFirst(); @@ -3831,7 +4208,7 @@ void deleteFeedback__whenTraceDoesNotExist__thenReturnNoContent() { @DisplayName("Success") void deleteFeedback() { - Trace trace = factory.manufacturePojo(Trace.class); + var trace = factory.manufacturePojo(Trace.class); var id = create(trace, API_KEY, TEST_WORKSPACE); var score = FeedbackScore.builder() .name("name") @@ -3869,7 +4246,7 @@ void deleteFeedback() { @TestInstance(TestInstance.Lifecycle.PER_CLASS) class BatchTracesFeedback { - public Stream invalidRequestBodyParams() { + Stream invalidRequestBodyParams() { return Stream.of( arguments(FeedbackScoreBatch.builder().build(), "scores must not be null"), arguments(FeedbackScoreBatch.builder().scores(List.of()).build(), @@ -3919,17 +4296,11 @@ public Stream invalidRequestBodyParams() { @Test @DisplayName("Success") void feedback() { - - Instant now = Instant.now(); - var trace = factory.manufacturePojo(Trace.class) .toBuilder() .projectName(DEFAULT_PROJECT) - .id(null) .endTime(null) .output(null) - .createdAt(null) - .lastUpdatedAt(null) .metadata(null) .tags(null) .feedbackScores(null) @@ -3940,11 +4311,8 @@ void feedback() { var trace2 = factory.manufacturePojo(Trace.class) .toBuilder() .projectName(UUID.randomUUID().toString()) - .id(null) .endTime(null) .output(null) - .createdAt(null) - .lastUpdatedAt(null) .metadata(null) .tags(null) .feedbackScores(null) @@ -3972,23 +4340,14 @@ void feedback() { .value(factory.manufacturePojo(BigDecimal.class)) .build(); - try (var actualResponse = client.target(URL_TEMPLATE.formatted(baseURI)) - .path("feedback-scores") - .request() - .header(HttpHeaders.AUTHORIZATION, API_KEY) - .header(WORKSPACE_HEADER, TEST_WORKSPACE) - .put(Entity.json( - new FeedbackScoreBatch(List.of(score, score2, score3))))) { - - assertThat(actualResponse.getStatusInfo().getStatusCode()).isEqualTo(204); - assertThat(actualResponse.hasEntity()).isFalse(); - } + var feedbackScoreBatch = FeedbackScoreBatch.builder().scores(List.of(score, score2, score3)).build(); + createAndAssertForTrace(feedbackScoreBatch, TEST_WORKSPACE, API_KEY); UUID projectId = getProjectId(trace.projectName(), TEST_WORKSPACE, API_KEY); UUID projectId2 = getProjectId(trace2.projectName(), TEST_WORKSPACE, API_KEY); - var actualTrace1 = getAndAssert(trace, id, projectId, now, API_KEY, TEST_WORKSPACE); - var actualTrace2 = getAndAssert(trace2, id2, projectId2, now, API_KEY, TEST_WORKSPACE); + var actualTrace1 = getAndAssert(trace, projectId, API_KEY, TEST_WORKSPACE); + var actualTrace2 = getAndAssert(trace2, projectId2, API_KEY, TEST_WORKSPACE); assertThat(actualTrace2.feedbackScores()).hasSize(1); assertThat(actualTrace1.feedbackScores()).hasSize(2); @@ -4000,11 +4359,9 @@ void feedback() { @Test @DisplayName("when workspace is specified, then return no content") void feedback__whenWorkspaceIsSpecified__thenReturnNoContent() { - - Instant now = Instant.now(); - String projectName = UUID.randomUUID().toString(); - String workspaceName = UUID.randomUUID().toString(); - String workspaceId = UUID.randomUUID().toString(); + var projectName = UUID.randomUUID().toString(); + var workspaceName = UUID.randomUUID().toString(); + var workspaceId = UUID.randomUUID().toString(); String apiKey = UUID.randomUUID().toString(); mockTargetWorkspace(apiKey, workspaceName, workspaceId); @@ -4044,22 +4401,14 @@ void feedback__whenWorkspaceIsSpecified__thenReturnNoContent() { .value(factory.manufacturePojo(BigDecimal.class)) .build(); - try (var actualResponse = client.target(URL_TEMPLATE.formatted(baseURI)) - .path("feedback-scores") - .request() - .header(HttpHeaders.AUTHORIZATION, apiKey) - .header(WORKSPACE_HEADER, workspaceName) - .put(Entity.json(new FeedbackScoreBatch(List.of(score, score2, score3))))) { - - assertThat(actualResponse.getStatusInfo().getStatusCode()).isEqualTo(204); - assertThat(actualResponse.hasEntity()).isFalse(); - } + var feedbackScoreBatch = FeedbackScoreBatch.builder().scores(List.of(score, score2, score3)).build(); + createAndAssertForTrace(feedbackScoreBatch, workspaceName, apiKey); UUID projectId = getProjectId(DEFAULT_PROJECT, workspaceName, apiKey); UUID projectId2 = getProjectId(projectName, workspaceName, apiKey); - var actualTrace1 = getAndAssert(expectedTrace1, id, projectId, now, apiKey, workspaceName); - var actualTrace2 = getAndAssert(expectedTrace2, id2, projectId2, now, apiKey, workspaceName); + var actualTrace1 = getAndAssert(expectedTrace1, projectId, apiKey, workspaceName); + var actualTrace2 = getAndAssert(expectedTrace2, projectId2, apiKey, workspaceName); assertThat(actualTrace2.feedbackScores()).hasSize(1); assertThat(actualTrace1.feedbackScores()).hasSize(2); @@ -4092,18 +4441,14 @@ void feedback__whenFeedbackWithoutCategoryNameOrReason__thenReturnNoContent() { var trace = factory.manufacturePojo(Trace.class) .toBuilder() - .id(null) .projectName(DEFAULT_PROJECT) .endTime(null) .output(null) - .createdAt(null) - .lastUpdatedAt(null) .metadata(null) .tags(null) .feedbackScores(null) .build(); - var now = Instant.now(); var id = create(trace, API_KEY, TEST_WORKSPACE); var score = factory.manufacturePojo(FeedbackScoreBatchItem.class).toBuilder() @@ -4114,20 +4459,12 @@ void feedback__whenFeedbackWithoutCategoryNameOrReason__thenReturnNoContent() { .reason(null) .build(); - try (var actualResponse = client.target(URL_TEMPLATE.formatted(baseURI)) - .path("feedback-scores") - .request() - .header(HttpHeaders.AUTHORIZATION, API_KEY) - .header(WORKSPACE_HEADER, TEST_WORKSPACE) - .put(Entity.json(new FeedbackScoreBatch(List.of(score))))) { - - assertThat(actualResponse.getStatusInfo().getStatusCode()).isEqualTo(204); - assertThat(actualResponse.hasEntity()).isFalse(); - } + createAndAssertForTrace(FeedbackScoreBatch.builder().scores(List.of(score)).build(), TEST_WORKSPACE, + API_KEY); UUID projectId = getProjectId(trace.projectName(), TEST_WORKSPACE, API_KEY); - var actualEntity = getAndAssert(trace, id, projectId, now, API_KEY, TEST_WORKSPACE); + var actualEntity = getAndAssert(trace, projectId, API_KEY, TEST_WORKSPACE); assertThat(actualEntity.feedbackScores()).hasSize(1); @@ -4140,22 +4477,18 @@ void feedback__whenFeedbackWithoutCategoryNameOrReason__thenReturnNoContent() { @DisplayName("when feedback with category name or reason, then return no content") void feedback__whenFeedbackWithCategoryNameOrReason__thenReturnNoContent() { - String projectName = UUID.randomUUID().toString(); + var projectName = UUID.randomUUID().toString(); Trace expectedTrace = factory.manufacturePojo(Trace.class) .toBuilder() - .id(null) .projectName(projectName) .endTime(null) .output(null) - .createdAt(null) - .lastUpdatedAt(null) .metadata(null) .tags(null) .feedbackScores(null) .build(); - var now = Instant.now(); var id = create(expectedTrace, API_KEY, TEST_WORKSPACE); var score = factory.manufacturePojo(FeedbackScoreBatchItem.class).toBuilder() @@ -4164,19 +4497,11 @@ void feedback__whenFeedbackWithCategoryNameOrReason__thenReturnNoContent() { .value(factory.manufacturePojo(BigDecimal.class)) .build(); - try (var actualResponse = client.target(URL_TEMPLATE.formatted(baseURI)) - .path("feedback-scores") - .request() - .header(HttpHeaders.AUTHORIZATION, API_KEY) - .header(WORKSPACE_HEADER, TEST_WORKSPACE) - .put(Entity.json(new FeedbackScoreBatch(List.of(score))))) { - - assertThat(actualResponse.getStatusInfo().getStatusCode()).isEqualTo(204); - assertThat(actualResponse.hasEntity()).isFalse(); - } + createAndAssertForTrace(FeedbackScoreBatch.builder().scores(List.of(score)).build(), TEST_WORKSPACE, + API_KEY); - var actualEntity = getAndAssert(expectedTrace, id, - getProjectId(expectedTrace.projectName(), TEST_WORKSPACE, API_KEY), now, API_KEY, + var actualEntity = getAndAssert(expectedTrace, + getProjectId(expectedTrace.projectName(), TEST_WORKSPACE, API_KEY), API_KEY, TEST_WORKSPACE); assertThat(actualEntity.feedbackScores()).hasSize(1); @@ -4189,23 +4514,18 @@ void feedback__whenFeedbackWithCategoryNameOrReason__thenReturnNoContent() { @DisplayName("when overriding feedback value, then return no content") void feedback__whenOverridingFeedbackValue__thenReturnNoContent() { - String projectName = UUID.randomUUID().toString(); + var projectName = UUID.randomUUID().toString(); var trace = factory.manufacturePojo(Trace.class) .toBuilder() - .id(null) .projectName(projectName) .endTime(null) .output(null) - .createdAt(null) - .lastUpdatedAt(null) .metadata(null) .tags(null) .feedbackScores(null) .feedbackScores(null) .build(); - Instant now = Instant.now(); - var id = create(trace, API_KEY, TEST_WORKSPACE); var score = factory.manufacturePojo(FeedbackScoreBatchItem.class).toBuilder() @@ -4213,34 +4533,17 @@ void feedback__whenOverridingFeedbackValue__thenReturnNoContent() { .projectName(trace.projectName()) .build(); - try (var actualResponse = client.target(URL_TEMPLATE.formatted(baseURI)) - .path("feedback-scores") - .request() - .header(HttpHeaders.AUTHORIZATION, API_KEY) - .header(WORKSPACE_HEADER, TEST_WORKSPACE) - .put(Entity.json(new FeedbackScoreBatch(List.of(score))))) { - - assertThat(actualResponse.getStatusInfo().getStatusCode()).isEqualTo(204); - assertThat(actualResponse.hasEntity()).isFalse(); - } + createAndAssertForTrace(FeedbackScoreBatch.builder().scores(List.of(score)).build(), TEST_WORKSPACE, + API_KEY); FeedbackScoreBatchItem newItem = score.toBuilder().value(factory.manufacturePojo(BigDecimal.class)).build(); - try (var actualResponse = client.target(URL_TEMPLATE.formatted(baseURI)) - .path("feedback-scores") - .request() - .header(HttpHeaders.AUTHORIZATION, API_KEY) - .header(WORKSPACE_HEADER, TEST_WORKSPACE) - .put(Entity.json( - new FeedbackScoreBatch(List.of(newItem))))) { - - assertThat(actualResponse.getStatusInfo().getStatusCode()).isEqualTo(204); - assertThat(actualResponse.hasEntity()).isFalse(); - } + createAndAssertForTrace(FeedbackScoreBatch.builder().scores(List.of(newItem)).build(), TEST_WORKSPACE, + API_KEY); UUID projectId = getProjectId(trace.projectName(), TEST_WORKSPACE, API_KEY); - var actualEntity = getAndAssert(trace, id, projectId, now, API_KEY, TEST_WORKSPACE); + var actualEntity = getAndAssert(trace, projectId, API_KEY, TEST_WORKSPACE); assertThat(actualEntity.feedbackScores()).hasSize(1); FeedbackScore actualScore = actualEntity.feedbackScores().getFirst(); @@ -4259,17 +4562,8 @@ void feedback__whenTraceDoesNotExist__thenReturnNoContentAndCreateScore() { .projectName(DEFAULT_PROJECT) .build(); - try (var actualResponse = client.target(URL_TEMPLATE.formatted(baseURI)) - .path("feedback-scores") - .request() - .header(HttpHeaders.AUTHORIZATION, API_KEY) - .header(WORKSPACE_HEADER, TEST_WORKSPACE) - .put(Entity.json(new FeedbackScoreBatch(List.of(score))))) { - - assertThat(actualResponse.getStatusInfo().getStatusCode()).isEqualTo(204); - assertThat(actualResponse.hasEntity()).isFalse(); - } - + createAndAssertForTrace(FeedbackScoreBatch.builder().scores(List.of(score)).build(), TEST_WORKSPACE, + API_KEY); } @Test @@ -4327,16 +4621,7 @@ void feedback__whenFeedbackSpanBatchHasMaxSize__thenReturnNoContentAndCreateScor .build()) .toList(); - try (var actualResponse = client.target(URL_TEMPLATE.formatted(baseURI)) - .path("feedback-scores") - .request() - .header(HttpHeaders.AUTHORIZATION, API_KEY) - .header(WORKSPACE_HEADER, TEST_WORKSPACE) - .put(Entity.json(new FeedbackScoreBatch(scores)))) { - - assertThat(actualResponse.getStatusInfo().getStatusCode()).isEqualTo(204); - assertThat(actualResponse.hasEntity()).isFalse(); - } + createAndAssertForTrace(FeedbackScoreBatch.builder().scores(scores).build(), TEST_WORKSPACE, API_KEY); } @Test @@ -4363,11 +4648,32 @@ void feedback__whenFeedbackTraceIdIsNotValid__thenReturn400() { } } + private void createAndAssertForTrace(FeedbackScoreBatch request, String workspaceName, String apiKey) { + createAndAssert(URL_TEMPLATE.formatted(baseURI), request, workspaceName, apiKey); + } + + private void createAndAssertForSpan(FeedbackScoreBatch request, String workspaceName, String apiKey) { + createAndAssert(URL_TEMPLATE_SPANS.formatted(baseURI), request, workspaceName, apiKey); + } + + private void createAndAssert(String path, FeedbackScoreBatch request, String workspaceName, String apiKey) { + try (var actualResponse = client.target(path) + .path("feedback-scores") + .request() + .header(HttpHeaders.AUTHORIZATION, apiKey) + .header(WORKSPACE_HEADER, workspaceName) + .put(Entity.json(request))) { + + assertThat(actualResponse.getStatusInfo().getStatusCode()).isEqualTo(204); + assertThat(actualResponse.hasEntity()).isFalse(); + } + } + private void assertEqualsForScores(FeedbackScore actualScore, FeedbackScore expectedScore) { assertThat(actualScore) .usingRecursiveComparison() .withComparatorForType(BigDecimal::compareTo, BigDecimal.class) - .ignoringFields(IGNORED_FIELDS) + .ignoringFields(IGNORED_FIELDS_SCORES) .isEqualTo(expectedScore); } @@ -4375,7 +4681,7 @@ private void assertEqualsForScores(FeedbackScore actualScore, FeedbackScoreBatch assertThat(actualScore) .usingRecursiveComparison() .withComparatorForType(BigDecimal::compareTo, BigDecimal.class) - .ignoringFields(IGNORED_FIELDS) + .ignoringFields(IGNORED_FIELDS_SCORES) .isEqualTo(expectedScore); } @@ -4383,11 +4689,10 @@ private void assertEqualsForScores(List expected, List actual) { assertThat(actual) .usingRecursiveComparison( RecursiveComparisonConfiguration.builder() - .withIgnoredFields(IGNORED_FIELDS) + .withIgnoredFields(IGNORED_FIELDS_SCORES) .withComparatorForType(BigDecimal::compareTo, BigDecimal.class) .build()) .ignoringCollectionOrder() .isEqualTo(expected); } - } diff --git a/apps/opik-backend/src/test/java/com/comet/opik/podam/BigDecimalStrategy.java b/apps/opik-backend/src/test/java/com/comet/opik/podam/BigDecimalStrategy.java new file mode 100644 index 0000000000..11e1894614 --- /dev/null +++ b/apps/opik-backend/src/test/java/com/comet/opik/podam/BigDecimalStrategy.java @@ -0,0 +1,34 @@ +package com.comet.opik.podam; + +import com.comet.opik.utils.ValidationUtils; +import jakarta.validation.constraints.DecimalMax; +import jakarta.validation.constraints.DecimalMin; +import uk.co.jemos.podam.api.PodamUtils; +import uk.co.jemos.podam.common.AttributeStrategy; +import uk.co.jemos.podam.common.BeanValidationStrategy; + +import java.lang.annotation.Annotation; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.List; + +public class BigDecimalStrategy implements AttributeStrategy { + + public static final BigDecimalStrategy INSTANCE = new BigDecimalStrategy(); + + @Override + public BigDecimal getValue(Class attrType, List annotations) { + var min = ValidationUtils.MIN_FEEDBACK_SCORE_VALUE; + var decimalMin = BeanValidationStrategy.findTypeFromList(annotations, DecimalMin.class); + if (null != decimalMin) { + min = decimalMin.value(); + } + var max = ValidationUtils.MAX_FEEDBACK_SCORE_VALUE; + var decimalMax = BeanValidationStrategy.findTypeFromList(annotations, DecimalMax.class); + if (null != decimalMax) { + max = decimalMax.value(); + } + var value = PodamUtils.getDoubleInRange(Double.parseDouble(min), Double.parseDouble(max)); + return new BigDecimal(value).setScale(ValidationUtils.SCALE, RoundingMode.HALF_EVEN); + } +} diff --git a/apps/opik-backend/src/test/java/com/comet/opik/podam/PodamFactoryUtils.java b/apps/opik-backend/src/test/java/com/comet/opik/podam/PodamFactoryUtils.java index d7cecf72d1..333da413ed 100644 --- a/apps/opik-backend/src/test/java/com/comet/opik/podam/PodamFactoryUtils.java +++ b/apps/opik-backend/src/test/java/com/comet/opik/podam/PodamFactoryUtils.java @@ -8,6 +8,8 @@ import com.comet.opik.podam.manufacturer.NumericalFeedbackDetailTypeManufacturer; import com.comet.opik.podam.manufacturer.UUIDTypeManufacturer; import com.fasterxml.jackson.databind.JsonNode; +import jakarta.validation.constraints.DecimalMax; +import jakarta.validation.constraints.DecimalMin; import jakarta.validation.constraints.Pattern; import uk.co.jemos.podam.api.PodamFactory; import uk.co.jemos.podam.api.PodamFactoryImpl; @@ -26,9 +28,11 @@ public class PodamFactoryUtils { public static PodamFactory newPodamFactory() { var podamFactory = new PodamFactoryImpl(); var strategy = ((RandomDataProviderStrategy) podamFactory.getStrategy()); + strategy.addOrReplaceAttributeStrategy(Pattern.class, PatternStrategy.INSTANCE); + strategy.addOrReplaceAttributeStrategy(DecimalMax.class, BigDecimalStrategy.INSTANCE); + strategy.addOrReplaceAttributeStrategy(DecimalMin.class, BigDecimalStrategy.INSTANCE); strategy.addOrReplaceTypeManufacturer(BigDecimal.class, BigDecimalTypeManufacturer.INSTANCE); strategy.addOrReplaceTypeManufacturer(UUID.class, UUIDTypeManufacturer.INSTANCE); - strategy.addOrReplaceAttributeStrategy(Pattern.class, PatternStrategy.INSTANCE); strategy.addOrReplaceTypeManufacturer( NumericalFeedbackDefinition.NumericalFeedbackDetail.class, new NumericalFeedbackDetailTypeManufacturer());