From 4676497f36b8425825a212c53e2a872b92a1e918 Mon Sep 17 00:00:00 2001 From: Aleksandrs Ulme Date: Fri, 13 Jun 2025 19:18:38 +0800 Subject: [PATCH 1/4] V3 Identity Map test --- pom.xml | 2 +- src/test/java/app/component/Operator.java | 7 +++ .../java/suite/E2ELocalFullTestSuite.java | 6 +-- .../suite/E2EPrivateOperatorTestSuite.java | 4 +- .../suite/E2EPublicOperatorTestSuite.java | 6 +-- ...lyTest.java => OperatorLocalOnlyTest.java} | 4 +- ...yTest.java => OperatorPublicOnlyTest.java} | 2 +- ...ApiOperatorTest.java => OperatorTest.java} | 51 ++++++++++++++++++- src/test/java/suite/operator/TestData.java | 28 +++++++++- 9 files changed, 93 insertions(+), 17 deletions(-) rename src/test/java/suite/operator/{V2ApiOperatorLocalOnlyTest.java => OperatorLocalOnlyTest.java} (97%) rename src/test/java/suite/operator/{V2ApiOperatorPublicOnlyTest.java => OperatorPublicOnlyTest.java} (99%) rename src/test/java/suite/operator/{V2ApiOperatorTest.java => OperatorTest.java} (77%) diff --git a/pom.xml b/pom.xml index 109263a..75e4723 100644 --- a/pom.xml +++ b/pom.xml @@ -90,7 +90,7 @@ com.uid2 uid2-client - 4.3.22 + 4.6.8-alpha-29-SNAPSHOT org.assertj diff --git a/src/test/java/app/component/Operator.java b/src/test/java/app/component/Operator.java index 0c6fe44..3c731e2 100644 --- a/src/test/java/app/component/Operator.java +++ b/src/test/java/app/component/Operator.java @@ -25,6 +25,7 @@ import java.time.Clock; import java.time.Instant; import java.util.Base64; +import java.util.List; public class Operator extends App { public enum Type { @@ -266,6 +267,12 @@ public JsonNode v2IdentityMap(String payload) throws Exception { return v2DecryptEncryptedResponse(encryptedResponse, envelope.nonce(), getClientApiSecret()); } + public IdentityMapV3Response v3IdentityMap(List emails, List phones, List emailHashes, List phoneHashes) { + IdentityMapV3Client identityMapV3Client = new IdentityMapV3Client(getBaseUrl(), CLIENT_API_KEY, CLIENT_API_SECRET); + IdentityMapV3Input input = new IdentityMapV3Input().withEmails(emails).withPhones(phones).withHashedEmails(emailHashes).withHashedPhones(phoneHashes); + return identityMapV3Client.generateIdentityMap(input); + } + public JsonNode v2IdentityBuckets(String payload) throws Exception { V2Envelope envelope = v2CreateEnvelope(payload, CLIENT_API_SECRET); String encryptedResponse = HttpClient.post(getBaseUrl() + "/v2/identity/buckets", envelope.envelope(), CLIENT_API_KEY); diff --git a/src/test/java/suite/E2ELocalFullTestSuite.java b/src/test/java/suite/E2ELocalFullTestSuite.java index cc301d5..0e5c299 100644 --- a/src/test/java/suite/E2ELocalFullTestSuite.java +++ b/src/test/java/suite/E2ELocalFullTestSuite.java @@ -14,9 +14,9 @@ BasicTest.class, CoreTest.class, CoreRefreshTest.class, - V2ApiOperatorTest.class, - V2ApiOperatorPublicOnlyTest.class, - V2ApiOperatorLocalOnlyTest.class, + OperatorTest.class, + OperatorPublicOnlyTest.class, + OperatorLocalOnlyTest.class, OptoutTest.class, V2ApiValidatorTest.class }) diff --git a/src/test/java/suite/E2EPrivateOperatorTestSuite.java b/src/test/java/suite/E2EPrivateOperatorTestSuite.java index e290d28..775be7e 100644 --- a/src/test/java/suite/E2EPrivateOperatorTestSuite.java +++ b/src/test/java/suite/E2EPrivateOperatorTestSuite.java @@ -3,12 +3,12 @@ import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; import suite.basic.BasicTest; -import suite.operator.V2ApiOperatorTest; +import suite.operator.OperatorTest; @Suite @SelectClasses({ BasicTest.class, - V2ApiOperatorTest.class + OperatorTest.class }) public class E2EPrivateOperatorTestSuite { } diff --git a/src/test/java/suite/E2EPublicOperatorTestSuite.java b/src/test/java/suite/E2EPublicOperatorTestSuite.java index 7a483d5..93735f4 100644 --- a/src/test/java/suite/E2EPublicOperatorTestSuite.java +++ b/src/test/java/suite/E2EPublicOperatorTestSuite.java @@ -13,9 +13,9 @@ BasicTest.class, CoreTest.class, CoreRefreshTest.class, - V2ApiOperatorTest.class, - V2ApiOperatorPublicOnlyTest.class, - V2ApiOperatorLocalOnlyTest.class, + OperatorTest.class, + OperatorPublicOnlyTest.class, + OperatorLocalOnlyTest.class, OptoutTest.class }) public class E2EPublicOperatorTestSuite { diff --git a/src/test/java/suite/operator/V2ApiOperatorLocalOnlyTest.java b/src/test/java/suite/operator/OperatorLocalOnlyTest.java similarity index 97% rename from src/test/java/suite/operator/V2ApiOperatorLocalOnlyTest.java rename to src/test/java/suite/operator/OperatorLocalOnlyTest.java index 2c3aa9d..401c0da 100644 --- a/src/test/java/suite/operator/V2ApiOperatorLocalOnlyTest.java +++ b/src/test/java/suite/operator/OperatorLocalOnlyTest.java @@ -3,8 +3,6 @@ import app.component.App; import app.component.Operator; import com.uid2.client.*; -import common.Const; -import common.EnvUtil; import org.junit.jupiter.api.condition.EnabledIf; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -18,7 +16,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; @EnabledIf("common.EnabledCondition#isLocal") -public class V2ApiOperatorLocalOnlyTest { +public class OperatorLocalOnlyTest { @ParameterizedTest(name = "{index} ==> Sender {0} encrypts with {1}, recipient {2} decrypts with {3}, expected result is {4}") @MethodSource({ "suite.operator.TestData#sharingArgs", diff --git a/src/test/java/suite/operator/V2ApiOperatorPublicOnlyTest.java b/src/test/java/suite/operator/OperatorPublicOnlyTest.java similarity index 99% rename from src/test/java/suite/operator/V2ApiOperatorPublicOnlyTest.java rename to src/test/java/suite/operator/OperatorPublicOnlyTest.java index ef451c6..8d5527d 100644 --- a/src/test/java/suite/operator/V2ApiOperatorPublicOnlyTest.java +++ b/src/test/java/suite/operator/OperatorPublicOnlyTest.java @@ -13,7 +13,7 @@ import static org.junit.jupiter.api.Assertions.*; @SuppressWarnings("unused") -public class V2ApiOperatorPublicOnlyTest { +public class OperatorPublicOnlyTest { private static final String EMAIL_OPTOUT_ID = "optout@unifiedid.com"; private static final String PHONE_OPTOUT_ID = "+00000000001"; diff --git a/src/test/java/suite/operator/V2ApiOperatorTest.java b/src/test/java/suite/operator/OperatorTest.java similarity index 77% rename from src/test/java/suite/operator/V2ApiOperatorTest.java rename to src/test/java/suite/operator/OperatorTest.java index 3800af1..173167b 100644 --- a/src/test/java/suite/operator/V2ApiOperatorTest.java +++ b/src/test/java/suite/operator/OperatorTest.java @@ -11,13 +11,15 @@ import org.junit.jupiter.params.provider.MethodSource; import java.time.Duration; +import java.time.Instant; import java.util.List; +import java.util.Objects; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; @SuppressWarnings("unused") -public class V2ApiOperatorTest { +public class OperatorTest { /* TODO: /v2/token/generate - Add failure case @@ -31,6 +33,7 @@ public class V2ApiOperatorTest { private static final ObjectMapper OBJECT_MAPPER = Mapper.getInstance(); private static final String CLIENT_SITE_ID = EnvUtil.getEnv(Const.Config.Operator.CLIENT_SITE_ID); + private static final int RAW_UID_SIZE = 44; @ParameterizedTest(name = "/v2/token/generate - {0} - {2}") @MethodSource({ @@ -113,7 +116,7 @@ public void testV2IdentityMap(String label, Operator operator, String operatorNa "suite.operator.TestData#identityMapBigBatchArgs" }) public void testV2IdentityMapLargeBatch(String label, Operator operator, String operatorName, String payload, List diis) { - assertTimeoutPreemptively(Duration.ofSeconds(5), () -> { // Validate we didn't make mapping too slow. + assertTimeoutPreemptively(Duration.ofSeconds(5), () -> { // Validate we didn't make mapping too slow. JsonNode response = operator.v2IdentityMap(payload); assertThat(response.at("/status").asText()).isEqualTo("success"); @@ -129,6 +132,50 @@ public void testV2IdentityMapLargeBatch(String label, Operator operator, String }); } + @ParameterizedTest(name = "/v3/identity/map - {0} - {2}") + @MethodSource({ + "suite.operator.TestData#identityMapV3BigBatchArgs" + }) + public void testV3IdentityMapLargeBatch( + String label, + Operator operator, + String operatorName, + List emails, + List phones, + List emailHashes, + List phoneHashes, + List diis + ) { + assertTimeoutPreemptively(Duration.ofSeconds(5), () -> { // Validate we didn't make mapping too slow. + var response = operator.v3IdentityMap(emails, phones, emailHashes, phoneHashes); + + assertThat(response.isSuccess()).isTrue(); + + var allMappedDiis = response.getMappedIdentities(); + assertThat(allMappedDiis.size()).isEqualTo(10_000); + + + for (var dii : diis) { + var mappedDii = allMappedDiis.get(dii); + assertThat(mappedDii).isNotNull(); + + // Current UID should always be there and should have correct length + assertThat(mappedDii.getCurrentRawUid().length()).isEqualTo(RAW_UID_SIZE); + + // Previous UID is there for 90 days after rotation only, then it's null. + // If it's there, it should have the correct size + assertThat(mappedDii.getPreviousRawUid()).satisfiesAnyOf( + uid -> assertThat(uid).isNull(), + uid -> assertThat(uid).hasSize(RAW_UID_SIZE) + ); + + // Sanity check that refresh from is a date not too far in the past. + // If it is, either there is an Operator issue or salt rotation hasn't been running for a long time. + assertThat(mappedDii.getRefreshFrom()).isAfter(Instant.now().minus(Duration.ofDays(10))); + } + }); + } + @ParameterizedTest(name = "/v2/identity/map - VALIDATE EMAIL - {0} - {2}") @MethodSource({ diff --git a/src/test/java/suite/operator/TestData.java b/src/test/java/suite/operator/TestData.java index bee844e..85203fb 100644 --- a/src/test/java/suite/operator/TestData.java +++ b/src/test/java/suite/operator/TestData.java @@ -103,8 +103,8 @@ public static Set tokenPhoneArgsSpecialOptout() { Set args = new HashSet<>(); if (App.PHONE_SUPPORT) { for (Operator operator : operators) { - args.add(Arguments.of("optout special phone", operator, operator.getName(), "phone", "+00000000000", true)); - args.add(Arguments.of("optout special phone", operator, operator.getName(), "phone", "+00000000000", false)); + args.add(Arguments.of("optout special phone", operator, operator.getName(), "phone", "+00000000000", true)); + args.add(Arguments.of("optout special phone", operator, operator.getName(), "phone", "+00000000000", false)); } } return args; @@ -356,6 +356,30 @@ public static Set identityMapBigBatchArgs() throws JsonProcessingExce return args; } + public static Set identityMapV3BigBatchArgs() { + Set operators = AppsMap.getApps(Operator.class); + + List emails = new ArrayList<>(); + List phones = new ArrayList<>(); + List emailHashes = new ArrayList<>(); + List phoneHashes = new ArrayList<>(); + for (int i = 0; i < 10_000; i++) { + emails.add(randomEmail()); + phones.add(randomPhoneNumber()); + emailHashes.add(randomHash()); + phoneHashes.add(randomHash()); + } + + Set args = new HashSet<>(); + for (Operator operator : operators) { + args.add(Arguments.of("10k emails", operator, operator.getName(), emails, List.of(), List.of(), List.of(), emails)); + args.add(Arguments.of("10k phones", operator, operator.getName(), List.of(), phones, List.of(), List.of(), phones)); + args.add(Arguments.of("10k email hashes", operator, operator.getName(), List.of(), List.of(), emailHashes, List.of(), emailHashes)); + args.add(Arguments.of("10k phone hashes", operator, operator.getName(), List.of(), List.of(), List.of(), phoneHashes, phoneHashes)); + } + return args; + } + private static String identityMapPayload(ObjectMapper mapper, String field, List diis) throws JsonProcessingException { var json = mapper.createObjectNode().putPOJO(field, diis).put("policy", 1); return mapper.writeValueAsString(json); From 6a4d365e82dd08962365601fdcd396e30b32d521 Mon Sep 17 00:00:00 2001 From: Aleksandrs Ulme Date: Mon, 16 Jun 2025 19:46:14 +0800 Subject: [PATCH 2/4] More tests for V3 Identity Map --- src/test/java/app/component/Operator.java | 16 +- src/test/java/app/component/Validator.java | 7 - .../operator/OperatorPublicOnlyTest.java | 20 ++ .../java/suite/operator/OperatorTest.java | 72 ++++--- src/test/java/suite/operator/TestData.java | 179 ++++++++++++------ 5 files changed, 201 insertions(+), 93 deletions(-) diff --git a/src/test/java/app/component/Operator.java b/src/test/java/app/component/Operator.java index 3c731e2..c6fd4dd 100644 --- a/src/test/java/app/component/Operator.java +++ b/src/test/java/app/component/Operator.java @@ -261,15 +261,27 @@ public DecryptionResponse v2TokenDecrypt(String token) throws UID2ClientExceptio return dspClient.decrypt(token); } + // Need to use the manual mapping for error cases - SDK won't allow creating input with bad emails or disable optout check public JsonNode v2IdentityMap(String payload) throws Exception { V2Envelope envelope = v2CreateEnvelope(payload, getClientApiSecret()); String encryptedResponse = HttpClient.post(getBaseUrl() + "/v2/identity/map", envelope.envelope(), getClientApiKey()); return v2DecryptEncryptedResponse(encryptedResponse, envelope.nonce(), getClientApiSecret()); } - public IdentityMapV3Response v3IdentityMap(List emails, List phones, List emailHashes, List phoneHashes) { + public IdentityMapResponse v2IdentityMap(IdentityMapInput input) { + IdentityMapClient identityMapClient = new IdentityMapClient(getBaseUrl(), CLIENT_API_KEY, CLIENT_API_SECRET); + return identityMapClient.generateIdentityMap(input); + } + + // Need to use the manual mapping for error cases - SDK won't allow creating input with bad emails + public JsonNode v3IdentityMap(String payload) throws Exception { + V2Envelope envelope = v2CreateEnvelope(payload, getClientApiSecret()); + String encryptedResponse = HttpClient.post(getBaseUrl() + "/v3/identity/map", envelope.envelope(), getClientApiKey()); + return v2DecryptEncryptedResponse(encryptedResponse, envelope.nonce(), getClientApiSecret()); + } + + public IdentityMapV3Response v3IdentityMap(IdentityMapV3Input input) { IdentityMapV3Client identityMapV3Client = new IdentityMapV3Client(getBaseUrl(), CLIENT_API_KEY, CLIENT_API_SECRET); - IdentityMapV3Input input = new IdentityMapV3Input().withEmails(emails).withPhones(phones).withHashedEmails(emailHashes).withHashedPhones(phoneHashes); return identityMapV3Client.generateIdentityMap(input); } diff --git a/src/test/java/app/component/Validator.java b/src/test/java/app/component/Validator.java index 9ea4bee..ccc3a3f 100644 --- a/src/test/java/app/component/Validator.java +++ b/src/test/java/app/component/Validator.java @@ -8,7 +8,6 @@ public class Validator extends App { private final PublisherUid2Helper publisherHelper; - private final UID2Client dspClient; private final Headers standardHeaders; private final MediaType FORM = MediaType.get("application/x-www-form-urlencoded");; @@ -20,12 +19,6 @@ public Validator(String host, Integer port, String name, String clientApiKey, St .add("Authorization", "Bearer " + clientApiKey) .add("X-UID2-Client-Version: java-e2e-test") .build(); - - dspClient = new UID2Client( - this.getBaseUrl(), - clientApiKey, - clientSecret, - IdentityScope.UID2); } public Response triggerGenerateTokenFromEmail(String email) throws IOException { diff --git a/src/test/java/suite/operator/OperatorPublicOnlyTest.java b/src/test/java/suite/operator/OperatorPublicOnlyTest.java index 8d5527d..ebd6c2a 100644 --- a/src/test/java/suite/operator/OperatorPublicOnlyTest.java +++ b/src/test/java/suite/operator/OperatorPublicOnlyTest.java @@ -9,6 +9,8 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; @@ -81,6 +83,24 @@ public void testV2IdentityMapSpecialOptoutNoParam(String label, Operator operato assertThat(response.get("body").get("unmapped").get(0).get("reason").asText()).isEqualTo("optout"); } + @ParameterizedTest(name = "/v3/identity/map - OPTOUT EMAIL, NO OPTOUT PARAM - {0} - {2} - Old Participant: {5}") + @MethodSource({ + "suite.operator.TestData#tokenEmailArgsSpecialOptout", + "suite.operator.TestData#tokenPhoneArgsSpecialOptout" + }) + public void testV3IdentityMapSpecialOptout(String label, Operator operator, String operatorName, String type, String identity) throws Exception { + if (isPrivateOperator(operator)) { + return; + } + + // We need all properties to be there for Identity Map V3, so default all to empty + // In JSON if a property appears multiple times, the last value wins + String payload = "{\"email\":[], \"email_hash\":[], \"phone\":[], \"phone_hash\":[], \"" + type + "\": [\"" + identity + "\"]}"; + JsonNode response = operator.v3IdentityMap(payload); + + assertThat(response.get("body").get(type).get(0).get("e").asText()).isEqualTo("optout"); + } + @EnabledIf("common.EnabledCondition#isLocal") @ParameterizedTest(name = "/v2/token/client-generate - {0} - {2}") @MethodSource({ diff --git a/src/test/java/suite/operator/OperatorTest.java b/src/test/java/suite/operator/OperatorTest.java index 173167b..c569d04 100644 --- a/src/test/java/suite/operator/OperatorTest.java +++ b/src/test/java/suite/operator/OperatorTest.java @@ -13,7 +13,6 @@ import java.time.Duration; import java.time.Instant; import java.util.List; -import java.util.Objects; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; @@ -99,62 +98,75 @@ public void testV2TokenValidate(String label, Operator operator, String operator @ParameterizedTest(name = "/v2/identity/map - {0} - {2}") @MethodSource({ - "suite.operator.TestData#identityMapBatchEmailArgs", - "suite.operator.TestData#identityMapBatchPhoneArgs", "suite.operator.TestData#identityMapBatchBadEmailArgs", "suite.operator.TestData#identityMapBatchBadPhoneArgs" }) - public void testV2IdentityMap(String label, Operator operator, String operatorName, String payload) throws Exception { + public void testV2IdentityMapUnmapped(String label, Operator operator, String operatorName, String payload) throws Exception { JsonNode response = operator.v2IdentityMap(payload); - // TODO: Assert the value assertThat(response.at("/status").asText()).isEqualTo("success"); + assertThat(response.at("/body/unmapped/0/reason").asText()).isEqualTo("invalid identifier"); } @ParameterizedTest(name = "/v2/identity/map - {0} - {2}") @MethodSource({ - "suite.operator.TestData#identityMapBigBatchArgs" + "suite.operator.TestData#identityMapBatchEmailArgs", + "suite.operator.TestData#identityMapBatchPhoneArgs", }) - public void testV2IdentityMapLargeBatch(String label, Operator operator, String operatorName, String payload, List diis) { + public void testV2IdentityMapMapped(String label, Operator operator, String operatorName, String payload) throws Exception { + JsonNode response = operator.v2IdentityMap(payload); + + // TODO: Assert the value + assertThat(response.at("/status").asText()).isEqualTo("success"); + } + + @ParameterizedTest(name = "/v2/identity/map - {0} - {2}") + @MethodSource({"suite.operator.TestData#identityMapArgs"}) + public void testV2IdentityMap( + String label, + Operator operator, + String operatorName, + IdentityMapInput input, + List diis + ) { assertTimeoutPreemptively(Duration.ofSeconds(5), () -> { // Validate we didn't make mapping too slow. - JsonNode response = operator.v2IdentityMap(payload); + var response = operator.v2IdentityMap(input); - assertThat(response.at("/status").asText()).isEqualTo("success"); + assertThat(response.isSuccess()).isTrue(); - var mapped = response.at("/body/mapped"); - assertThat(mapped.size()).isEqualTo(10_000); + assertThat(response.getUnmappedIdentities()).isEmpty(); - for (int i = 0; i < 10_000; i++) { - assertThat(mapped.get(i).get("identifier").asText()).isEqualTo(diis.get(i)); - assertThat(mapped.get(i).get("advertising_id").asText()).isNotNull().isNotEmpty(); - assertThat(mapped.get(i).get("bucket_id").asText()).isNotNull().isNotEmpty(); + var allMappedDiis = response.getMappedIdentities(); + assertThat(allMappedDiis.size()).isEqualTo(10_000); + + for (var dii : diis) { + var mappedDii = allMappedDiis.get(dii); + assertThat(mappedDii).isNotNull(); + assertThat(mappedDii.getRawUid().length()).isEqualTo(RAW_UID_SIZE); + assertThat(mappedDii.getBucketId()).isNotBlank(); } }); } @ParameterizedTest(name = "/v3/identity/map - {0} - {2}") - @MethodSource({ - "suite.operator.TestData#identityMapV3BigBatchArgs" - }) + @MethodSource({"suite.operator.TestData#identityMapV3Args"}) public void testV3IdentityMapLargeBatch( String label, Operator operator, String operatorName, - List emails, - List phones, - List emailHashes, - List phoneHashes, + IdentityMapV3Input input, List diis ) { assertTimeoutPreemptively(Duration.ofSeconds(5), () -> { // Validate we didn't make mapping too slow. - var response = operator.v3IdentityMap(emails, phones, emailHashes, phoneHashes); + var response = operator.v3IdentityMap(input); assertThat(response.isSuccess()).isTrue(); + assertThat(response.getUnmappedIdentities()).isEmpty(); + var allMappedDiis = response.getMappedIdentities(); assertThat(allMappedDiis.size()).isEqualTo(10_000); - for (var dii : diis) { var mappedDii = allMappedDiis.get(dii); assertThat(mappedDii).isNotNull(); @@ -176,6 +188,18 @@ public void testV3IdentityMapLargeBatch( }); } + @ParameterizedTest(name = "/v3/identity/map - {0} - {2}") + @MethodSource({ + "suite.operator.TestData#identityMapV3BatchBadEmailArgs", + "suite.operator.TestData#identityMapV3BatchBadPhoneArgs" + }) + public void testV3IdentityMapUnmapped(String label, Operator operator, String operatorName, String payload, String section) throws Exception { + JsonNode response = operator.v3IdentityMap(payload); + + assertThat(response.at("/status").asText()).isEqualTo("success"); + assertThat(response.at("/body/" + section + "/0/e").asText()).isEqualTo("invalid identifier"); + } + @ParameterizedTest(name = "/v2/identity/map - VALIDATE EMAIL - {0} - {2}") @MethodSource({ diff --git a/src/test/java/suite/operator/TestData.java b/src/test/java/suite/operator/TestData.java index 85203fb..e55fbb3 100644 --- a/src/test/java/suite/operator/TestData.java +++ b/src/test/java/suite/operator/TestData.java @@ -3,9 +3,9 @@ import app.AppsMap; import app.component.App; import app.component.Operator; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import com.uid2.client.DecryptionStatus; +import com.uid2.client.IdentityMapInput; +import com.uid2.client.IdentityMapV3Input; import org.junit.jupiter.params.provider.Arguments; import java.util.*; @@ -268,42 +268,43 @@ public static Set identityMapPhoneArgs() { return args; } - public static Set identityMapBadEmailArgs() { + public static Set identityMapBatchBadEmailArgs() { Set operators = AppsMap.getApps(Operator.class); Set> inputs = Set.of( - List.of("empty email", "email", ""), - List.of("empty email hash", "email_hash", ""), - List.of("bad email", "email", "abc"), - List.of("bad email hash", "email_hash", "abc") + List.of("bad email list policy=1", "{\"email\":[\"abc\",\"user2@example.com\"], \"policy\":1}"), + List.of("bad email hash list policy=1", "{\"email_hash\":[\"eVvLS/Vg+YZ6+z3i0NOpSXYyQAfEXqCZ7BTpAjFUBUc=\",\"abc\"], \"policy\":1}"), + List.of("bad email list optout_check=1", "{\"email\":[\"abc\",\"user2@example.com\"], \"optout_check\":1}"), + List.of("bad email hash list optout_check=1", "{\"email_hash\":[\"eVvLS/Vg+YZ6+z3i0NOpSXYyQAfEXqCZ7BTpAjFUBUc=\",\"abc\"], \"optout_check\":1}") ); Set args = new HashSet<>(); for (Operator operator : operators) { for (List input : inputs) { - args.add(Arguments.of(input.get(0), operator, operator.getName(), input.get(1), input.get(2))); + args.add(Arguments.of(input.get(0), operator, operator.getName(), input.get(1))); } } return args; } - public static Set identityMapBadPhoneArgs() { + public static Set identityMapBatchBadPhoneArgs() { Set operators = AppsMap.getApps(Operator.class); Set> inputs = Set.of( - List.of("empty phone", "phone", ""), - List.of("empty phone hash", "phone_hash", ""), - List.of("bad phone", "phone", "abc"), - List.of("bad phone hash", "phone_hash", "abc") + List.of("bad phone list policy=1", "{\"phone\":[\"+1111111111\",\"abc\"], \"policy\":1}"), + List.of("bad phone hash list policy=1", "{\"phone_hash\":[\"abc\",\"tMmiiTI7IaAcPpQPFQ65uMVCWH8av9jw4cwf/F5HVRQ=\"], \"policy\":1}"), + List.of("bad phone list optout_check=1", "{\"phone\":[\"+1111111111\",\"abc\"], \"optout_check\":1}"), + List.of("bad phone hash list optout_check=1", "{\"phone_hash\":[\"abc\",\"tMmiiTI7IaAcPpQPFQ65uMVCWH8av9jw4cwf/F5HVRQ=\"], \"optout_check\":1}") ); Set args = new HashSet<>(); - for (Operator operator : operators) { - for (List input : inputs) { - args.add(Arguments.of(input.get(0), operator, operator.getName(), input.get(1), input.get(2))); + if (App.PHONE_SUPPORT) { + for (Operator operator : operators) { + for (List input : inputs) { + args.add(Arguments.of(input.get(0), operator, operator.getName(), input.get(1))); + } } } return args; } - public static Set identityMapBatchEmailArgs() { Set operators = AppsMap.getApps(Operator.class); Set> inputs = Set.of( @@ -326,8 +327,7 @@ public static Set identityMapBatchEmailArgs() { return args; } - public static Set identityMapBigBatchArgs() throws JsonProcessingException { - ObjectMapper mapper = new ObjectMapper(); + public static Set identityMapArgs() { Set operators = AppsMap.getApps(Operator.class); List emails = new ArrayList<>(); @@ -341,50 +341,103 @@ public static Set identityMapBigBatchArgs() throws JsonProcessingExce phoneHashes.add(randomHash()); } - var emailsPayload = identityMapPayload(mapper, "email", emails); - var phonesPayload = identityMapPayload(mapper, "phone", phones); - var emailHashesPayload = identityMapPayload(mapper, "email_hash", emailHashes); - var phoneHashesPayload = identityMapPayload(mapper, "phone_hash", phoneHashes); - Set args = new HashSet<>(); for (Operator operator : operators) { - args.add(Arguments.of("10k emails", operator, operator.getName(), emailsPayload, emails)); - args.add(Arguments.of("10k phones", operator, operator.getName(), phonesPayload, phones)); - args.add(Arguments.of("10k email hashes", operator, operator.getName(), emailHashesPayload, emailHashes)); - args.add(Arguments.of("10k phone hashes", operator, operator.getName(), phoneHashesPayload, phoneHashes)); + args.add(Arguments.of("10k emails", operator, operator.getName(), IdentityMapInput.fromEmails(emails), emails)); + args.add(Arguments.of("10k phones", operator, operator.getName(), IdentityMapInput.fromPhones(phones), phones)); + args.add(Arguments.of("10k email hashes", operator, operator.getName(), IdentityMapInput.fromHashedEmails(emailHashes), emailHashes)); + args.add(Arguments.of("10k phone hashes", operator, operator.getName(), IdentityMapInput.fromHashedPhones(phoneHashes), phoneHashes)); } return args; } - public static Set identityMapV3BigBatchArgs() { + public static Set identityMapV3Args() { Set operators = AppsMap.getApps(Operator.class); List emails = new ArrayList<>(); List phones = new ArrayList<>(); List emailHashes = new ArrayList<>(); List phoneHashes = new ArrayList<>(); + List mixedDIIs = new ArrayList<>(); + + IdentityMapV3Input emailInput = IdentityMapV3Input.fromEmails(emails); + IdentityMapV3Input phoneInput = IdentityMapV3Input.fromPhones(phones); + IdentityMapV3Input emailHashInput = IdentityMapV3Input.fromHashedEmails(emailHashes); + IdentityMapV3Input phoneHashInput = IdentityMapV3Input.fromHashedPhones(phoneHashes); + IdentityMapV3Input mixedInput = IdentityMapV3Input.fromHashedPhones(phoneHashes); for (int i = 0; i < 10_000; i++) { - emails.add(randomEmail()); - phones.add(randomPhoneNumber()); - emailHashes.add(randomHash()); - phoneHashes.add(randomHash()); + String email = randomEmail(); + String phone = randomPhoneNumber(); + String emailHash = randomHash(); + String phoneHash = randomHash(); + + emails.add(email); + phones.add(phone); + emailHashes.add(emailHash); + phoneHashes.add(phoneHash); + + emailInput.withEmail(email); + phoneInput.withPhone(phone); + emailHashInput.withHashedEmail(emailHash); + phoneHashInput.withHashedPhone(phoneHash); + + if (i < 2_500) { // all 4 DII types in the same collection, so we only need 2.5k of each + mixedDIIs.add(email); + mixedDIIs.add(phone); + mixedDIIs.add(emailHash); + mixedDIIs.add(phoneHash); + + mixedInput.withEmail(email).withPhone(phone).withHashedEmail(emailHash).withHashedPhone(phoneHash); + } + + } + + Set args = new HashSet<>(); + for (Operator operator : operators) { + args.add(Arguments.of("10k emails", operator, operator.getName(), emailInput, emails)); + args.add(Arguments.of("10k phones", operator, operator.getName(), phoneInput, phones)); + args.add(Arguments.of("10k email hashes", operator, operator.getName(), emailHashInput, emailHashes)); + args.add(Arguments.of("10k phone hashes", operator, operator.getName(), phoneHashInput, phoneHashes)); + args.add(Arguments.of("10k mixed DIIs", operator, operator.getName(), mixedInput, mixedDIIs)); } + return args; + } + + public static Set identityMapV3BatchBadEmailArgs() { + Set operators = AppsMap.getApps(Operator.class); + Set> inputs = Set.of( + List.of("bad email", "{\"email\":[\"abc\"], \"email_hash\":[], \"phone\":[], \"phone_hash\":[]}", "email"), + List.of("bad email hash", "{\"email\":[], \"email_hash\":[\"abc\"], \"phone\":[], \"phone_hash\":[]}", "email_hash") + ); Set args = new HashSet<>(); for (Operator operator : operators) { - args.add(Arguments.of("10k emails", operator, operator.getName(), emails, List.of(), List.of(), List.of(), emails)); - args.add(Arguments.of("10k phones", operator, operator.getName(), List.of(), phones, List.of(), List.of(), phones)); - args.add(Arguments.of("10k email hashes", operator, operator.getName(), List.of(), List.of(), emailHashes, List.of(), emailHashes)); - args.add(Arguments.of("10k phone hashes", operator, operator.getName(), List.of(), List.of(), List.of(), phoneHashes, phoneHashes)); + for (List input : inputs) { + args.add(Arguments.of(input.get(0), operator, operator.getName(), input.get(1), input.get(2))); + } } return args; } - private static String identityMapPayload(ObjectMapper mapper, String field, List diis) throws JsonProcessingException { - var json = mapper.createObjectNode().putPOJO(field, diis).put("policy", 1); - return mapper.writeValueAsString(json); + public static Set identityMapV3BatchBadPhoneArgs() { + Set operators = AppsMap.getApps(Operator.class); + Set> inputs = Set.of( + List.of("bad phone", "{\"email\":[], \"email_hash\":[], \"phone\":[\"abc\"], \"phone_hash\":[]}", "phone"), + List.of("bad phone hash", "{\"email\":[], \"email_hash\":[], \"phone\":[], \"phone_hash\":[\"abc\"]}", "phone_hash") + ); + + Set args = new HashSet<>(); + if (App.PHONE_SUPPORT) { + for (Operator operator : operators) { + for (List input : inputs) { + args.add(Arguments.of(input.get(0), operator, operator.getName(), input.get(1), input.get(2))); + } + } + } + return args; } + private static String randomEmail() { return "email_" + Math.abs(RANDOM.nextLong()) + "@example.com"; } @@ -395,7 +448,7 @@ private static String randomPhoneNumber() { } private static String randomHash() { - // This isn't really a hashed DII but looks like one ot UID2 + // This isn't really a hashed DII but looks like one to UID2 byte[] randomBytes = new byte[32]; RANDOM.nextBytes(randomBytes); return Base64.getEncoder().encodeToString(randomBytes); @@ -541,39 +594,45 @@ public static Set identityMapBatchPhoneArgsBadPolicy() { return args; } - public static Set identityMapBatchBadEmailArgs() { + public static Set identityMapBadInputArgs() { Set operators = AppsMap.getApps(Operator.class); - Set> inputs = Set.of( - List.of("bad email list policy=1", "{\"email\":[\"abc\",\"user2@example.com\"], \"policy\":1}"), - List.of("bad email hash list policy=1", "{\"email_hash\":[\"eVvLS/Vg+YZ6+z3i0NOpSXYyQAfEXqCZ7BTpAjFUBUc=\",\"abc\"], \"policy\":1}"), - List.of("bad email list optout_check=1", "{\"email\":[\"abc\",\"user2@example.com\"], \"optout_check\":1}"), - List.of("bad email hash list optout_check=1", "{\"email_hash\":[\"eVvLS/Vg+YZ6+z3i0NOpSXYyQAfEXqCZ7BTpAjFUBUc=\",\"abc\"], \"optout_check\":1}") - ); + + List badEmails = List.of("abc", "user2@example.com"); + List badEmailHashes = List.of("eVvLS/Vg+YZ6+z3i0NOpSXYyQAfEXqCZ7BTpAjFUBUc=", "abc"); Set args = new HashSet<>(); for (Operator operator : operators) { - for (List input : inputs) { - args.add(Arguments.of(input.get(0), operator, operator.getName(), input.get(1))); + args.add(Arguments.of("bad emails", operator, operator.getName(), IdentityMapInput.fromEmails(badEmails), badEmails)); + args.add(Arguments.of("bad email hashes", operator, operator.getName(), IdentityMapInput.fromHashedEmails(badEmailHashes), badEmailHashes)); + } + + List badPhones = List.of("+1111111111", "abc"); + List badPhoneHashes = List.of("abc", "tMmiiTI7IaAcPpQPFQ65uMVCWH8av9jw4cwf/F5HVRQ="); + + if (App.PHONE_SUPPORT) { + for (Operator operator : operators) { + args.add(Arguments.of("bad phones", operator, operator.getName(), IdentityMapInput.fromPhones(badPhones), badPhones)); + args.add(Arguments.of("bad phone hashes", operator, operator.getName(), IdentityMapInput.fromHashedPhones(badPhoneHashes), badPhoneHashes)); } } return args; } - public static Set identityMapBatchBadPhoneArgs() { + public static Set identityMapV3BadInputArgs() { Set operators = AppsMap.getApps(Operator.class); - Set> inputs = Set.of( - List.of("bad phone list policy=1", "{\"phone\":[\"+1111111111\",\"abc\"], \"policy\":1}"), - List.of("bad phone hash list policy=1", "{\"phone_hash\":[\"abc\",\"tMmiiTI7IaAcPpQPFQ65uMVCWH8av9jw4cwf/F5HVRQ=\"], \"policy\":1}"), - List.of("bad phone list optout_check=1", "{\"phone\":[\"+1111111111\",\"abc\"], \"optout_check\":1}"), - List.of("bad phone hash list optout_check=1", "{\"phone_hash\":[\"abc\",\"tMmiiTI7IaAcPpQPFQ65uMVCWH8av9jw4cwf/F5HVRQ=\"], \"optout_check\":1}") - ); + + List badEmailHashes = List.of("eVvLS/Vg+YZ6+z3i0NOpSXYyQAfEXqCZ7BTpAjFUBUc=", "abc"); Set args = new HashSet<>(); + for (Operator operator : operators) { + args.add(Arguments.of("bad email hashes", operator, operator.getName(), IdentityMapV3Input.fromHashedEmails(badEmailHashes), badEmailHashes)); + } + + List badPhoneHashes = List.of("abc", "tMmiiTI7IaAcPpQPFQ65uMVCWH8av9jw4cwf/F5HVRQ="); + if (App.PHONE_SUPPORT) { for (Operator operator : operators) { - for (List input : inputs) { - args.add(Arguments.of(input.get(0), operator, operator.getName(), input.get(1))); - } + args.add(Arguments.of("bad phone hashes", operator, operator.getName(), IdentityMapV3Input.fromHashedPhones(badPhoneHashes), badPhoneHashes)); } } return args; From c0b6854ac0b3da00cc5ff9f8bfb67036767803c9 Mon Sep 17 00:00:00 2001 From: Aleksandrs Ulme Date: Tue, 24 Jun 2025 15:31:39 +0800 Subject: [PATCH 3/4] Addressing feedback --- src/test/java/app/component/Operator.java | 21 ++++++------------- .../operator/OperatorPublicOnlyTest.java | 4 +--- .../java/suite/operator/OperatorTest.java | 6 +++--- 3 files changed, 10 insertions(+), 21 deletions(-) diff --git a/src/test/java/app/component/Operator.java b/src/test/java/app/component/Operator.java index c6fd4dd..493a123 100644 --- a/src/test/java/app/component/Operator.java +++ b/src/test/java/app/component/Operator.java @@ -25,7 +25,6 @@ import java.time.Clock; import java.time.Instant; import java.util.Base64; -import java.util.List; public class Operator extends App { public enum Type { @@ -263,9 +262,9 @@ public DecryptionResponse v2TokenDecrypt(String token) throws UID2ClientExceptio // Need to use the manual mapping for error cases - SDK won't allow creating input with bad emails or disable optout check public JsonNode v2IdentityMap(String payload) throws Exception { - V2Envelope envelope = v2CreateEnvelope(payload, getClientApiSecret()); - String encryptedResponse = HttpClient.post(getBaseUrl() + "/v2/identity/map", envelope.envelope(), getClientApiKey()); - return v2DecryptEncryptedResponse(encryptedResponse, envelope.nonce(), getClientApiSecret()); + V2Envelope envelope = v2CreateEnvelope(payload, CLIENT_API_SECRET); + String encryptedResponse = HttpClient.post(getBaseUrl() + "/v2/identity/map", envelope.envelope(), CLIENT_API_KEY); + return v2DecryptEncryptedResponse(encryptedResponse, envelope.nonce(), CLIENT_API_SECRET); } public IdentityMapResponse v2IdentityMap(IdentityMapInput input) { @@ -275,9 +274,9 @@ public IdentityMapResponse v2IdentityMap(IdentityMapInput input) { // Need to use the manual mapping for error cases - SDK won't allow creating input with bad emails public JsonNode v3IdentityMap(String payload) throws Exception { - V2Envelope envelope = v2CreateEnvelope(payload, getClientApiSecret()); - String encryptedResponse = HttpClient.post(getBaseUrl() + "/v3/identity/map", envelope.envelope(), getClientApiKey()); - return v2DecryptEncryptedResponse(encryptedResponse, envelope.nonce(), getClientApiSecret()); + V2Envelope envelope = v2CreateEnvelope(payload, CLIENT_API_SECRET); + String encryptedResponse = HttpClient.post(getBaseUrl() + "/v3/identity/map", envelope.envelope(), CLIENT_API_KEY); + return v2DecryptEncryptedResponse(encryptedResponse, envelope.nonce(), CLIENT_API_SECRET); } public IdentityMapV3Response v3IdentityMap(IdentityMapV3Input input) { @@ -303,14 +302,6 @@ public JsonNode v2KeySharing() throws Exception { return v2DecryptEncryptedResponse(encryptedResponse, envelope.nonce(), CLIENT_API_SECRET); } - private String getClientApiKey() { - return CLIENT_API_KEY; - } - - private String getClientApiSecret() { - return CLIENT_API_SECRET; - } - private V2Envelope v2CreateEnvelope(String payload, String secret) throws Exception { // Unencrypted envelope payload = timestamp + nonce + raw payload Instant timestamp = Instant.now(); diff --git a/src/test/java/suite/operator/OperatorPublicOnlyTest.java b/src/test/java/suite/operator/OperatorPublicOnlyTest.java index ebd6c2a..34dbac1 100644 --- a/src/test/java/suite/operator/OperatorPublicOnlyTest.java +++ b/src/test/java/suite/operator/OperatorPublicOnlyTest.java @@ -93,9 +93,7 @@ public void testV3IdentityMapSpecialOptout(String label, Operator operator, Stri return; } - // We need all properties to be there for Identity Map V3, so default all to empty - // In JSON if a property appears multiple times, the last value wins - String payload = "{\"email\":[], \"email_hash\":[], \"phone\":[], \"phone_hash\":[], \"" + type + "\": [\"" + identity + "\"]}"; + String payload = "{\"" + type + "\": [\"" + identity + "\"]}"; JsonNode response = operator.v3IdentityMap(payload); assertThat(response.get("body").get(type).get(0).get("e").asText()).isEqualTo("optout"); diff --git a/src/test/java/suite/operator/OperatorTest.java b/src/test/java/suite/operator/OperatorTest.java index c569d04..17422fe 100644 --- a/src/test/java/suite/operator/OperatorTest.java +++ b/src/test/java/suite/operator/OperatorTest.java @@ -183,7 +183,7 @@ public void testV3IdentityMapLargeBatch( // Sanity check that refresh from is a date not too far in the past. // If it is, either there is an Operator issue or salt rotation hasn't been running for a long time. - assertThat(mappedDii.getRefreshFrom()).isAfter(Instant.now().minus(Duration.ofDays(10))); + assertThat(mappedDii.getRefreshFrom()).isAfter(Instant.now().minus(Duration.ofHours(1))); } }); } @@ -193,11 +193,11 @@ public void testV3IdentityMapLargeBatch( "suite.operator.TestData#identityMapV3BatchBadEmailArgs", "suite.operator.TestData#identityMapV3BatchBadPhoneArgs" }) - public void testV3IdentityMapUnmapped(String label, Operator operator, String operatorName, String payload, String section) throws Exception { + public void testV3IdentityMapUnmapped(String label, Operator operator, String operatorName, String payload, String identityType) throws Exception { JsonNode response = operator.v3IdentityMap(payload); assertThat(response.at("/status").asText()).isEqualTo("success"); - assertThat(response.at("/body/" + section + "/0/e").asText()).isEqualTo("invalid identifier"); + assertThat(response.at("/body/" + identityType + "/0/e").asText()).isEqualTo("invalid identifier"); } From 3bf54ccb7b6d45db97a3f9443a88e41c2f5e89ab Mon Sep 17 00:00:00 2001 From: Aleksandrs Ulme Date: Mon, 30 Jun 2025 15:32:55 +0800 Subject: [PATCH 4/4] Updated to latest java sdk --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 75e4723..019d32d 100644 --- a/pom.xml +++ b/pom.xml @@ -90,7 +90,7 @@ com.uid2 uid2-client - 4.6.8-alpha-29-SNAPSHOT + 4.8.0 org.assertj