Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .trivyignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# List any vulnerability that are to be accepted
# See https://aquasecurity.github.io/trivy/v0.35/docs/vulnerability/examples/filter/
# for more details

#
CVE-2025-68973
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ ENV E2E_PHONE_SUPPORT ""

ENV UID2_CORE_E2E_OPERATOR_API_KEY ""
ENV UID2_CORE_E2E_OPTOUT_API_KEY ""
ENV UID2_CORE_E2E_OPTOUT_INTERNAL_API_KEY "test-optout-internal-key"
ENV UID2_CORE_E2E_CORE_URL ""
ENV UID2_CORE_E2E_OPTOUT_URL ""

Expand Down
2 changes: 2 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ services:
environment:
- EDGE_PORT=5001
- KMS_PROVIDER=local-kms
- LOCALSTACK_HOST=localstack
healthcheck:
test: awslocal s3api wait bucket-exists --bucket test-core-bucket
&& awslocal s3api wait bucket-exists --bucket test-optout-bucket
Expand Down Expand Up @@ -49,6 +50,7 @@ services:
image: ghcr.io/iabtechlab/uid2-optout:latest
ports:
- "127.0.0.1:8081:8081"
- "127.0.0.1:8082:8082"
- "127.0.0.1:5090:5005"
volumes:
- ./docker/uid2-optout/conf/default-config.json:/app/conf/default-config.json
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.uid2</groupId>
<artifactId>uid2-e2e</artifactId>
<version>4.1.0</version>
<version>4.1.5-alpha-79-SNAPSHOT</version>

<properties>
<maven.compiler.source>21</maven.compiler.source>
Expand Down
82 changes: 82 additions & 0 deletions src/test/java/app/component/Optout.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package app.component;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.uid2.shared.util.Mapper;
import common.Const;
import common.EnvUtil;
import common.HttpClient;

/**
* Component for interacting with the UID2 Optout service.
*/
public class Optout extends App {
private static final ObjectMapper OBJECT_MAPPER = Mapper.getInstance();
private static final String OPTOUT_INTERNAL_API_KEY = EnvUtil.getEnv(Const.Config.Core.OPTOUT_INTERNAL_API_KEY);
public static final String OPTOUT_URL = EnvUtil.getEnv(Const.Config.Core.OPTOUT_URL);

// The SQS delta producer runs on port 8082 (8081 + 1)
private static final int DELTA_PRODUCER_PORT_OFFSET = 1;

public Optout(String host, Integer port, String name) {
super(host, port, name);
}

public Optout(String host, String name) {
super(host, null, name);
}

/**
* Triggers delta production on the optout service.
* This reads from the SQS queue and produces delta files.
* The endpoint is on port 8082 (optout port + 1).
*/
public JsonNode triggerDeltaProduce() throws Exception {
String deltaProduceUrl = getDeltaProducerBaseUrl() + "/optout/deltaproduce";
String response = HttpClient.post(deltaProduceUrl, "", OPTOUT_INTERNAL_API_KEY);
return OBJECT_MAPPER.readTree(response);
}

/**
* Gets the status of the current delta production job.
*/
public JsonNode getDeltaProduceStatus() throws Exception {
String statusUrl = getDeltaProducerBaseUrl() + "/optout/deltaproduce/status";
String response = HttpClient.get(statusUrl, OPTOUT_INTERNAL_API_KEY);
return OBJECT_MAPPER.readTree(response);
}

/**
* Triggers delta production and waits for it to complete.
* @param maxWaitSeconds Maximum time to wait for completion
* @return true if delta production completed successfully
*/
public boolean triggerDeltaProduceAndWait(int maxWaitSeconds) throws Exception {
triggerDeltaProduce();

long startTime = System.currentTimeMillis();
long maxWaitMs = maxWaitSeconds * 1000L;

while (System.currentTimeMillis() - startTime < maxWaitMs) {
Thread.sleep(2000); // Poll every 2 seconds

JsonNode status = getDeltaProduceStatus();
String state = status.path("state").asText();

if ("COMPLETED".equals(state) || "FAILED".equals(state)) {
return "COMPLETED".equals(state);
}
}

return false; // Timed out
}

private String getDeltaProducerBaseUrl() {
// Delta producer runs on optout port + 1
if (getPort() != null) {
return "http://" + getHost() + ":" + (getPort() + DELTA_PRODUCER_PORT_OFFSET);
}
// If port not specified, assume default optout port (8081) + 1
return "http://" + getHost() + ":8082";
}
}
1 change: 1 addition & 0 deletions src/test/java/common/Const.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public static final class Config {
public static final class Core {
public static final String OPERATOR_API_KEY = "UID2_CORE_E2E_OPERATOR_API_KEY";
public static final String OPTOUT_API_KEY = "UID2_CORE_E2E_OPTOUT_API_KEY";
public static final String OPTOUT_INTERNAL_API_KEY = "UID2_CORE_E2E_OPTOUT_INTERNAL_API_KEY";
public static final String CORE_URL = "UID2_CORE_E2E_CORE_URL";
public static final String OPTOUT_URL = "UID2_CORE_E2E_OPTOUT_URL";
}
Expand Down
34 changes: 32 additions & 2 deletions src/test/java/suite/optout/OptoutTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package suite.optout;

import app.component.Operator;
import app.component.Optout;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.uid2.client.IdentityTokens;
Expand All @@ -9,6 +10,8 @@
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.Instant;
import java.util.HashSet;
Expand All @@ -23,19 +26,23 @@
@SuppressWarnings("unused")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class OptoutTest {
// TODO: Test failure case
private static final Logger LOGGER = LoggerFactory.getLogger(OptoutTest.class);

private static final ObjectMapper OBJECT_MAPPER = Mapper.getInstance();
private static final int OPTOUT_DELAY_MS = 1000;
private static final int OPTOUT_WAIT_SECONDS = 300;
private static final int DELTA_PRODUCE_WAIT_SECONDS = 120;

private static Set<Arguments> outputArgs;
private static Set<Arguments> outputAdvertisingIdArgs;
private static Optout optoutService;

@BeforeAll
public static void setupAll() {
outputArgs = new HashSet<>();
outputAdvertisingIdArgs = new HashSet<>();
// Initialize optout service component for delta production
optoutService = new Optout("optout", 8081, "Optout Service");
}

@ParameterizedTest(name = "/v2/token/logout with /v2/token/generate - {0} - {2}")
Expand Down Expand Up @@ -78,7 +85,30 @@ public void testV2LogoutWithV2IdentityMap(String label, Operator operator, Strin
outputAdvertisingIdArgs.add(Arguments.of(label, operator, operatorName, rawUID, toOptOut, beforeOptOutTimestamp));
}

/**
* Triggers delta production on the optout service after all logout requests.
* This reads the opt-out requests from SQS and produces delta files that
* the operator will sync to reflect the opt-outs.
*/
@Test
@Order(4)
public void triggerDeltaProduction() throws Exception {
LOGGER.info("Triggering delta production on optout service");

// Trigger delta production
JsonNode response = optoutService.triggerDeltaProduce();
LOGGER.info("Delta production triggered: {}", response);

// Wait for completion
boolean success = optoutService.triggerDeltaProduceAndWait(DELTA_PRODUCE_WAIT_SECONDS);
assertThat(success).as("Delta production should complete successfully").isTrue();

// Get final status
JsonNode status = optoutService.getDeltaProduceStatus();
LOGGER.info("Delta production completed: {}", status);
}

@Order(5)
@ParameterizedTest(name = "/v2/token/refresh after {2} generate and {3} logout - {0} - {1}")
@MethodSource({
"afterOptoutTokenArgs"
Expand All @@ -89,7 +119,7 @@ public void testV2TokenRefreshAfterOptOut(String label, Operator operator, Strin
with().pollInterval(5, TimeUnit.SECONDS).await("Get V2 Token Response").atMost(OPTOUT_WAIT_SECONDS, TimeUnit.SECONDS).until(() -> operator.v2TokenRefresh(refreshToken, refreshResponseKey).equals(OBJECT_MAPPER.readTree("{\"status\":\"optout\"}")));
}

@Order(5)
@Order(6)
@ParameterizedTest(name = "/v2/optout/status after v2/identity/map and v2/token/logout - DII {0} - expecting {4} - {2}")
@MethodSource({"afterOptoutAdvertisingIdArgs"})
public void testV2OptOutStatus(String label, Operator operator, String operatorName, String rawUID,
Expand Down