diff --git a/pom.xml b/pom.xml
index 8a9110d..2e53e3a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,6 +18,8 @@
1.1.0
7.19.0
${project.version}
+ 5.10.1
+ 5.10.1
@@ -69,6 +71,36 @@
vertx-micrometer-metrics
${vertx.version}
+
+ org.junit.jupiter
+ junit-jupiter-api
+ ${junit-jupiter.version}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ ${junit-jupiter.version}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-params
+ ${junit-jupiter.version}
+ test
+
+
+ io.vertx
+ vertx-junit5
+ ${vertx.version}
+ test
+
+
+ org.mockito
+ mockito-core
+ 5.10.0
+ test
+
io.micrometer
micrometer-registry-jmx
diff --git a/src/main/resources/localstack/init-aws.sh b/src/main/resources/localstack/init-aws.sh
old mode 100644
new mode 100755
diff --git a/src/test/java/com/uid2/optout/TestUtils.java b/src/test/java/com/uid2/optout/TestUtils.java
index c52e5f4..1981bbf 100644
--- a/src/test/java/com/uid2/optout/TestUtils.java
+++ b/src/test/java/com/uid2/optout/TestUtils.java
@@ -64,12 +64,26 @@ public static String newDeltaFile(long... ids) {
return newDeltaFile(TestUtils.toEntries(ids));
}
+ public static String newDeltaFile(Path path, long... ids) {
+ return newDeltaFile(TestUtils.toEntries(ids), path);
+ }
+
public static String newDeltaFile(OptOutEntry[] entries) {
+ Path tmpFile;
+ try {
+ tmpFile = Files.createTempFile(OptOutUtils.prefixDeltaFile, newSuffix());
+ } catch (IOException e) {
+ e.printStackTrace();
+ return null;
+ }
+ return newDeltaFile(entries, tmpFile);
+ }
+
+ public static String newDeltaFile(OptOutEntry[] entries, Path path) {
try {
- Path tmpFile = Files.createTempFile(OptOutUtils.prefixDeltaFile, newSuffix());
OptOutCollection store = new OptOutCollection(entries);
- Files.write(tmpFile, store.getStore(), StandardOpenOption.CREATE);
- return tmpFile.toString();
+ Files.write(path, store.getStore(), StandardOpenOption.CREATE);
+ return path.toString();
} catch (IOException e) {
e.printStackTrace();
return null;
diff --git a/src/test/java/com/uid2/optout/vertx/OptOutSenderTest.java b/src/test/java/com/uid2/optout/vertx/OptOutSenderTest.java
new file mode 100644
index 0000000..79f2f32
--- /dev/null
+++ b/src/test/java/com/uid2/optout/vertx/OptOutSenderTest.java
@@ -0,0 +1,127 @@
+package com.uid2.optout.vertx;
+
+import com.uid2.optout.Const;
+import com.uid2.optout.TestUtils;
+import com.uid2.optout.partner.IOptOutPartnerEndpoint;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import com.uid2.shared.optout.OptOutEntry;
+import io.micrometer.core.instrument.Metrics;
+import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
+import io.vertx.core.Future;
+import io.vertx.core.Vertx;
+import io.vertx.core.json.JsonObject;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import io.vertx.junit5.VertxExtension;
+import io.vertx.junit5.VertxTestContext;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.nio.file.Files;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(VertxExtension.class)
+public class OptOutSenderTest {
+
+ private AutoCloseable mocks;
+ @Mock
+ private IOptOutPartnerEndpoint optOutPartnerEndpoint;
+ private final String partnerName = "testPartner";
+ private final String filePath = "/tmp/uid2/optout";
+ private final String eventBusName = "testEventBus";
+ private CompletableFuture test;
+ private OptOutSender optoutSender;
+ private final JsonObject config = new JsonObject();
+
+ private SimpleMeterRegistry registry;
+
+ @BeforeEach
+ public void setup() {
+ new File(filePath + "/consumer/delta").mkdirs();
+ }
+
+ public void deployVerticle(Vertx vertx, VertxTestContext testContext) {
+ mocks = MockitoAnnotations.openMocks(this);
+
+ setupConfig();
+ setupMocks(vertx);
+
+ this.optoutSender = new OptOutSender(config, optOutPartnerEndpoint, eventBusName);
+
+ vertx.deployVerticle(optoutSender, testContext.succeeding(id -> testContext.completeNow()));
+
+ this.registry = new SimpleMeterRegistry();
+ Metrics.globalRegistry.add(registry);
+ }
+
+ private void setupMocks(Vertx vertx) {
+ when(optOutPartnerEndpoint.name()).thenReturn(partnerName);
+ test = new CompletableFuture();
+ when(optOutPartnerEndpoint.send(any())).then((a) -> {
+ test.complete(null);
+ return Future.fromCompletionStage(test, vertx.getOrCreateContext());
+ });
+ }
+
+ private void setupConfig() {
+ config.put(Const.Config.OptOutDataDirProp, filePath);
+ config.put(Const.Config.OptOutProducerReplicaIdProp, 1);
+
+ config.put(Const.Config.OptOutSenderReplicaIdProp, 1);
+ config.put(Const.Config.OptOutProducerMaxReplicasProp, 1);
+
+ config.put(Const.Config.OptOutDeltaRotateIntervalProp, 300);
+ }
+
+ @AfterEach
+ public void cleanup() throws IOException {
+ Files.walk(Paths.get(filePath))
+ .map(Path::toFile)
+ .forEach(File::delete);
+ }
+
+ private Path getDeltaPath() {
+ return Paths.get(filePath, "consumer/delta", "optout-delta-" + TestUtils.newSuffix());
+ }
+
+
+ // Also tests ScanLocalForUnprocessedWithNoNewFiles
+ @Test
+ void verticleDeployed(Vertx vertx, VertxTestContext testContext) {
+ deployVerticle(vertx, testContext);
+ testContext.completeNow();
+ }
+
+ @Test
+ void testScanLocalForUnprocessedWithNewFile(Vertx vertx, VertxTestContext testContext) {
+ TestUtils.newDeltaFile(getDeltaPath(), 1, 2, 3);
+ deployVerticle(vertx, testContext);
+ testContext.completeNow();
+ }
+
+ // If this test hangs delete the /tmp/uid2/optout folder and run again.
+ @Test
+ void testRecieveMessageAndSendsIDs(Vertx vertx, VertxTestContext testContext) throws InterruptedException {
+ deployVerticle(vertx, testContext);
+ Path newFile = getDeltaPath();
+ TestUtils.newDeltaFile(newFile, 1, 2, 3);
+ vertx.eventBus().publish(eventBusName, newFile.toString());
+
+ while(!test.isDone()) {
+ Thread.sleep(100);
+ }
+ verify(optOutPartnerEndpoint, times(3)).send(any());
+ testContext.completeNow();
+ }
+}
diff --git a/src/test/java/com/uid2/optout/vertx/PartnerConfigMonitorV2Test.java b/src/test/java/com/uid2/optout/vertx/PartnerConfigMonitorV2Test.java
new file mode 100644
index 0000000..6e3ab08
--- /dev/null
+++ b/src/test/java/com/uid2/optout/vertx/PartnerConfigMonitorV2Test.java
@@ -0,0 +1,114 @@
+package com.uid2.optout.vertx;
+
+import com.uid2.optout.Const;
+import com.uid2.shared.cloud.CloudStorageException;
+import com.uid2.shared.cloud.DownloadCloudStorage;
+import io.vertx.core.Vertx;
+import io.vertx.core.json.JsonObject;
+import io.vertx.junit5.VertxExtension;
+import io.vertx.junit5.VertxTestContext;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.io.ByteArrayInputStream;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(VertxExtension.class)
+public class PartnerConfigMonitorV2Test {
+ private AutoCloseable mocks;
+ private final String filePath = "/tmp/uid2/optout";
+ private final JsonObject config = new JsonObject();
+ private final String eventBusName = "testEventBus";
+
+ @Mock
+ private DownloadCloudStorage metadataStorage;
+ @Mock
+ private DownloadCloudStorage contentStorage;
+
+ private PartnerConfigMonitorV2 partnerConfigMonitorV2;
+
+ @BeforeEach
+ public void deployVerticle(Vertx vertx, VertxTestContext testContext) {
+ mocks = MockitoAnnotations.openMocks(this);
+
+ setupConfig();
+
+ partnerConfigMonitorV2 = new PartnerConfigMonitorV2(vertx, config, metadataStorage, contentStorage, eventBusName);
+ testContext.completeNow();
+ }
+
+ private void setupConfig() {
+ config.put(Const.Config.OptOutDataDirProp, filePath);
+ config.put(Const.Config.OptOutProducerReplicaIdProp, 1);
+
+ config.put(Const.Config.OptOutSenderReplicaIdProp, 1);
+ config.put(Const.Config.OptOutProducerMaxReplicasProp, 1);
+
+ config.put(Const.Config.OptOutDeltaRotateIntervalProp, 300);
+
+ config.put(Const.Config.PartnersMetadataPathProp, "testPath");
+ }
+
+ @Test
+ void testConstructor(Vertx vertx, VertxTestContext testContext) {
+ testContext.completeNow();
+ }
+
+ @Test
+ void testLoadContent(Vertx vertx, VertxTestContext testContext) throws Exception {
+ JsonObject metadata = new JsonObject();
+ JsonObject partner = new JsonObject();
+ partner.put("location", "testLocation");
+ metadata.put("partners", partner);
+
+ String testString = """
+ [
+ {
+ "name": "test1",
+ "url": "https:/test.com/uid2/optout",
+ "method": "GET",
+ "query_params": [
+ "action=dooptout",
+ "uid2=${ADVERTISING_ID}",
+ "timestamp=${OPTOUT_EPOCH}"
+ ],
+ "additional_headers": [
+ "Authorization: Bearer some_bearer"
+ ],
+ "retry_count": 600,
+ "retry_backoff_ms": 6000
+ },
+ {
+ "name": "test2",
+ "url": "https:/example.com/optout",
+ "method": "POST",
+ "query_params": [
+ "token=${ADVERTISING_ID}",
+ "timestamp=${OPTOUT_EPOCH}"
+ ],
+ "additional_headers": [
+ "Authorization: Bearer bearer2"
+ ],
+ "retry_count": 60,
+ "retry_backoff_ms": 1000
+ }
+ ]
+ """;
+
+ when(contentStorage.download(any())).thenReturn(new ByteArrayInputStream(testString.getBytes()));
+
+ long endpoints = partnerConfigMonitorV2.loadContent(metadata);
+
+ //Two endpoints senders should be deployed
+ assertEquals(2, endpoints);
+
+
+ testContext.completeNow();
+ }
+}