/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.adservices.service.measurement; import android.net.Uri; import com.android.adservices.data.measurement.DatastoreException; import com.android.adservices.service.measurement.actions.Action; import com.android.adservices.service.measurement.actions.ReportObjects; import org.json.JSONException; import org.json.JSONObject; import org.junit.Assert; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import java.io.IOException; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; /** * End-to-end test from source and trigger registration to attribution reporting, using mocked HTTP * requests, testing functionality when the test adds trigger data noise with 100% probability. */ @RunWith(Parameterized.class) public class E2EImpressionNoiseMockTest extends E2EMockTest { private static final String TEST_DIR_NAME = "msmt_e2e_noise_tests"; private final Map> mActualTriggerDataDistributions = new HashMap<>(); private final Map> mExpectedTriggerDataDistributions = new HashMap<>(); private interface PayloadKeys { String EVENT_ID = "source_event_id"; String TRIGGER_DATA = "trigger_data"; } @Parameterized.Parameters(name = "{3}") public static Collection getData() throws IOException, JSONException { return data(TEST_DIR_NAME); } public E2EImpressionNoiseMockTest(Collection actions, ReportObjects expectedOutput, PrivacyParamsProvider privacyParamsProvider, String name) throws DatastoreException { super(actions, expectedOutput, privacyParamsProvider, name); mAttributionHelper = TestObjectProvider.getAttributionJobHandler(sDatastoreManager); mMeasurementImpl = TestObjectProvider.getMeasurementImpl( sDatastoreManager, mClickVerifier, mFlags, mMeasurementDataDeleter, sEnrollmentDao); mAsyncRegistrationQueueRunner = TestObjectProvider.getAsyncRegistrationQueueRunner( TestObjectProvider.Type.NOISY, sDatastoreManager, mAsyncSourceFetcher, mAsyncTriggerFetcher, sEnrollmentDao); getExpectedTriggerDataDistributions(); } @Override void processEventReports(List eventReports, List destinations, List payloads) throws JSONException { // Each report-destination × event-ID should have the same count of trigger_data as in the // expected output, but the trigger_data value distribution should be different. The test // is currently supporting only one reporting job, which batches multiple reports at once, // although each is a separate network request. for (int i = 0; i < destinations.size(); i++) { String uri = getReportUrl(ReportType.EVENT, destinations.get(i).toString()); JSONObject payload = payloads.get(i); String eventId = payload.getString(PayloadKeys.EVENT_ID); String triggerData = payload.getString(PayloadKeys.TRIGGER_DATA); mActualTriggerDataDistributions.computeIfAbsent( getKey(uri, eventId), k -> new HashMap()).merge( triggerData, 1, Integer::sum); } } @Override void evaluateResults() { for (String key : mActualTriggerDataDistributions.keySet()) { if (!mExpectedTriggerDataDistributions.containsKey(key)) { Assert.assertTrue(getTestFailureMessage( "Missing key in expected trigger data distributions" + getDatastoreState()), false); } } boolean testPassed = false; for (String key1 : mExpectedTriggerDataDistributions.keySet()) { // No reports for a source is valid impression noise. if (!mActualTriggerDataDistributions.containsKey(key1)) { continue; } Map expectedDistribution = mExpectedTriggerDataDistributions.get(key1); Map actualDistribution = mActualTriggerDataDistributions.get(key1); for (String key2 : expectedDistribution.keySet()) { if (!actualDistribution.containsKey(key2) || !actualDistribution.get(key2).equals( expectedDistribution.get(key2))) { testPassed = true; break; } } } Assert.assertTrue( getTestFailureMessage( "Trigger data distributions were the same " + getDatastoreState()), testPassed); } private void getExpectedTriggerDataDistributions() { for (JSONObject reportObject : mExpectedOutput.mEventReportObjects) { String uri = reportObject.optString(TestFormatJsonMapping.REPORT_TO_KEY); JSONObject payload = reportObject.optJSONObject(TestFormatJsonMapping.PAYLOAD_KEY); String eventId = payload.optString(PayloadKeys.EVENT_ID); String triggerData = payload.optString(PayloadKeys.TRIGGER_DATA); mExpectedTriggerDataDistributions.computeIfAbsent( getKey(uri, eventId), k -> new HashMap()).merge( triggerData, 1, Integer::sum); } } private String getTestFailureMessage(String message) { return String.format("%s:\n\nExpected distributions: %s\n\nActual distributions: %s", message, mExpectedTriggerDataDistributions, mActualTriggerDataDistributions); } private String getKey(String uri, String eventId) { return uri + "," + eventId; } }