diff --git a/extra/bundle/pom.xml b/extra/bundle/pom.xml
index 6958b0dc3b9..14f477556eb 100644
--- a/extra/bundle/pom.xml
+++ b/extra/bundle/pom.xml
@@ -65,6 +65,11 @@
wurfl-devicedetection
${project.version}
+
+ org.prebid.server.hooks.modules
+ live-intent-omni-channel-identity
+ ${project.version}
+
diff --git a/extra/modules/live-intent-omni-channel-identity/README.md b/extra/modules/live-intent-omni-channel-identity/README.md
new file mode 100644
index 00000000000..be5ad801ec1
--- /dev/null
+++ b/extra/modules/live-intent-omni-channel-identity/README.md
@@ -0,0 +1,46 @@
+# Overview
+
+This module enriches bid requests with user EIDs.
+
+The user EIDs to be enriched are configured per partner as part of the LiveIntent HIRO onboarding process. As part of this onboarding process, partners will also be provided with the `identity-resolution-endpoint` URL as well as with the `auth-token`.
+
+`treatment-rate` is a value between 0.0 and 1.0 (including 0.0 and 1.0) and defines the percentage of requests for which identity enrichment should be performed. This value can be freely picked. We recommend a value between 0.9 and 0.95
+
+## Configuration
+
+To start using the LiveIntent Omni Channel Identity module you have to enable it and add configuration:
+
+```yaml
+hooks:
+ liveintent-omni-channel-identity:
+ enabled: true
+ host-execution-plan: >
+ {
+ "endpoints": {
+ "/openrtb2/auction": {
+ "stages": {
+ "processed-auction-request": {
+ "groups": [
+ {
+ "timeout": 100,
+ "hook-sequence": [
+ {
+ "module-code": "liveintent-omni-channel-identity",
+ "hook-impl-code": "liveintent-omni-channel-identity-enrichment-hook"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+ modules:
+ liveintent-omni-channel-identity:
+ request-timeout-ms: 2000
+ identity-resolution-endpoint: "https://liveintent.com/idx"
+ auth-token: "secret-token"
+ treatment-rate: 0.9
+```
+
diff --git a/extra/modules/live-intent-omni-channel-identity/pom.xml b/extra/modules/live-intent-omni-channel-identity/pom.xml
new file mode 100644
index 00000000000..7ed184a262d
--- /dev/null
+++ b/extra/modules/live-intent-omni-channel-identity/pom.xml
@@ -0,0 +1,18 @@
+
+
+ 4.0.0
+
+ org.prebid.server.hooks.modules
+ all-modules
+ 3.30.0-SNAPSHOT
+
+
+ live-intent-omni-channel-identity
+
+ live-intent-omni-channel-identity
+ LiveIntent Omni-Channel Identity
+
+
+
+
diff --git a/extra/modules/live-intent-omni-channel-identity/src/main/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/config/LiveIntentOmniChannelIdentityConfiguration.java b/extra/modules/live-intent-omni-channel-identity/src/main/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/config/LiveIntentOmniChannelIdentityConfiguration.java
new file mode 100644
index 00000000000..7eab97804cc
--- /dev/null
+++ b/extra/modules/live-intent-omni-channel-identity/src/main/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/config/LiveIntentOmniChannelIdentityConfiguration.java
@@ -0,0 +1,39 @@
+package org.prebid.server.hooks.modules.liveintent.omni.channel.identity.config;
+
+import org.prebid.server.hooks.modules.liveintent.omni.channel.identity.model.config.ModuleConfig;
+import org.prebid.server.hooks.modules.liveintent.omni.channel.identity.v1.LiveIntentOmniChannelIdentityModule;
+import org.prebid.server.hooks.modules.liveintent.omni.channel.identity.v1.hooks.LiveIntentOmniChannelIdentityProcessedAuctionRequestHook;
+import org.prebid.server.hooks.v1.Hook;
+import org.prebid.server.hooks.v1.InvocationContext;
+import org.prebid.server.hooks.v1.Module;
+import org.prebid.server.json.JacksonMapper;
+import org.prebid.server.vertx.httpclient.HttpClient;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.Set;
+import java.util.concurrent.ThreadLocalRandom;
+
+@Configuration
+@ConditionalOnProperty(
+ prefix = "hooks." + LiveIntentOmniChannelIdentityModule.CODE,
+ name = "enabled",
+ havingValue = "true")
+public class LiveIntentOmniChannelIdentityConfiguration {
+
+ @Bean
+ @ConfigurationProperties(prefix = "hooks.modules." + LiveIntentOmniChannelIdentityModule.CODE)
+ ModuleConfig moduleConfig() {
+ return new ModuleConfig();
+ }
+
+ @Bean
+ Module liveIntentOmniChannelIdentityModule(ModuleConfig config, JacksonMapper mapper, HttpClient httpClient) {
+ final Set extends Hook, ? extends InvocationContext>> hooks = Set.of(
+ new LiveIntentOmniChannelIdentityProcessedAuctionRequestHook(
+ config, mapper, httpClient, () -> ThreadLocalRandom.current().nextLong()));
+ return new LiveIntentOmniChannelIdentityModule(hooks);
+ }
+}
diff --git a/extra/modules/live-intent-omni-channel-identity/src/main/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/model/IdResResponse.java b/extra/modules/live-intent-omni-channel-identity/src/main/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/model/IdResResponse.java
new file mode 100644
index 00000000000..c94df691c2f
--- /dev/null
+++ b/extra/modules/live-intent-omni-channel-identity/src/main/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/model/IdResResponse.java
@@ -0,0 +1,14 @@
+package org.prebid.server.hooks.modules.liveintent.omni.channel.identity.model;
+
+import com.iab.openrtb.request.Eid;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@NoArgsConstructor
+public class IdResResponse {
+
+ List eids;
+}
diff --git a/extra/modules/live-intent-omni-channel-identity/src/main/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/model/config/ModuleConfig.java b/extra/modules/live-intent-omni-channel-identity/src/main/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/model/config/ModuleConfig.java
new file mode 100644
index 00000000000..a7fc92b1feb
--- /dev/null
+++ b/extra/modules/live-intent-omni-channel-identity/src/main/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/model/config/ModuleConfig.java
@@ -0,0 +1,15 @@
+package org.prebid.server.hooks.modules.liveintent.omni.channel.identity.model.config;
+
+import lombok.Data;
+
+@Data
+public final class ModuleConfig {
+
+ long requestTimeoutMs;
+
+ String identityResolutionEndpoint;
+
+ String authToken;
+
+ float treatmentRate;
+}
diff --git a/extra/modules/live-intent-omni-channel-identity/src/main/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/v1/LiveIntentOmniChannelIdentityModule.java b/extra/modules/live-intent-omni-channel-identity/src/main/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/v1/LiveIntentOmniChannelIdentityModule.java
new file mode 100644
index 00000000000..0ffdce8b436
--- /dev/null
+++ b/extra/modules/live-intent-omni-channel-identity/src/main/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/v1/LiveIntentOmniChannelIdentityModule.java
@@ -0,0 +1,23 @@
+package org.prebid.server.hooks.modules.liveintent.omni.channel.identity.v1;
+
+import org.prebid.server.hooks.v1.Hook;
+import org.prebid.server.hooks.v1.InvocationContext;
+import org.prebid.server.hooks.v1.Module;
+
+import java.util.Collection;
+
+public record LiveIntentOmniChannelIdentityModule(
+ Collection extends Hook, ? extends InvocationContext>> hooks) implements Module {
+
+ public static final String CODE = "liveintent-omni-channel-identity";
+
+ @Override
+ public String code() {
+ return CODE;
+ }
+
+ @Override
+ public Collection extends Hook, ? extends InvocationContext>> hooks() {
+ return hooks;
+ }
+}
diff --git a/extra/modules/live-intent-omni-channel-identity/src/main/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/v1/hooks/LiveIntentOmniChannelIdentityProcessedAuctionRequestHook.java b/extra/modules/live-intent-omni-channel-identity/src/main/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/v1/hooks/LiveIntentOmniChannelIdentityProcessedAuctionRequestHook.java
new file mode 100644
index 00000000000..421bdb4caee
--- /dev/null
+++ b/extra/modules/live-intent-omni-channel-identity/src/main/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/v1/hooks/LiveIntentOmniChannelIdentityProcessedAuctionRequestHook.java
@@ -0,0 +1,115 @@
+package org.prebid.server.hooks.modules.liveintent.omni.channel.identity.v1.hooks;
+
+import com.iab.openrtb.request.BidRequest;
+import com.iab.openrtb.request.Eid;
+import com.iab.openrtb.request.User;
+import io.vertx.core.Future;
+import io.vertx.core.MultiMap;
+import org.prebid.server.hooks.execution.v1.InvocationResultImpl;
+import org.prebid.server.hooks.execution.v1.auction.AuctionRequestPayloadImpl;
+import org.prebid.server.hooks.modules.liveintent.omni.channel.identity.model.IdResResponse;
+import org.prebid.server.hooks.modules.liveintent.omni.channel.identity.model.config.ModuleConfig;
+import org.prebid.server.hooks.v1.InvocationAction;
+import org.prebid.server.hooks.v1.InvocationResult;
+import org.prebid.server.hooks.v1.InvocationStatus;
+import org.prebid.server.hooks.v1.auction.AuctionInvocationContext;
+import org.prebid.server.hooks.v1.auction.AuctionRequestPayload;
+import org.prebid.server.hooks.v1.auction.ProcessedAuctionRequestHook;
+import org.prebid.server.json.JacksonMapper;
+import org.prebid.server.log.Logger;
+import org.prebid.server.log.LoggerFactory;
+import org.prebid.server.util.HttpUtil;
+import org.prebid.server.util.ListUtil;
+import org.prebid.server.vertx.httpclient.HttpClient;
+import org.prebid.server.vertx.httpclient.model.HttpClientResponse;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.random.RandomGenerator;
+
+public class LiveIntentOmniChannelIdentityProcessedAuctionRequestHook implements ProcessedAuctionRequestHook {
+
+ private static final Logger logger =
+ LoggerFactory.getLogger(LiveIntentOmniChannelIdentityProcessedAuctionRequestHook.class);
+ private static final String CODE = "liveintent-omni-channel-identity-enrichment-hook";
+
+ private final ModuleConfig config;
+ private final JacksonMapper mapper;
+ private final HttpClient httpClient;
+ private final RandomGenerator random;
+
+ public LiveIntentOmniChannelIdentityProcessedAuctionRequestHook(
+ ModuleConfig config,
+ JacksonMapper mapper,
+ HttpClient httpClient,
+ RandomGenerator random) {
+
+ this.config = Objects.requireNonNull(config);
+ this.mapper = Objects.requireNonNull(mapper);
+ this.httpClient = Objects.requireNonNull(httpClient);
+ this.random = Objects.requireNonNull(random);
+ }
+
+ @Override
+ public Future> call(
+ AuctionRequestPayload auctionRequestPayload,
+ AuctionInvocationContext invocationContext) {
+ if (random.nextFloat() < config.getTreatmentRate()) {
+ return requestEnrichment(auctionRequestPayload)
+ .>map(resolutionResult ->
+ InvocationResultImpl.builder()
+ .status(InvocationStatus.success)
+ .action(InvocationAction.update)
+ .payloadUpdate(requestPayload -> updatedPayload(requestPayload, resolutionResult))
+ .build())
+ .onFailure(throwable -> logger.error("Failed enrichment:", throwable));
+ }
+ return Future.succeededFuture(
+ InvocationResultImpl.builder()
+ .status(InvocationStatus.success)
+ .action(InvocationAction.no_action)
+ .build());
+
+ }
+
+ private Future requestEnrichment(AuctionRequestPayload auctionRequestPayload) {
+ final String bidRequestJson = mapper.encodeToString(auctionRequestPayload.bidRequest());
+ return httpClient.post(
+ config.getIdentityResolutionEndpoint(),
+ headers(),
+ bidRequestJson,
+ config.getRequestTimeoutMs())
+ .map(this::processResponse);
+ }
+
+ private MultiMap headers() {
+ return MultiMap.caseInsensitiveMultiMap()
+ .add(HttpUtil.AUTHORIZATION_HEADER, "Bearer " + config.getAuthToken());
+ }
+
+ private IdResResponse processResponse(HttpClientResponse response) {
+ return mapper.decodeValue(response.getBody(), IdResResponse.class);
+ }
+
+ private AuctionRequestPayload updatedPayload(AuctionRequestPayload requestPayload, IdResResponse idResResponse) {
+ final User user = Optional.ofNullable(
+ requestPayload.bidRequest())
+ .map(BidRequest::getUser)
+ .orElse(User.builder().build());
+
+ final List allEids = ListUtil.union(
+ Optional.ofNullable(user.getEids()).orElse(Collections.emptyList()), idResResponse.getEids());
+
+ final User updatedUser = user.toBuilder().eids(allEids).build();
+ final BidRequest updatedBidRequest = requestPayload.bidRequest().toBuilder().user(updatedUser).build();
+
+ return AuctionRequestPayloadImpl.of(updatedBidRequest);
+ }
+
+ @Override
+ public String code() {
+ return CODE;
+ }
+}
diff --git a/extra/modules/live-intent-omni-channel-identity/src/test/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/model/config/IdResResponseTest.java b/extra/modules/live-intent-omni-channel-identity/src/test/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/model/config/IdResResponseTest.java
new file mode 100644
index 00000000000..aa073979b34
--- /dev/null
+++ b/extra/modules/live-intent-omni-channel-identity/src/test/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/model/config/IdResResponseTest.java
@@ -0,0 +1,40 @@
+package org.prebid.server.hooks.modules.liveintent.omni.channel.identity.model.config;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.iab.openrtb.request.Eid;
+import com.iab.openrtb.request.Uid;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.prebid.server.hooks.modules.liveintent.omni.channel.identity.model.IdResResponse;
+import org.prebid.server.json.JacksonMapper;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class IdResResponseTest {
+
+ private JacksonMapper jacksonMapper;
+
+ @BeforeEach
+ public void setUp() {
+ final ObjectMapper mapper = new ObjectMapper();
+ jacksonMapper = new JacksonMapper(mapper);
+ }
+
+ @Test
+ public void shouldDecodeFromString() {
+ // given
+ final IdResResponse result = jacksonMapper.decodeValue(
+ "{\"eids\": [ { \"source\": \"liveintent.com\", "
+ + "\"uids\": [ { \"atype\": 3, \"id\" : \"some_id\" } ] } ] }",
+ IdResResponse.class);
+
+ // when and then
+ assertThat(result.getEids()).isEqualTo(List.of(
+ Eid.builder()
+ .source("liveintent.com")
+ .uids(List.of(Uid.builder().atype(3).id("some_id").build()))
+ .build()));
+ }
+}
diff --git a/extra/modules/live-intent-omni-channel-identity/src/test/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/model/config/ModuleConfigTest.java b/extra/modules/live-intent-omni-channel-identity/src/test/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/model/config/ModuleConfigTest.java
new file mode 100644
index 00000000000..39cfd34298e
--- /dev/null
+++ b/extra/modules/live-intent-omni-channel-identity/src/test/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/model/config/ModuleConfigTest.java
@@ -0,0 +1,35 @@
+package org.prebid.server.hooks.modules.liveintent.omni.channel.identity.model.config;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ModuleConfigTest {
+
+ @Test
+ public void shouldReturnRequestTimeoutMs() {
+ final ModuleConfig moduleConfig = new ModuleConfig();
+ moduleConfig.setRequestTimeoutMs(5);
+ assertThat(moduleConfig.getRequestTimeoutMs()).isEqualTo(5);
+ }
+
+ @Test
+ public void shouldReturnIdentityResolutionEndpoint() {
+ // given
+ final ModuleConfig moduleConfig = new ModuleConfig();
+ moduleConfig.setIdentityResolutionEndpoint("https://test.com/idres");
+
+ // when and then
+ assertThat(moduleConfig.getIdentityResolutionEndpoint()).isEqualTo("https://test.com/idres");
+ }
+
+ @Test
+ public void shouldReturnAuthToken() {
+ // given
+ final ModuleConfig moduleConfig = new ModuleConfig();
+ moduleConfig.setAuthToken("secret_token");
+
+ // when and then
+ assertThat(moduleConfig.getAuthToken()).isEqualTo("secret_token");
+ }
+}
diff --git a/extra/modules/live-intent-omni-channel-identity/src/test/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/v1/LiveIntentOmniChannelIdentityProcessedAuctionRequestHookTest.java b/extra/modules/live-intent-omni-channel-identity/src/test/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/v1/LiveIntentOmniChannelIdentityProcessedAuctionRequestHookTest.java
new file mode 100644
index 00000000000..a01fc949ce2
--- /dev/null
+++ b/extra/modules/live-intent-omni-channel-identity/src/test/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/v1/LiveIntentOmniChannelIdentityProcessedAuctionRequestHookTest.java
@@ -0,0 +1,196 @@
+package org.prebid.server.hooks.modules.liveintent.omni.channel.identity.v1;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.iab.openrtb.request.BidRequest;
+import com.iab.openrtb.request.Eid;
+import com.iab.openrtb.request.Uid;
+import com.iab.openrtb.request.User;
+import io.vertx.core.Future;
+import io.vertx.core.MultiMap;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.prebid.server.hooks.execution.v1.auction.AuctionInvocationContextImpl;
+import org.prebid.server.hooks.execution.v1.auction.AuctionRequestPayloadImpl;
+import org.prebid.server.hooks.modules.liveintent.omni.channel.identity.model.config.ModuleConfig;
+import org.prebid.server.hooks.modules.liveintent.omni.channel.identity.v1.hooks.LiveIntentOmniChannelIdentityProcessedAuctionRequestHook;
+import org.prebid.server.hooks.v1.InvocationAction;
+import org.prebid.server.hooks.v1.InvocationResult;
+import org.prebid.server.hooks.v1.InvocationStatus;
+import org.prebid.server.hooks.v1.auction.AuctionInvocationContext;
+import org.prebid.server.hooks.v1.auction.AuctionRequestPayload;
+import org.prebid.server.json.JacksonMapper;
+import org.prebid.server.vertx.httpclient.HttpClient;
+import org.prebid.server.vertx.httpclient.model.HttpClientResponse;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+public class LiveIntentOmniChannelIdentityProcessedAuctionRequestHookTest {
+
+ private ModuleConfig moduleConfig;
+ private LiveIntentOmniChannelIdentityProcessedAuctionRequestHook target;
+ private JacksonMapper jacksonMapper;
+
+ @Mock
+ private HttpClient httpClient;
+ @Mock
+ private Random random;
+
+ private final ArgumentMatcher bearerAuthHeaderMatcher = new ArgumentMatcher<>() {
+ @Override
+ public boolean matches(MultiMap entries) {
+ return entries.contains("Authorization", "Bearer " + moduleConfig.getAuthToken(), true);
+ }
+ };
+
+ @BeforeEach
+ public void setUp() {
+ final ObjectMapper mapper = new ObjectMapper();
+ jacksonMapper = new JacksonMapper(mapper);
+
+ moduleConfig = new ModuleConfig();
+ moduleConfig.setRequestTimeoutMs(5);
+ moduleConfig.setIdentityResolutionEndpoint("https://test.com/idres");
+ moduleConfig.setAuthToken("secret_auth_token");
+ moduleConfig.setTreatmentRate(0.9f);
+
+ target = new LiveIntentOmniChannelIdentityProcessedAuctionRequestHook(
+ moduleConfig, jacksonMapper, httpClient, random);
+
+ }
+
+ @Test
+ public void shouldAddResolvedEids() {
+ // given
+ final Uid providedUid = Uid.builder().id("id1").atype(2).build();
+ final Eid providedEid = Eid.builder().source("some.source.com")
+ .uids(Collections.singletonList(providedUid)).build();
+
+ final Uid enrichedUid = Uid.builder().id("id2").atype(3).build();
+ final Eid enrichedEid = Eid.builder().source("liveintent.com")
+ .uids(Collections.singletonList(enrichedUid)).build();
+
+ final User user = User.builder().eids(Collections.singletonList(providedEid)).build();
+ final BidRequest bidRequest = BidRequest.builder().id("request").user(user).build();
+
+ final AuctionInvocationContext auctionInvocationContext = AuctionInvocationContextImpl.of(
+ null, null, false, null, null);
+
+ final HttpClientResponse mockResponse = mock(HttpClientResponse.class);
+
+ // when
+ when(random.nextFloat()).thenReturn(0.89f);
+
+ when(mockResponse.getBody())
+ .thenReturn("{\"eids\": [ { \"source\": \"" + enrichedEid.getSource()
+ + "\", \"uids\": [ { \"atype\": "
+ + enrichedUid.getAtype()
+ + ", \"id\" : \""
+ + enrichedUid.getId() + "\" } ] } ] }");
+
+ when(httpClient.post(
+ eq(moduleConfig.getIdentityResolutionEndpoint()),
+ argThat(bearerAuthHeaderMatcher),
+ eq(jacksonMapper.encodeToString(bidRequest)),
+ eq(moduleConfig.getRequestTimeoutMs()))
+ ).thenReturn(Future.succeededFuture(mockResponse));
+
+ final Future> future =
+ target.call(AuctionRequestPayloadImpl.of(bidRequest), auctionInvocationContext);
+ final InvocationResult result = future.result();
+
+ // then
+ assertThat(future.succeeded()).isTrue();
+ assertThat(result.status()).isEqualTo(InvocationStatus.success);
+ assertThat(result.action()).isEqualTo(InvocationAction.update);
+ assertThat(result.payloadUpdate().apply(AuctionRequestPayloadImpl.of(bidRequest))
+ .bidRequest().getUser().getEids()).isEqualTo(List.of(providedEid, enrichedEid));
+ }
+
+ @Test
+ public void shouldNotAttemptToResolveEids() {
+ // given
+ final Uid providedUid = Uid.builder().id("id1").atype(2).build();
+ final Eid providedEid = Eid.builder().source("some.source.com")
+ .uids(Collections.singletonList(providedUid)).build();
+
+ final User user = User.builder().eids(Collections.singletonList(providedEid)).build();
+ final BidRequest bidRequest = BidRequest.builder().id("request").user(user).build();
+
+ final AuctionInvocationContext auctionInvocationContext = AuctionInvocationContextImpl.of(
+ null, null, false, null, null);
+
+ // when
+ when(random.nextFloat()).thenReturn(0.91f);
+
+ final Future> future =
+ target.call(AuctionRequestPayloadImpl.of(bidRequest), auctionInvocationContext);
+ final InvocationResult result = future.result();
+
+ // then
+ assertThat(future.succeeded()).isTrue();
+ assertThat(result.status()).isEqualTo(InvocationStatus.success);
+ assertThat(result.action()).isEqualTo(InvocationAction.no_action);
+ assertThat(result.payloadUpdate()).isNull();
+ }
+
+ @Test
+ public void shouldCreateUserWhenNotPresent() {
+ // given
+ final Uid enrichedUid = Uid.builder().id("id2").atype(3).build();
+ final Eid enrichedEid = Eid.builder().source("liveintent.com")
+ .uids(Collections.singletonList(enrichedUid)).build();
+
+ final BidRequest bidRequest = BidRequest.builder().id("request").build();
+
+ final AuctionInvocationContext auctionInvocationContext = AuctionInvocationContextImpl.of(
+ null, null, false, null, null);
+
+ final HttpClientResponse mockResponse = mock(HttpClientResponse.class);
+
+ // when
+ when(random.nextFloat()).thenReturn(0.89f);
+
+ when(mockResponse.getBody())
+ .thenReturn("{\"eids\": [{ \"source\": \""
+ + enrichedEid.getSource()
+ + "\", \"uids\": [{ \"atype\": "
+ + enrichedUid.getAtype() + ", \"id\" : \""
+ + enrichedUid.getId() + "\" }]}]}");
+
+ when(
+ httpClient.post(
+ eq(moduleConfig.getIdentityResolutionEndpoint()),
+ argThat(bearerAuthHeaderMatcher),
+ eq(jacksonMapper.encodeToString(bidRequest)),
+ eq(moduleConfig.getRequestTimeoutMs())
+ )
+ ).thenReturn(Future.succeededFuture(mockResponse));
+
+ final Future> future
+ = target.call(AuctionRequestPayloadImpl.of(bidRequest), auctionInvocationContext);
+ final InvocationResult result = future.result();
+
+ // then
+ assertThat(future.succeeded()).isTrue();
+ assertThat(result.status()).isEqualTo(InvocationStatus.success);
+ assertThat(result.action()).isEqualTo(InvocationAction.update);
+ assertThat(result.payloadUpdate()
+ .apply(AuctionRequestPayloadImpl.of(bidRequest))
+ .bidRequest()
+ .getUser()
+ .getEids()).isEqualTo(Collections.singletonList(enrichedEid));
+ }
+}
diff --git a/extra/modules/pom.xml b/extra/modules/pom.xml
index 10cfac79c6e..e74c8e4b0a8 100644
--- a/extra/modules/pom.xml
+++ b/extra/modules/pom.xml
@@ -26,6 +26,7 @@
pb-request-correction
optable-targeting
wurfl-devicedetection
+ live-intent-omni-channel-identity