Skip to content

Commit

Permalink
✅ Update CSM tests for live bidding without prefetch
Browse files Browse the repository at this point in the history
JIRA: DPP-4000
  • Loading branch information
TurpIF committed Jun 1, 2022
1 parent a9230aa commit 1ff18f1
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
Expand All @@ -37,6 +39,7 @@
import androidx.test.filters.FlakyTest;
import com.criteo.publisher.Clock;
import com.criteo.publisher.Criteo;
import com.criteo.publisher.LiveCdbCallListener;
import com.criteo.publisher.TestAdUnits;
import com.criteo.publisher.context.ContextData;
import com.criteo.publisher.csm.MetricRequest.MetricRequestFeedback;
Expand All @@ -46,6 +49,7 @@
import com.criteo.publisher.mock.SpyBean;
import com.criteo.publisher.model.AdUnit;
import com.criteo.publisher.network.CdbMock;
import com.criteo.publisher.network.LiveBidRequestSender;
import com.criteo.publisher.network.PubSdkApi;
import com.criteo.publisher.privacy.ConsentData;
import com.criteo.publisher.util.BuildConfigWrapper;
Expand Down Expand Up @@ -90,38 +94,38 @@ public class CsmFunctionalTest {
@Inject
private ConsentData consentData;

@Inject
private MetricSendingQueueConsumer metricSendingQueueConsumer;

@SpyBean
private LiveBidRequestSender liveBidRequestSender;

@Before
public void setUp() throws Exception {
integrationRegistry.declare(Integration.IN_HOUSE);
consentData.setConsentGiven(true);
}

@Test
public void givenBidsThenConsumption_CallApiWithCsmOfConsumedBid() throws Exception {
public void givenConsumedLiveBids_CallApiWithCsmOfConsumedBid() throws Exception {
givenInitializedCriteo();
waitForIdleState();

loadBid(TestAdUnits.BANNER_320_50);
waitForIdleState();

sendMetricBatch();

AtomicReference<String> firstImpressionId = new AtomicReference<>();
AtomicReference<String> firstRequestGroupId = new AtomicReference<>();

// A third call is needed to trigger the sending of metrics to CSM. This is because,
// the execution of onBidConsumed() callback happens after the second call to CDB.
// An INTERSTITIAL AdUnit is set here on purpose to verify that the metric that is sent
// to CSM relates to BANNER_320_50, which is the one that was consumed and whose metric
// was pushed to the sending queue.
loadBid(TestAdUnits.INTERSTITIAL);
waitForIdleState();

verify(api).postCsm(argThat(request -> {
assertRequestHeaderIsExpected(request);

// There is only one expected because the second one was not ready when sending was triggered.
assertEquals(1, request.getFeedbacks().size());
MetricRequestFeedback feedback = request.getFeedbacks().get(0);
assertItRepresentsConsumedBid(feedback);
assertItRepresentsConsumedLiveBid(feedback);
firstImpressionId.set(feedback.getSlots().get(0).getImpressionId());
firstRequestGroupId.set(feedback.getRequestGroupId());

Expand All @@ -132,45 +136,45 @@ public void givenBidsThenConsumption_CallApiWithCsmOfConsumedBid() throws Except
loadBid(TestAdUnits.BANNER_320_50);
waitForIdleState();

sendMetricBatch();

verify(api).postCsm(argThat(request -> {
assertRequestHeaderIsExpected(request);

assertEquals(1, request.getFeedbacks().size());
MetricRequestFeedback feedback = request.getFeedbacks().get(0);
assertItRepresentsConsumedBid(feedback);
assertItRepresentsConsumedLiveBid(feedback);
assertNotEquals(firstImpressionId.get(), feedback.getSlots().get(0).getImpressionId());

// This two metrics come from the same CDB request (prefetch) and should have the same ID
assertEquals(firstRequestGroupId.get(), feedback.getRequestGroupId());
// This two metrics come from the different CDB requests and should have the different ID
assertNotEquals(firstRequestGroupId.get(), feedback.getRequestGroupId());

return true;
}));
}

@Test
public void givenConsumedExpiredBid_CallApiWithCsmOfConsumedExpiredBid() throws Exception {
when(clock.getCurrentTimeInMillis()).thenReturn(0L);
givenTimeBudgetExceededWhenFetchingLiveBids();
givenInitializedCriteo();
waitForIdleState();

when(clock.getCurrentTimeInMillis()).thenReturn(Long.MAX_VALUE);
loadBid(TestAdUnits.INTERSTITIAL);
when(clock.getCurrentTimeInMillis()).thenReturn(0L);
loadBid(TestAdUnits.INTERSTITIAL); // fetched but not consumed
waitForIdleState();

// A third call is needed to trigger the sending of metrics to CSM. This is because,
// the execution of onBidConsumed() callback happens after the second call to CDB.
// An BANNER_320_50 AdUnit is set here on purpose to verify that the metric that is sent
// to CSM relates to INTERSTITIAL, which is the one that was consumed and whose metric
// was pushed to the sending queue.
loadBid(TestAdUnits.BANNER_320_50);
when(clock.getCurrentTimeInMillis()).thenReturn(Long.MAX_VALUE); // bid is expired
loadBid(TestAdUnits.INTERSTITIAL); // consumed but expired
waitForIdleState();

sendMetricBatch();

verify(api).postCsm(argThat(request -> {
assertRequestHeaderIsExpected(request);

assertEquals(1, request.getFeedbacks().size());
MetricRequestFeedback feedback = request.getFeedbacks().get(0);
assertItRepresentsExpiredConsumedBid(feedback);
assertItRepresentsExpiredConsumedLiveBid(feedback);

return true;
}));
Expand All @@ -184,6 +188,8 @@ public void givenNoBidFromCdb_CallApiWithCsmOfNoBid() throws Exception {
loadBid(TestAdUnits.INTERSTITIAL_UNKNOWN);
waitForIdleState();

sendMetricBatch();

verify(api).postCsm(argThat(request -> {
assertRequestHeaderIsExpected(request);

Expand All @@ -205,38 +211,33 @@ public void givenNetworkErrorFromCdb_CallApiWithCsmOfNetworkError() throws Excep
loadBid(TestAdUnits.INTERSTITIAL_UNKNOWN);
waitForIdleState();

sendMetricBatch();

verify(api).postCsm(argThat(request -> {
assertRequestHeaderIsExpected(request);

assertEquals(2, request.getFeedbacks().size());
MetricRequestFeedback feedback1 = request.getFeedbacks().get(0);
MetricRequestFeedback feedback2 = request.getFeedbacks().get(1);
assertItRepresentsNetworkError(feedback1);
assertItRepresentsNetworkError(feedback2);

assertNotEquals(
feedback1.getSlots().get(0).getImpressionId(),
feedback2.getSlots().get(0).getImpressionId()
);

assertEquals(feedback1.getRequestGroupId(), feedback2.getRequestGroupId());
assertEquals(1, request.getFeedbacks().size());
MetricRequestFeedback feedback = request.getFeedbacks().get(0);
assertItRepresentsNetworkError(feedback);

return true;
}));
}

@Test
public void givenTimeoutErrorFromCdb_CallApiWithCsmOfTimeoutError() throws Exception {
when(buildConfigWrapper.getNetworkTimeoutInMillis()).thenReturn(1);

givenInitializedCriteo();
waitForIdleState();

when(buildConfigWrapper.getNetworkTimeoutInMillis()).thenCallRealMethod();
when(buildConfigWrapper.getNetworkTimeoutInMillis()).thenReturn(1);

loadBid(TestAdUnits.INTERSTITIAL_UNKNOWN);
loadBid(TestAdUnits.BANNER_320_480);
waitForIdleState();

when(buildConfigWrapper.getNetworkTimeoutInMillis()).thenCallRealMethod();
sendMetricBatch();

verify(api).postCsm(argThat(request -> {
assertRequestHeaderIsExpected(request);

Expand All @@ -251,7 +252,7 @@ public void givenTimeoutErrorFromCdb_CallApiWithCsmOfTimeoutError() throws Excep
feedback2.getSlots().get(0).getImpressionId()
);

assertEquals(feedback1.getRequestGroupId(), feedback2.getRequestGroupId());
assertNotEquals(feedback1.getRequestGroupId(), feedback2.getRequestGroupId());

return true;
}));
Expand All @@ -266,46 +267,57 @@ public void givenErrorWhenSendingCsm_QueueMetricsUntilCsmRequestWorksAgain() thr
givenInitializedCriteo();
waitForIdleState();

// Consumed and not expired
// Consumed and not expired -> CSM
loadBid(TestAdUnits.BANNER_320_50);
waitForIdleState();

// No bid consumed, and bid (1) is cached -> no CSM
givenTimeBudgetExceededWhenFetchingLiveBids();
loadBid(TestAdUnits.INTERSTITIAL);
waitForIdleState();

// Consumed and not expired -> CSM
givenTimeBudgetRespectedWhenFetchingLiveBids();
loadBid(TestAdUnits.INTERSTITIAL);
waitForIdleState();

// Consumed but expired
// There is also a bid request here and consumed at timeout step
// Bid (1) is consumed but expired -> CSM of (1)
// a bid (2) is cached but never consumed -> no CSM
when(clock.getCurrentTimeInMillis()).thenCallRealMethod();
givenTimeBudgetExceededWhenFetchingLiveBids();
loadBid(TestAdUnits.INTERSTITIAL);
waitForIdleState();

// No bid
// No bid -> CSM
givenTimeBudgetRespectedWhenFetchingLiveBids();
loadBid(TestAdUnits.INTERSTITIAL_UNKNOWN);
waitForIdleState();

// Timeout
// Timeout -> CSM
cdbMock.simulatorSlowNetworkOnNextRequest();
when(buildConfigWrapper.getNetworkTimeoutInMillis()).thenReturn(1);
loadBid(TestAdUnits.INTERSTITIAL);
loadBid(TestAdUnits.BANNER_320_50);
waitForIdleState();
when(buildConfigWrapper.getNetworkTimeoutInMillis()).thenCallRealMethod();

// Network error
// Network error -> CSM
doThrow(IOException.class).when(api).loadCdb(any(), any());
loadBid(TestAdUnits.INTERSTITIAL);
loadBid(TestAdUnits.BANNER_320_50);
waitForIdleState();
doCallRealMethod().when(api).loadCdb(any(), any());

// CSM endpoint works again: on next bid request, there should metrics for all previous bids.
// CSM endpoint works again
clearInvocations(api);
doCallRealMethod().when(api).postCsm(any());
loadBid(TestAdUnits.BANNER_UNKNOWN);
waitForIdleState();
sendMetricBatch();

verify(api).postCsm(argThat(request -> {
assertRequestHeaderIsExpected(request);

List<MetricRequestFeedback> feedbacks = request.getFeedbacks();
assertEquals(6, feedbacks.size());
assertOnlyNSatisfy(feedbacks, 2, this::assertItRepresentsConsumedBid);
assertOnlyNSatisfy(feedbacks, 1, this::assertItRepresentsExpiredConsumedBid);
assertOnlyNSatisfy(feedbacks, 2, this::assertItRepresentsConsumedLiveBid);
assertOnlyNSatisfy(feedbacks, 1, this::assertItRepresentsExpiredConsumedLiveBid);
assertOnlyNSatisfy(feedbacks, 1, this::assertItRepresentsNoBid);
assertOnlyNSatisfy(feedbacks, 1, this::assertItRepresentsTimeoutError);
assertOnlyNSatisfy(feedbacks, 1, this::assertItRepresentsNetworkError);
Expand Down Expand Up @@ -350,18 +362,18 @@ private void assertRequestHeaderIsExpected(MetricRequest request) {
assertEquals(buildConfigWrapper.getSdkVersion(), request.getWrapperVersion());
}

private void assertItRepresentsConsumedBid(MetricRequestFeedback feedback) {
private void assertItRepresentsConsumedLiveBid(MetricRequestFeedback feedback) {
assertEquals(0, feedback.getCdbCallStartElapsed());
assertNotNull(feedback.getCdbCallEndElapsed());
assertNotNull(feedback.getElapsed());
assertFalse(feedback.isTimeout());
assertNotNull(feedback.getRequestGroupId());
assertEquals(1, feedback.getSlots().size());
assertTrue(feedback.getSlots().get(0).getCachedBidUsed());
assertFalse(feedback.getSlots().get(0).getCachedBidUsed());
assertNotNull(feedback.getSlots().get(0).getZoneId());
}

private void assertItRepresentsExpiredConsumedBid(MetricRequestFeedback feedback) {
private void assertItRepresentsExpiredConsumedLiveBid(MetricRequestFeedback feedback) {
assertEquals(0, feedback.getCdbCallStartElapsed());
assertNotNull(feedback.getCdbCallEndElapsed());
assertNull(feedback.getElapsed());
Expand Down Expand Up @@ -412,4 +424,20 @@ private void waitForIdleState() {
private void loadBid(@NonNull AdUnit adUnit) {
Criteo.getInstance().loadBid(adUnit, new ContextData(), ignored -> { /* no op */ });
}

private void sendMetricBatch() {
metricSendingQueueConsumer.sendMetricBatch();
waitForIdleState();
}

private void givenTimeBudgetRespectedWhenFetchingLiveBids() {
doNothing().when(liveBidRequestSender).scheduleTimeBudgetExceeded$publisher_sdk_debug(any());
}

private void givenTimeBudgetExceededWhenFetchingLiveBids() {
doAnswer(invocation -> {
invocation.getArgument(0, LiveCdbCallListener.class).onTimeBudgetExceeded();
return null;
}).when(liveBidRequestSender).scheduleTimeBudgetExceeded$publisher_sdk_debug(any());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,7 @@ public BidLifecycleListener provideBidLifecycleListener() {
listener.add(new CsmBidLifecycleListener(
provideMetricRepository(),
provideMetricSendingQueueProducer(),
provideMetricSendingQueueConsumer(),
provideClock(),
provideConfig(),
provideConsentData(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
import com.criteo.publisher.model.CdbResponseSlot;
import com.criteo.publisher.model.Config;
import com.criteo.publisher.privacy.ConsentData;

import java.io.InterruptedIOException;
import java.util.concurrent.Executor;

Expand All @@ -46,6 +45,9 @@ public class CsmBidLifecycleListener implements BidLifecycleListener {
@NonNull
private final MetricSendingQueueProducer sendingQueueProducer;

@NonNull
private final MetricSendingQueueConsumer sendingQueueConsumer;

@NonNull
private final Clock clock;

Expand All @@ -61,13 +63,15 @@ public class CsmBidLifecycleListener implements BidLifecycleListener {
public CsmBidLifecycleListener(
@NonNull MetricRepository repository,
@NonNull MetricSendingQueueProducer sendingQueueProducer,
@NonNull MetricSendingQueueConsumer sendingQueueConsumer,
@NonNull Clock clock,
@NonNull Config config,
@NonNull ConsentData consentData,
@NonNull Executor executor
) {
this.repository = repository;
this.sendingQueueProducer = sendingQueueProducer;
this.sendingQueueConsumer = sendingQueueConsumer;
this.clock = clock;
this.config = config;
this.consentData = consentData;
Expand All @@ -78,7 +82,7 @@ public CsmBidLifecycleListener(
* SDK initialization is caused either by a fresh or restart of the application. As a consequence,
* the in-memory bid-cache is lost and the metrics associated to the previously cached bids will
* never be updated again. In this case, all previously stored metrics are moved to the sending
* queue.
* queue and posted to backend.
*/
@Override
public void onSdkInitialized() {
Expand All @@ -90,6 +94,7 @@ public void onSdkInitialized() {
@Override
public void runSafely() {
sendingQueueProducer.pushAllInQueue(repository);
sendingQueueConsumer.sendMetricBatch();
}
});
}
Expand Down
Loading

0 comments on commit 1ff18f1

Please sign in to comment.