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(); + } +}