1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.adservices.service.measurement;
18 
19 import static android.view.MotionEvent.ACTION_BUTTON_PRESS;
20 import static android.view.MotionEvent.obtain;
21 
22 import static com.android.adservices.service.Flags.MEASUREMENT_MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS;
23 import static com.android.adservices.service.Flags.MEASUREMENT_MIN_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS;
24 import static com.android.adservices.service.measurement.reporting.AggregateReportSender.AGGREGATE_ATTRIBUTION_REPORT_URI_PATH;
25 import static com.android.adservices.service.measurement.reporting.AggregateReportSender.DEBUG_AGGREGATE_ATTRIBUTION_REPORT_URI_PATH;
26 import static com.android.adservices.service.measurement.reporting.DebugReportSender.DEBUG_REPORT_URI_PATH;
27 import static com.android.adservices.service.measurement.reporting.EventReportSender.DEBUG_EVENT_ATTRIBUTION_REPORT_URI_PATH;
28 import static com.android.adservices.service.measurement.reporting.EventReportSender.EVENT_ATTRIBUTION_REPORT_URI_PATH;
29 
30 import android.content.res.AssetManager;
31 import android.database.Cursor;
32 import android.database.DatabaseUtils;
33 import android.database.sqlite.SQLiteDatabase;
34 import android.provider.DeviceConfig;
35 import android.util.Log;
36 import android.view.InputDevice;
37 import android.view.InputEvent;
38 import android.view.MotionEvent.PointerCoords;
39 import android.view.MotionEvent.PointerProperties;
40 
41 import androidx.annotation.Nullable;
42 
43 import com.android.adservices.common.AdServicesUnitTestCase;
44 import com.android.adservices.data.DbTestUtil;
45 import com.android.adservices.service.FlagsConstants;
46 import com.android.adservices.service.measurement.actions.Action;
47 import com.android.adservices.service.measurement.actions.AggregateReportingJob;
48 import com.android.adservices.service.measurement.actions.EventReportingJob;
49 import com.android.adservices.service.measurement.actions.InstallApp;
50 import com.android.adservices.service.measurement.actions.RegisterListSources;
51 import com.android.adservices.service.measurement.actions.RegisterSource;
52 import com.android.adservices.service.measurement.actions.RegisterTrigger;
53 import com.android.adservices.service.measurement.actions.RegisterWebSource;
54 import com.android.adservices.service.measurement.actions.RegisterWebTrigger;
55 import com.android.adservices.service.measurement.actions.ReportObjects;
56 import com.android.adservices.service.measurement.actions.UninstallApp;
57 import com.android.adservices.service.measurement.actions.UriConfig;
58 
59 import com.google.common.collect.ImmutableList;
60 
61 import org.json.JSONArray;
62 import org.json.JSONException;
63 import org.json.JSONObject;
64 import org.junit.Assert;
65 import org.junit.Test;
66 
67 import java.io.IOException;
68 import java.io.InputStream;
69 import java.nio.charset.StandardCharsets;
70 import java.util.ArrayList;
71 import java.util.Arrays;
72 import java.util.Collection;
73 import java.util.Collections;
74 import java.util.Comparator;
75 import java.util.HashMap;
76 import java.util.HashSet;
77 import java.util.Iterator;
78 import java.util.List;
79 import java.util.Map;
80 import java.util.Set;
81 import java.util.concurrent.TimeUnit;
82 import java.util.function.Function;
83 
84 /**
85  * End-to-end test from source and trigger registration to attribution reporting. Extensions of this
86  * class can implement different ways to prepare the registrations, either with an external server
87  * or mocking HTTP responses, for example; similarly for examining the attribution reports.
88  *
89  * <p>Consider @RunWith(Parameterized.class)
90  */
91 public abstract class E2ETest extends AdServicesUnitTestCase {
92     // Used to fuzzy-match expected report (not delivery) time
93     private static final String LOG_TAG = "ADSERVICES_MSMT_E2E_TEST";
94 
95     private final String mName;
96     private final Collection<Action> mActionsList;
97     final ReportObjects mExpectedOutput;
98     private final Map<String, String> mPhFlagsMap;
99     // Extenders of the class populate in their own ways this container for actual output.
100     final ReportObjects mActualOutput;
101 
102     public static final long AGGREGATE_REPORT_DELAY = TimeUnit.HOURS.toMillis(1);
103 
104     enum ReportType {
105         EVENT,
106         AGGREGATE,
107         EVENT_DEBUG,
108         AGGREGATE_DEBUG,
109         VERBOSE_DEBUG
110     }
111 
112     private enum OutputType {
113         EXPECTED,
114         ACTUAL
115     }
116 
117     private interface EventReportPayloadKeys {
118         // Keys used to compare actual with expected output
119         List<String> STRINGS =
120                 ImmutableList.of(
121                         "scheduled_report_time",
122                         "source_event_id",
123                         "trigger_data",
124                         "source_type",
125                         "source_debug_key",
126                         "trigger_debug_key",
127                         "trigger_summary_bucket");
128         String DOUBLE = "randomized_trigger_rate";
129         String STRING_OR_ARRAY = "attribution_destination";
130         String ARRAY = "trigger_debug_keys";
131     }
132 
133     interface AggregateReportPayloadKeys {
134         String SHARED_INFO = "shared_info";
135         String AGGREGATION_COORDINATOR_ORIGIN = "aggregation_coordinator_origin";
136         String HISTOGRAMS = "histograms";
137         String SOURCE_DEBUG_KEY = "source_debug_key";
138         String TRIGGER_DEBUG_KEY = "trigger_debug_key";
139         String TRIGGER_CONTEXT_ID = "trigger_context_id";
140     }
141 
142     static List<String> sAggregateReportSharedInfoKeys =
143             ImmutableList.of(
144                     "api",
145                     "attribution_destination",
146                     "debug_mode",
147                     "reporting_origin",
148                     "scheduled_report_time",
149                     "version",
150                     "source_registration_time");
151 
152     interface DebugReportPayloadKeys {
153         String TYPE = "type";
154         String BODY = "body";
155         List<String> BODY_KEYS =
156                 ImmutableList.of(
157                         "attribution_destination",
158                         "limit",
159                         "randomized_trigger_rate",
160                         "scheduled_report_time",
161                         "source_debug_key",
162                         "source_event_id",
163                         "source_site",
164                         "source_type",
165                         "trigger_debug_key");
166     }
167 
168     interface AggregateHistogramKeys {
169         String BUCKET = "key";
170         String VALUE = "value";
171     }
172 
173     public interface TestFormatJsonMapping {
174         String DEFAULT_CONFIG_FILENAME = "default_config.json";
175         String API_CONFIG_KEY = "api_config";
176         String PH_FLAGS_OVERRIDE_KEY = "phflags_override";
177         String TEST_INPUT_KEY = "input";
178         String TEST_OUTPUT_KEY = "output";
179         String REGISTRATIONS_KEY = "registrations";
180         String SOURCE_REGISTRATIONS_KEY = "sources";
181         String WEB_SOURCES_KEY = "web_sources";
182         String LIST_SOURCES_KEY = "list_sources";
183         String SOURCE_PARAMS_REGISTRATIONS_KEY = "source_params";
184         String TRIGGERS_KEY = "triggers";
185         String WEB_TRIGGERS_KEY = "web_triggers";
186         String TRIGGER_PARAMS_REGISTRATIONS_KEY = "trigger_params";
187         String URI_TO_RESPONSE_HEADERS_KEY = "responses";
188         String URI_TO_RESPONSE_HEADERS_URL_KEY = "url";
189         String URI_TO_RESPONSE_HEADERS_RESPONSE_KEY = "response";
190         String REGISTRATION_REQUEST_KEY = "registration_request";
191         String ATTRIBUTION_SOURCE_KEY = "registrant";
192         String ATTRIBUTION_SOURCE_DEFAULT = "com.interop.app";
193         String CONTEXT_ORIGIN_URI_KEY = "context_origin";
194         String SOURCE_TOP_ORIGIN_URI_KEY = "source_origin";
195         String TRIGGER_TOP_ORIGIN_URI_KEY = "destination_origin";
196         String SOURCE_APP_DESTINATION_URI_KEY = "app_destination";
197         String SOURCE_WEB_DESTINATION_URI_KEY = "web_destination";
198         String SOURCE_VERIFIED_DESTINATION_URI_KEY = "verified_destination";
199         String REGISTRATION_URI_KEY = "attribution_src_url";
200         String REGISTRATION_URIS_KEY = "attribution_src_urls";
201         String HAS_AD_ID_PERMISSION = "has_ad_id_permission";
202         String DEBUG_KEY = "debug_key";
203         String DEBUG_PERMISSION_KEY = "debug_permission";
204         String DEBUG_REPORTING_KEY = "debug_reporting";
205         String INPUT_EVENT_KEY = "source_type";
206         String SOURCE_VIEW_TYPE = "event";
207         String INTEROP_INPUT_EVENT_KEY = "Attribution-Reporting-Eligible";
208         String INTEROP_SOURCE_VIEW_TYPE = "event-source";
209         String SOURCE_REGISTRATION_HEADER = "Attribution-Reporting-Register-Source";
210         String TRIGGER_REGISTRATION_HEADER = "Attribution-Reporting-Register-Trigger";
211         String TIMESTAMP_KEY = "timestamp";
212         String REPORTS_OBJECTS_KEY = "reports";
213         String EVENT_REPORT_OBJECTS_KEY = "event_level_results";
214         String AGGREGATE_REPORT_OBJECTS_KEY = "aggregatable_results";
215         String DEBUG_EVENT_REPORT_OBJECTS_KEY = "debug_event_level_results";
216         String DEBUG_AGGREGATE_REPORT_OBJECTS_KEY = "debug_aggregatable_results";
217         String VERBOSE_DEBUG_OBJECTS_KEY = "verbose_debug_reports";
218         String INSTALLS_KEY = "installs";
219         String UNINSTALLS_KEY = "uninstalls";
220         String INSTALLS_URI_KEY = "uri";
221         String INSTALLS_TIMESTAMP_KEY = "timestamp";
222         String REPORT_TIME_KEY = "report_time";
223         String REPORT_TO_KEY = "report_url";
224         String PAYLOAD_KEY = "payload";
225         String ENROLL = "enroll";
226         String PLATFORM_AD_ID = "platform_ad_id";
227         String SOURCE_REGISTRATION_TIME = "source_registration_time";
228         String SCHEDULED_REPORT_TIME = "scheduled_report_time";
229     }
230 
231     private interface ApiConfigKeys {
232         // Privacy params
233         String NAVIGATION_SOURCE_TRIGGER_DATA_CARDINALITY =
234                 "navigation_source_trigger_data_cardinality";
235     }
236 
237     public static class ParamsProvider {
238         // Privacy params
239         private Integer mNavigationTriggerDataCardinality;
ParamsProvider(JSONObject json)240         public ParamsProvider(JSONObject json) throws JSONException {
241             // Privacy params
242             if (!json.isNull(ApiConfigKeys.NAVIGATION_SOURCE_TRIGGER_DATA_CARDINALITY)) {
243                 mNavigationTriggerDataCardinality = json.getInt(
244                         ApiConfigKeys.NAVIGATION_SOURCE_TRIGGER_DATA_CARDINALITY);
245             } else {
246                 mNavigationTriggerDataCardinality =
247                         PrivacyParams.getNavigationTriggerDataCardinality();
248             }
249         }
250 
251         // Privacy params
getNavigationTriggerDataCardinality()252         public Integer getNavigationTriggerDataCardinality() {
253             return mNavigationTriggerDataCardinality;
254         }
255     }
256 
data(String testDirName, Function<String, String> preprocessor)257     static Collection<Object[]> data(String testDirName, Function<String, String> preprocessor)
258             throws IOException, JSONException {
259         return data(testDirName, preprocessor, new HashMap<>());
260     }
261 
data(String testDirName, Function<String, String> preprocessor, Map<String, String> apiConfigPhFlags)262     static Collection<Object[]> data(String testDirName, Function<String, String> preprocessor,
263             Map<String, String> apiConfigPhFlags) throws IOException, JSONException {
264         AssetManager assetManager = sContext.getAssets();
265         List<InputStream> inputStreams = new ArrayList<>();
266         List<String> dirPathList = new ArrayList<>(Collections.singletonList(testDirName));
267         List<String> testFileList = new ArrayList<>();
268         while (dirPathList.size() > 0) {
269             testDirName = dirPathList.remove(0);
270             String[] testAssets = assetManager.list(testDirName);
271             for (String testAsset : testAssets) {
272                 if (isDirectory(testDirName + "/" + testAsset)) {
273                     dirPathList.add(testDirName + "/" + testAsset);
274                 } else {
275                     inputStreams.add(assetManager.open(testDirName + "/" + testAsset));
276                     testFileList.add(testAsset);
277                 }
278             }
279         }
280         return getTestCasesFrom(
281                 inputStreams,
282                 testFileList.stream().toArray(String[]::new),
283                 preprocessor,
284                 apiConfigPhFlags);
285     }
286 
isDirectory(String testAssetName)287     private static boolean isDirectory(String testAssetName) throws IOException {
288         String[] assetList = sContext.getAssets().list(testAssetName);
289         if (assetList.length > 0) {
290             return true;
291         }
292         return false;
293     }
294 
295     /** Returns the first URL in the list of registration responses. */
getFirstUrl(JSONObject registrationObj)296     public static String getFirstUrl(JSONObject registrationObj)
297             throws JSONException {
298         return registrationObj
299                 .getJSONArray(TestFormatJsonMapping.URI_TO_RESPONSE_HEADERS_KEY)
300                 .getJSONObject(0)
301                 .getString(TestFormatJsonMapping.URI_TO_RESPONSE_HEADERS_URL_KEY);
302     }
303 
hasArDebugPermission(JSONObject obj)304     public static boolean hasArDebugPermission(JSONObject obj) throws JSONException {
305         JSONObject urlToResponse =
306                 obj.getJSONArray(TestFormatJsonMapping.URI_TO_RESPONSE_HEADERS_KEY)
307                         .getJSONObject(0);
308         return urlToResponse.optBoolean(TestFormatJsonMapping.DEBUG_PERMISSION_KEY, false);
309     }
310 
hasAdIdPermission(JSONObject obj)311     public static boolean hasAdIdPermission(JSONObject obj) throws JSONException {
312         JSONObject urlToResponse =
313                 obj.getJSONArray(TestFormatJsonMapping.URI_TO_RESPONSE_HEADERS_KEY)
314                         .getJSONObject(0);
315         return urlToResponse.optBoolean(TestFormatJsonMapping.HAS_AD_ID_PERMISSION, false);
316     }
317 
hasSourceDebugReportingPermission(JSONObject obj)318     public static boolean hasSourceDebugReportingPermission(JSONObject obj) throws JSONException {
319         JSONObject headersMapJson =
320                 obj.getJSONArray(TestFormatJsonMapping.URI_TO_RESPONSE_HEADERS_KEY)
321                         .getJSONObject(0)
322                         .getJSONObject(TestFormatJsonMapping.URI_TO_RESPONSE_HEADERS_RESPONSE_KEY);
323         if (headersMapJson.isNull(TestFormatJsonMapping.SOURCE_REGISTRATION_HEADER)) {
324             return false;
325         }
326         Object registerSourceObj =
327                 headersMapJson.get(TestFormatJsonMapping.SOURCE_REGISTRATION_HEADER);
328         if (!(registerSourceObj instanceof JSONObject)) {
329             return false;
330         }
331         return ((JSONObject) registerSourceObj).optBoolean(
332                 TestFormatJsonMapping.DEBUG_REPORTING_KEY, false);
333     }
334 
hasTriggerDebugReportingPermission(JSONObject obj)335     public static boolean hasTriggerDebugReportingPermission(JSONObject obj) throws JSONException {
336         JSONObject headersMapJson =
337                 obj.getJSONArray(TestFormatJsonMapping.URI_TO_RESPONSE_HEADERS_KEY)
338                         .getJSONObject(0)
339                         .getJSONObject(TestFormatJsonMapping.URI_TO_RESPONSE_HEADERS_RESPONSE_KEY);
340         if (headersMapJson.isNull(TestFormatJsonMapping.TRIGGER_REGISTRATION_HEADER)) {
341             return false;
342         }
343         Object registerTriggerObj =
344                 headersMapJson.get(TestFormatJsonMapping.TRIGGER_REGISTRATION_HEADER);
345         if (!(registerTriggerObj instanceof JSONObject)) {
346             return false;
347         }
348         return ((JSONObject)  registerTriggerObj).optBoolean(
349                 TestFormatJsonMapping.DEBUG_REPORTING_KEY, false);
350     }
351 
getUriToResponseHeadersMap( JSONObject obj)352     public static Map<String, List<Map<String, List<String>>>> getUriToResponseHeadersMap(
353             JSONObject obj) throws JSONException {
354         JSONArray uriToResArray = obj.getJSONArray(
355                 TestFormatJsonMapping.URI_TO_RESPONSE_HEADERS_KEY);
356         Map<String, List<Map<String, List<String>>>> uriToResponseHeadersMap = new HashMap<>();
357 
358         for (int i = 0; i < uriToResArray.length(); i++) {
359             JSONObject urlToResponse = uriToResArray.getJSONObject(i);
360             String uri = urlToResponse.getString(
361                     TestFormatJsonMapping.URI_TO_RESPONSE_HEADERS_URL_KEY);
362             JSONObject headersMapJson = urlToResponse.getJSONObject(
363                     TestFormatJsonMapping.URI_TO_RESPONSE_HEADERS_RESPONSE_KEY);
364 
365             Iterator<String> headers = headersMapJson.keys();
366             Map<String, List<String>> headersMap = new HashMap<>();
367 
368             while (headers.hasNext()) {
369                 String header = headers.next();
370                 if (!headersMapJson.isNull(header)) {
371                     String data = headersMapJson.getString(header);
372                     if (header.equals("Attribution-Reporting-Redirect")) {
373                         JSONArray redirects = new JSONArray(data);
374                         for (int j = 0; j < redirects.length(); j++) {
375                             String redirectUri = redirects.getString(j);
376                             headersMap.computeIfAbsent(
377                                     header, k -> new ArrayList<>()).add(redirectUri);
378                         }
379                     } else {
380                         headersMap.put(header, Collections.singletonList(data));
381                     }
382                 } else {
383                     headersMap.put(header, null);
384                 }
385             }
386 
387             uriToResponseHeadersMap.computeIfAbsent(uri, k -> new ArrayList<>()).add(headersMap);
388         }
389 
390         return uriToResponseHeadersMap;
391     }
392 
getUriConfigMap(JSONObject obj)393     public static Map<String, UriConfig> getUriConfigMap(JSONObject obj) throws JSONException {
394         JSONArray uriToResArray =
395                 obj.getJSONArray(TestFormatJsonMapping.URI_TO_RESPONSE_HEADERS_KEY);
396         Map<String, UriConfig> uriConfigMap = new HashMap<>();
397 
398         for (int i = 0; i < uriToResArray.length(); i++) {
399             JSONObject urlToResponse = uriToResArray.getJSONObject(i);
400             String uri =
401                     urlToResponse.getString(TestFormatJsonMapping.URI_TO_RESPONSE_HEADERS_URL_KEY);
402             uriConfigMap.put(uri, new UriConfig(urlToResponse));
403         }
404 
405         return uriConfigMap;
406     }
407 
getInputEvent()408     public static InputEvent getInputEvent() {
409         return obtain(
410                 0 /*long downTime*/,
411                 0 /*long eventTime*/,
412                 ACTION_BUTTON_PRESS,
413                 1 /*int pointerCount*/,
414                 new PointerProperties[] { new PointerProperties() },
415                 new PointerCoords[] { new PointerCoords() },
416                 0 /*int metaState*/,
417                 0 /*int buttonState*/,
418                 1.0f /*float xPrecision*/,
419                 1.0f /*float yPrecision*/,
420                 0 /*int deviceId*/,
421                 0 /*int edgeFlags*/,
422                 InputDevice.SOURCE_TOUCH_NAVIGATION,
423                 0 /*int flags*/);
424     }
425 
getReportUrl(ReportType reportType, String origin)426     static String getReportUrl(ReportType reportType, String origin) {
427         String reportUrl = null;
428         if (reportType == ReportType.EVENT) {
429             reportUrl = EVENT_ATTRIBUTION_REPORT_URI_PATH;
430         } else if (reportType == ReportType.AGGREGATE) {
431             reportUrl = AGGREGATE_ATTRIBUTION_REPORT_URI_PATH;
432         } else if (reportType == ReportType.EVENT_DEBUG) {
433             reportUrl = DEBUG_EVENT_ATTRIBUTION_REPORT_URI_PATH;
434         } else if (reportType == ReportType.AGGREGATE_DEBUG) {
435             reportUrl = DEBUG_AGGREGATE_ATTRIBUTION_REPORT_URI_PATH;
436         } else if (reportType == ReportType.VERBOSE_DEBUG) {
437             reportUrl = DEBUG_REPORT_URI_PATH;
438         }
439         return origin + "/" + reportUrl;
440     }
441 
clearDatabase()442     static void clearDatabase() {
443         SQLiteDatabase db = DbTestUtil.getMeasurementDbHelperForTest().getWritableDatabase();
444         emptyTables(db);
445 
446         DbTestUtil.getSharedDbHelperForTest()
447                 .getWritableDatabase()
448                 .delete("enrollment_data", null, null);
449     }
450 
451     // The 'name' parameter is needed for the JUnit parameterized test, although it's ostensibly
452     // unused by this constructor.
E2ETest( Collection<Action> actions, ReportObjects expectedOutput, String name, Map<String, String> phFlagsMap)453     E2ETest(
454             Collection<Action> actions,
455             ReportObjects expectedOutput,
456             String name,
457             Map<String, String> phFlagsMap) {
458         mActionsList = actions;
459         mExpectedOutput = expectedOutput;
460         mActualOutput = new ReportObjects();
461         mName = name;
462         mPhFlagsMap = phFlagsMap;
463     }
464 
465     @Test
runTest()466     public void runTest() throws IOException, JSONException, DeviceConfig.BadConfigException {
467         clearDatabase();
468         setupDeviceConfigForPhFlags();
469         for (Action action : mActionsList) {
470             if (action instanceof RegisterSource) {
471                 processAction((RegisterSource) action);
472             } else if (action instanceof RegisterTrigger) {
473                 processAction((RegisterTrigger) action);
474             } else if (action instanceof RegisterWebSource) {
475                 processAction((RegisterWebSource) action);
476             } else if (action instanceof RegisterWebTrigger) {
477                 processAction((RegisterWebTrigger) action);
478             } else if (action instanceof RegisterListSources) {
479                 processAction((RegisterListSources) action);
480             } else if (action instanceof EventReportingJob) {
481                 processAction((EventReportingJob) action);
482             } else if (action instanceof AggregateReportingJob) {
483                 processAction((AggregateReportingJob) action);
484             } else if (action instanceof InstallApp) {
485                 processAction((InstallApp) action);
486             } else if (action instanceof UninstallApp) {
487                 processAction((UninstallApp) action);
488             }
489         }
490         evaluateResults();
491         clearDatabase();
492     }
493 
log(String message)494     public void log(String message) {
495         Log.i(LOG_TAG, String.format("%s: %s", mName, message));
496     }
497 
498     /**
499      * The reporting job may be handled differently depending on whether network requests are mocked
500      * or a test server is used.
501      */
processAction(EventReportingJob reportingJob)502     abstract void processAction(EventReportingJob reportingJob) throws IOException, JSONException;
503 
504     /**
505      * The reporting job may be handled differently depending on whether network requests are mocked
506      * or a test server is used.
507      */
processAction(AggregateReportingJob reportingJob)508     abstract void processAction(AggregateReportingJob reportingJob)
509             throws IOException, JSONException;
510 
511     /**
512      * Override with HTTP response mocks, for example.
513      */
prepareRegistrationServer(RegisterSource sourceRegistration)514     abstract void prepareRegistrationServer(RegisterSource sourceRegistration)
515             throws IOException;
516 
517     /** Override with HTTP response mocks, for example. */
prepareRegistrationServer(RegisterListSources sourceRegistration)518     abstract void prepareRegistrationServer(RegisterListSources sourceRegistration)
519             throws IOException;
520 
521     /**
522      * Override with HTTP response mocks, for example.
523      */
prepareRegistrationServer(RegisterTrigger triggerRegistration)524     abstract void prepareRegistrationServer(RegisterTrigger triggerRegistration)
525             throws IOException;
526 
527     /** Override with HTTP response mocks, for example. */
prepareRegistrationServer(RegisterWebSource sourceRegistration)528     abstract void prepareRegistrationServer(RegisterWebSource sourceRegistration)
529             throws IOException;
530 
531     /** Override with HTTP response mocks, for example. */
prepareRegistrationServer(RegisterWebTrigger triggerRegistration)532     abstract void prepareRegistrationServer(RegisterWebTrigger triggerRegistration)
533             throws IOException;
534 
hashForEventReportObject(OutputType outputType, JSONObject obj)535     private static int hashForEventReportObject(OutputType outputType, JSONObject obj) {
536         int n = EventReportPayloadKeys.STRINGS.size();
537         int numValuesExcludingN = 5;
538         Object[] objArray = new Object[n + numValuesExcludingN];
539         objArray[0] = obj.optLong(TestFormatJsonMapping.REPORT_TIME_KEY, 0L);
540         String url = obj.optString(TestFormatJsonMapping.REPORT_TO_KEY, "");
541         objArray[1] =
542                 outputType == OutputType.EXPECTED ? url : getReportUrl(ReportType.EVENT, url);
543         JSONObject payload = obj.optJSONObject(TestFormatJsonMapping.PAYLOAD_KEY);
544         objArray[2] = payload.optDouble(EventReportPayloadKeys.DOUBLE, 0);
545         // Try string then JSONArray in order so as to override the string if the array parsing is
546         // successful.
547         objArray[3] = null;
548         String maybeString = payload.optString(EventReportPayloadKeys.STRING_OR_ARRAY);
549         if (maybeString != null) {
550             objArray[3] = maybeString;
551         }
552         JSONArray maybeArray1 = payload.optJSONArray(EventReportPayloadKeys.STRING_OR_ARRAY);
553         if (maybeArray1 != null) {
554             objArray[3] = maybeArray1;
555         }
556         JSONArray maybeArray2 = payload.optJSONArray(EventReportPayloadKeys.ARRAY);
557         objArray[4] = maybeArray2;
558         for (int i = 0; i < n; i++) {
559             objArray[i + numValuesExcludingN] =
560                     payload.optString(EventReportPayloadKeys.STRINGS.get(i), "");
561         }
562         return Arrays.hashCode(objArray);
563     }
564 
565     // 'obj1' is the expected result, 'obj2' is the actual result.
matchReportTimeAndReportTo(ReportType reportType, JSONObject obj1, JSONObject obj2)566     private boolean matchReportTimeAndReportTo(ReportType reportType, JSONObject obj1,
567             JSONObject obj2) throws JSONException {
568         if (obj1.getLong(TestFormatJsonMapping.REPORT_TIME_KEY)
569                 != obj2.getLong(TestFormatJsonMapping.REPORT_TIME_KEY)) {
570             log("Report-time mismatch. Report type: " + reportType.name());
571             return false;
572         }
573         String reportTo1 = obj1.getString(TestFormatJsonMapping.REPORT_TO_KEY);
574         String reportTo2 = getReportUrl(reportType,
575                 obj2.getString(TestFormatJsonMapping.REPORT_TO_KEY));
576         if (!reportTo1.equals(reportTo2)) {
577             log(String.format(
578                     "Report-to mismatch. Report type: %s Report-to-1: %s Report-to-2: %s",
579                     reportType.name(), reportTo1, reportTo2));
580             return false;
581         }
582         return true;
583     }
584 
areEqualStringOrJSONArray(Object expected, Object actual)585     private static boolean areEqualStringOrJSONArray(Object expected, Object actual)
586             throws JSONException {
587         if (expected instanceof String) {
588             return (actual instanceof String) && (expected.equals(actual));
589         } else {
590             JSONArray jsonArr1 = (JSONArray) expected;
591             JSONArray jsonArr2 = (JSONArray) actual;
592             if (jsonArr1.length() != jsonArr2.length()) {
593                 return false;
594             }
595             for (int i = 0; i < jsonArr1.length(); i++) {
596                 if (!jsonArr1.getString(i).equals(jsonArr2.getString(i))) {
597                     return false;
598                 }
599             }
600         }
601         return true;
602     }
603 
areNullOrEqualJSONArray(JSONArray expected, JSONArray actual)604     private static boolean areNullOrEqualJSONArray(JSONArray expected, JSONArray actual)
605             throws JSONException {
606         if (expected == null) {
607             return actual == null;
608         } else if (actual == null) {
609             return false;
610         }
611         if (expected.length() != actual.length()) {
612             return false;
613         }
614         for (int i = 0; i < expected.length(); i++) {
615             if (!expected.getString(i).equals(actual.getString(i))) {
616                 return false;
617             }
618         }
619         return true;
620     }
621 
areEqualEventReportJsons( ReportType reportType, JSONObject expected, JSONObject actual)622     private boolean areEqualEventReportJsons(
623             ReportType reportType, JSONObject expected, JSONObject actual) throws JSONException {
624         JSONObject expectedPayload = expected.getJSONObject(TestFormatJsonMapping.PAYLOAD_KEY);
625         JSONObject actualPayload = actual.getJSONObject(TestFormatJsonMapping.PAYLOAD_KEY);
626         if (expectedPayload.getDouble(EventReportPayloadKeys.DOUBLE)
627                 != actualPayload.getDouble(EventReportPayloadKeys.DOUBLE)) {
628             log("Event payload double mismatch. Report type: " + reportType.name());
629             return false;
630         }
631         if (!areEqualStringOrJSONArray(
632                 expectedPayload.get(EventReportPayloadKeys.STRING_OR_ARRAY),
633                 actualPayload.get(EventReportPayloadKeys.STRING_OR_ARRAY))) {
634             log("Event payload string-or-array mismatch. Report type: " + reportType.name());
635             return false;
636         }
637         if (!areNullOrEqualJSONArray(
638                 expectedPayload.optJSONArray(EventReportPayloadKeys.ARRAY),
639                 actualPayload.optJSONArray(EventReportPayloadKeys.ARRAY))) {
640             log("Event payload array mismatch. Report type: " + reportType.name());
641             return false;
642         }
643         for (String key : EventReportPayloadKeys.STRINGS) {
644             if (!expectedPayload.optString(key, "").equals(actualPayload.optString(key, ""))) {
645                 log("Event payload string mismatch: " + key + ". Report type: "
646                         + reportType.name());
647                 return false;
648             }
649         }
650         return matchReportTimeAndReportTo(reportType, expected, actual);
651     }
652 
areEqualSharedInfoJsons(JSONObject obj1, JSONObject obj2)653     private boolean areEqualSharedInfoJsons(JSONObject obj1, JSONObject obj2) throws JSONException {
654         for (String key : sAggregateReportSharedInfoKeys) {
655             if (!obj1.optString(key, "").equals(obj2.optString(key, ""))) {
656                 log("Aggregate shared_info mismatch for key " + key);
657                 return false;
658             }
659         }
660         return true;
661     }
662 
areEqualAggregateReportJsons( ReportType reportType, JSONObject expected, JSONObject actual)663     private boolean areEqualAggregateReportJsons(
664             ReportType reportType, JSONObject expected, JSONObject actual) throws JSONException {
665         JSONObject payload1 = expected.getJSONObject(TestFormatJsonMapping.PAYLOAD_KEY);
666         JSONObject payload2 = actual.getJSONObject(TestFormatJsonMapping.PAYLOAD_KEY);
667         if (!areEqualSharedInfoJsons(payload1.getJSONObject(AggregateReportPayloadKeys.SHARED_INFO),
668                 payload2.getJSONObject(AggregateReportPayloadKeys.SHARED_INFO))) {
669             log("Aggregate report shared_info mismatch");
670             return false;
671         }
672         if (!payload1.optString(
673                 AggregateReportPayloadKeys.AGGREGATION_COORDINATOR_ORIGIN, "").equals(
674                         payload2.optString(
675                                 AggregateReportPayloadKeys.AGGREGATION_COORDINATOR_ORIGIN, ""))) {
676             log("Aggregate aggregation coordinator origin mismatch");
677             return false;
678         }
679         if (!payload1.optString(AggregateReportPayloadKeys.SOURCE_DEBUG_KEY, "")
680                 .equals(payload2.optString(AggregateReportPayloadKeys.SOURCE_DEBUG_KEY, ""))) {
681             log("Source debug key mismatch");
682             return false;
683         }
684         if (!payload1.optString(AggregateReportPayloadKeys.TRIGGER_DEBUG_KEY, "")
685                 .equals(payload2.optString(AggregateReportPayloadKeys.TRIGGER_DEBUG_KEY, ""))) {
686             log("Trigger debug key mismatch");
687             return false;
688         }
689         JSONArray histograms1 = payload1.optJSONArray(AggregateReportPayloadKeys.HISTOGRAMS);
690         JSONArray histograms2 = payload2.optJSONArray(AggregateReportPayloadKeys.HISTOGRAMS);
691         if (!getComparableHistograms(histograms1).equals(getComparableHistograms(histograms2))) {
692             log("Aggregate histogram mismatch");
693             return false;
694         }
695         if (!payload1.optString(AggregateReportPayloadKeys.TRIGGER_CONTEXT_ID, "")
696                 .equals(payload2.optString(AggregateReportPayloadKeys.TRIGGER_CONTEXT_ID, ""))) {
697             log("Trigger context id mismatch");
698             return false;
699         }
700 
701         return matchReportTimeAndReportTo(reportType, expected, actual);
702     }
703 
areEqualDebugReportJsons( ReportType reportType, JSONObject expected, JSONObject actual)704     private boolean areEqualDebugReportJsons(
705             ReportType reportType, JSONObject expected, JSONObject actual) throws JSONException {
706         JSONArray payloads1 = expected.getJSONArray(TestFormatJsonMapping.PAYLOAD_KEY);
707         JSONArray payloads2 = actual.getJSONArray(TestFormatJsonMapping.PAYLOAD_KEY);
708         if (payloads1.length() != payloads2.length()) {
709             log("Debug report size mismatch");
710             return false;
711         }
712         for (int i = 0; i < payloads1.length(); i++) {
713             JSONObject payload1 = payloads1.getJSONObject(i);
714             String type = payload1.optString(DebugReportPayloadKeys.TYPE, "");
715             boolean hasSameType = false;
716             for (int j = 0; j < payloads2.length(); j++) {
717                 JSONObject payload2 = payloads2.getJSONObject(j);
718                 if (type.equals(payload2.optString(DebugReportPayloadKeys.TYPE, ""))) {
719                     hasSameType = true;
720                     JSONObject body1 = payload1.getJSONObject(DebugReportPayloadKeys.BODY);
721                     JSONObject body2 = payload2.getJSONObject(DebugReportPayloadKeys.BODY);
722                     if (body1.length() != body2.length()) {
723                         log(
724                                 "Verbose debug report payload body key-value pair not equal for"
725                                         + " type: "
726                                         + type);
727                         return false;
728                     }
729                     for (String key : DebugReportPayloadKeys.BODY_KEYS) {
730                         if (!body1.optString(key, "").equals(body2.optString(key, ""))) {
731                             log(
732                                     "Verbose debug report payload body mismatch for type: "
733                                             + type
734                                             + ", body key: "
735                                             + key);
736                             return false;
737                         }
738                     }
739                     break;
740                 }
741             }
742             if (!hasSameType) {
743                 log("Debug report type mismatch.");
744                 return false;
745             }
746         }
747         return matchReportTimeAndReportTo(reportType, expected, actual);
748     }
749 
getComparableHistograms(@ullable JSONArray arr)750     private static String getComparableHistograms(@Nullable JSONArray arr) {
751         if (arr == null) {
752             return "";
753         }
754         try {
755             List<String> tempList = new ArrayList<>();
756             for (int i = 0; i < arr.length(); i++) {
757                 JSONObject pair = arr.getJSONObject(i);
758                 tempList.add(pair.getString(AggregateHistogramKeys.BUCKET) + ","
759                         + pair.getString(AggregateHistogramKeys.VALUE));
760             }
761             Collections.sort(tempList);
762             return String.join(";", tempList);
763         } catch (JSONException ignored) {
764             return "";
765         }
766     }
767 
reportTimeFrom(JSONObject obj)768     private static long reportTimeFrom(JSONObject obj) {
769         return obj.optLong(TestFormatJsonMapping.REPORT_TIME_KEY, 0L);
770     }
771 
aggregateReportToFrom(OutputType outputType, JSONObject obj)772     private static String aggregateReportToFrom(OutputType outputType, JSONObject obj) {
773         String url = obj.optString(TestFormatJsonMapping.REPORT_TO_KEY, "");
774         return outputType == OutputType.EXPECTED
775                 ? url
776                 : getReportUrl(ReportType.AGGREGATE, url);
777     }
778 
sourceRegistrationTimeFrom(JSONObject obj)779     private static String sourceRegistrationTimeFrom(JSONObject obj) {
780         JSONObject payload = obj.optJSONObject(TestFormatJsonMapping.PAYLOAD_KEY);
781         return payload.optJSONObject(AggregateReportPayloadKeys.SHARED_INFO)
782                 .optString("source_registration_time", "");
783     }
784 
sortEventReportObjects(OutputType outputType, List<JSONObject> eventReportObjects)785     private static void sortEventReportObjects(OutputType outputType,
786             List<JSONObject> eventReportObjects) {
787         eventReportObjects.sort(
788                 // Report time can vary across implementations so cannot be included in the hash;
789                 // they should be similarly ordered, however, so we can use them to sort.
790                 Comparator.comparing(E2ETest::reportTimeFrom)
791                         .thenComparing(obj -> hashForEventReportObject(outputType, obj)));
792     }
793 
sortAggregateReportObjects(OutputType outputType, List<JSONObject> aggregateReportObjects)794     private static void sortAggregateReportObjects(OutputType outputType,
795             List<JSONObject> aggregateReportObjects) {
796         aggregateReportObjects.sort(
797                 Comparator.comparing(E2ETest::reportTimeFrom)
798                         .thenComparing(E2ETest::sourceRegistrationTimeFrom)
799                         .thenComparing(obj -> aggregateReportToFrom(outputType, obj)));
800     }
801 
areEqual(ReportObjects expected, ReportObjects actual)802     private boolean areEqual(ReportObjects expected, ReportObjects actual) throws JSONException {
803         if (expected.mEventReportObjects.size() != actual.mEventReportObjects.size()
804                 || expected.mAggregateReportObjects.size() != actual.mAggregateReportObjects.size()
805                 || expected.mDebugAggregateReportObjects.size()
806                         != actual.mDebugAggregateReportObjects.size()
807                 || expected.mDebugEventReportObjects.size()
808                         != actual.mDebugEventReportObjects.size()
809                 || expected.mDebugReportObjects.size() != actual.mDebugReportObjects.size()) {
810             log("Report list size mismatch");
811             return false;
812         }
813         for (int i = 0; i < expected.mEventReportObjects.size(); i++) {
814             if (!areEqualEventReportJsons(
815                     ReportType.EVENT,
816                     expected.mEventReportObjects.get(i),
817                     actual.mEventReportObjects.get(i))) {
818                 log("Event report object mismatch");
819                 return false;
820             }
821         }
822         for (int i = 0; i < expected.mAggregateReportObjects.size(); i++) {
823             if (!areEqualAggregateReportJsons(
824                     ReportType.AGGREGATE,
825                     expected.mAggregateReportObjects.get(i),
826                     actual.mAggregateReportObjects.get(i))) {
827                 log("Aggregate report object mismatch");
828                 return false;
829             }
830         }
831         for (int i = 0; i < expected.mDebugEventReportObjects.size(); i++) {
832             if (!areEqualEventReportJsons(
833                     ReportType.EVENT_DEBUG,
834                     expected.mDebugEventReportObjects.get(i),
835                     actual.mDebugEventReportObjects.get(i))) {
836                 log("Debug event report object mismatch");
837                 return false;
838             }
839         }
840         for (int i = 0; i < expected.mDebugAggregateReportObjects.size(); i++) {
841             if (!areEqualAggregateReportJsons(
842                     ReportType.AGGREGATE_DEBUG,
843                     expected.mDebugAggregateReportObjects.get(i),
844                     actual.mDebugAggregateReportObjects.get(i))) {
845                 log("Debug aggregate report object mismatch");
846                 return false;
847             }
848         }
849         for (int i = 0; i < expected.mDebugReportObjects.size(); i++) {
850             if (!areEqualDebugReportJsons(
851                     ReportType.VERBOSE_DEBUG,
852                     expected.mDebugReportObjects.get(i),
853                     actual.mDebugReportObjects.get(i))) {
854                 log("Debug report object mismatch");
855                 return false;
856             }
857         }
858 
859         return true;
860     }
861 
getTestFailureMessage(ReportObjects expectedOutput, ReportObjects actualOutput)862     private static String getTestFailureMessage(ReportObjects expectedOutput,
863             ReportObjects actualOutput) {
864         return String.format(
865                         "Actual output does not match expected.\n\n"
866                             + "(Note that displayed randomized_trigger_rate and report_url are not"
867                             + " normalised.\n"
868                             + "Note that report IDs are ignored in comparisons since they are not"
869                             + " known in advance.)\n\n"
870                             + "Event report objects:\n"
871                             + "%s\n\n"
872                             + "Debug Event report objects:\n"
873                             + "%s\n\n"
874                             + "Expected aggregate report objects: %s\n\n"
875                             + "Actual aggregate report objects: %s\n\n"
876                             + "Expected debug aggregate report objects: %s\n\n"
877                             + "Actual debug aggregate report objects: %s\n\n"
878                             + "Expected debug report objects: %s\n\n"
879                             + "Actual debug report objects: %s\n",
880                         prettify(
881                                 expectedOutput.mEventReportObjects,
882                                 actualOutput.mEventReportObjects),
883                         prettify(
884                                 expectedOutput.mDebugEventReportObjects,
885                                 actualOutput.mDebugEventReportObjects),
886                         expectedOutput.mAggregateReportObjects,
887                         actualOutput.mAggregateReportObjects,
888                         expectedOutput.mDebugAggregateReportObjects,
889                         actualOutput.mDebugAggregateReportObjects,
890                         expectedOutput.mDebugReportObjects,
891                         actualOutput.mDebugReportObjects)
892                 + getDatastoreState();
893     }
894 
prettify(List<JSONObject> expected, List<JSONObject> actual)895     private static String prettify(List<JSONObject> expected, List<JSONObject> actual) {
896         StringBuilder result = new StringBuilder("(Expected ::: Actual)"
897                 + "\n------------------------\n");
898         for (int i = 0; i < Math.max(expected.size(), actual.size()); i++) {
899             if (i < expected.size() && i < actual.size()) {
900                 result.append(prettifyObjs(expected.get(i), actual.get(i)));
901             } else {
902                 if (i < expected.size()) {
903                     result.append(prettifyObj("", expected.get(i)));
904                 }
905                 if (i < actual.size()) {
906                     result.append(prettifyObj(" ::: ", actual.get(i)));
907                 }
908             }
909             result.append("\n------------------------\n");
910         }
911         return result.toString();
912     }
913 
prettifyObjs(JSONObject obj1, JSONObject obj2)914     private static String prettifyObjs(JSONObject obj1, JSONObject obj2) {
915         StringBuilder result = new StringBuilder();
916         result.append(TestFormatJsonMapping.REPORT_TIME_KEY + ": ")
917                 .append(obj1.optString(TestFormatJsonMapping.REPORT_TIME_KEY))
918                 .append(" ::: ")
919                 .append(obj2.optString(TestFormatJsonMapping.REPORT_TIME_KEY))
920                 .append("\n");
921         result.append(TestFormatJsonMapping.REPORT_TO_KEY + ": ")
922                 .append(obj1.optString(TestFormatJsonMapping.REPORT_TO_KEY))
923                 .append(" ::: ")
924                 .append(obj2.optString(TestFormatJsonMapping.REPORT_TO_KEY))
925                 .append("\n");
926         JSONObject payload1 = obj1.optJSONObject(TestFormatJsonMapping.PAYLOAD_KEY);
927         JSONObject payload2 = obj2.optJSONObject(TestFormatJsonMapping.PAYLOAD_KEY);
928         try {
929             result.append(EventReportPayloadKeys.STRING_OR_ARRAY + ": ")
930                     .append(payload1.get(EventReportPayloadKeys.STRING_OR_ARRAY).toString())
931                     .append(" ::: ")
932                     .append(payload2.get(EventReportPayloadKeys.STRING_OR_ARRAY).toString() + "\n");
933         } catch (JSONException e) {
934             result.append("JSONObject::get failed for EventReportPayloadKeys.STRING_OR_ARRAY "
935                     + e + "\n");
936         }
937         result.append(EventReportPayloadKeys.ARRAY + ": ")
938                 .append(payload1.optJSONArray(EventReportPayloadKeys.ARRAY))
939                 .append(" ::: ")
940                 .append(payload2.optJSONArray(EventReportPayloadKeys.ARRAY))
941                 .append("\n");
942         for (String key : EventReportPayloadKeys.STRINGS) {
943             result.append(key)
944                     .append(": ")
945                     .append(payload1.optString(key))
946                     .append(" ::: ")
947                     .append(payload2.optString(key))
948                     .append("\n");
949         }
950         result.append(EventReportPayloadKeys.DOUBLE + ": ")
951                 .append(payload1.optDouble(EventReportPayloadKeys.DOUBLE))
952                 .append(" ::: ")
953                 .append(payload2.optDouble(EventReportPayloadKeys.DOUBLE));
954         return result.toString();
955     }
956 
prettifyObj(String pad, JSONObject obj)957     private static String prettifyObj(String pad, JSONObject obj) {
958         StringBuilder result = new StringBuilder();
959         result.append(TestFormatJsonMapping.REPORT_TIME_KEY + ": ")
960                 .append(pad)
961                 .append(obj.optString(TestFormatJsonMapping.REPORT_TIME_KEY))
962                 .append("\n");
963         JSONObject payload = obj.optJSONObject(TestFormatJsonMapping.PAYLOAD_KEY);
964         try {
965             result.append(EventReportPayloadKeys.STRING_OR_ARRAY + ": ")
966                     .append(pad)
967                     .append(payload.get(EventReportPayloadKeys.STRING_OR_ARRAY).toString() + "\n");
968         } catch (JSONException e) {
969             result.append("JSONObject::get failed for EventReportPayloadKeys.STRING_OR_ARRAY "
970                     + e + "\n");
971         }
972         result.append(EventReportPayloadKeys.ARRAY + ": ")
973                 .append(pad)
974                 .append(payload.optJSONArray(EventReportPayloadKeys.ARRAY))
975                 .append("\n");
976         for (String key : EventReportPayloadKeys.STRINGS) {
977             result.append(key).append(": ").append(pad).append(payload.optString(key)).append("\n");
978         }
979         result.append(EventReportPayloadKeys.DOUBLE + ": ")
980                 .append(pad)
981                 .append(payload.optDouble(EventReportPayloadKeys.DOUBLE));
982         return result.toString();
983     }
984 
getDatastoreState()985     protected static String getDatastoreState() {
986         StringBuilder result = new StringBuilder();
987         SQLiteDatabase db = DbTestUtil.getMeasurementDbHelperForTest().getWritableDatabase();
988         List<String> tableNames =
989                 ImmutableList.of(
990                         "msmt_source",
991                         "msmt_source_destination",
992                         "msmt_trigger",
993                         "msmt_attribution",
994                         "msmt_event_report",
995                         "msmt_aggregate_report",
996                         "msmt_async_registration_contract");
997         for (String tableName : tableNames) {
998             result.append("\n" + tableName + ":\n");
999             result.append(getTableState(db, tableName));
1000         }
1001         SQLiteDatabase enrollmentDb = DbTestUtil.getSharedDbHelperForTest().getWritableDatabase();
1002         List<String> enrollmentTables = ImmutableList.of("enrollment_data");
1003         for (String tableName : enrollmentTables) {
1004             result.append("\n" + tableName + ":\n");
1005             result.append(getTableState(enrollmentDb, tableName));
1006         }
1007         return result.toString();
1008     }
1009 
getTableState(SQLiteDatabase db, String tableName)1010     private static String getTableState(SQLiteDatabase db, String tableName) {
1011         Cursor cursor = getAllRows(db, tableName);
1012         StringBuilder result = new StringBuilder();
1013         while (cursor.moveToNext()) {
1014             result.append("\n" + DatabaseUtils.dumpCurrentRowToString(cursor));
1015         }
1016         return result.toString();
1017     }
1018 
getAllRows(SQLiteDatabase db, String tableName)1019     private static Cursor getAllRows(SQLiteDatabase db, String tableName) {
1020         return db.query(
1021                 /* boolean distinct */ false,
1022                 tableName,
1023                 /* String[] columns */ null,
1024                 /* String selection */ null,
1025                 /* String[] selectionArgs */ null,
1026                 /* String groupBy */ null,
1027                 /* String having */ null,
1028                 /* String orderBy */ null,
1029                 /* String limit */ null);
1030     }
1031 
getExpiryTimesFrom( Collection<List<Map<String, List<String>>>> responseHeadersCollection)1032     private static Set<Long> getExpiryTimesFrom(
1033             Collection<List<Map<String, List<String>>>> responseHeadersCollection)
1034             throws JSONException {
1035         Set<Long> expiryTimes = new HashSet<>();
1036 
1037         for (List<Map<String, List<String>>> responseHeaders : responseHeadersCollection) {
1038             for (Map<String, List<String>> headersMap : responseHeaders) {
1039                 if (!headersMap.containsKey(TestFormatJsonMapping.SOURCE_REGISTRATION_HEADER)) {
1040                     continue;
1041                 }
1042                 if (headersMap.get(TestFormatJsonMapping.SOURCE_REGISTRATION_HEADER) == null) {
1043                     continue;
1044                 }
1045                 try {
1046                     String sourceStr = headersMap.get(
1047                             TestFormatJsonMapping.SOURCE_REGISTRATION_HEADER).get(0);
1048                     JSONObject sourceJson = new JSONObject(sourceStr);
1049                     if (sourceJson.has("expiry")) {
1050                         expiryTimes.add(sourceJson.getLong("expiry"));
1051                     } else {
1052                         expiryTimes.add(
1053                                 MEASUREMENT_MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS);
1054                     }
1055                     if (sourceJson.has("event_report_windows")) {
1056                         expiryTimes.addAll(
1057                                 getFlexEndTimes(sourceJson.getJSONObject("event_report_windows")));
1058                     }
1059                 } catch (JSONException e) {
1060                     Log.i(LOG_TAG, String.format("%s is not a valid JSON object.",
1061                               TestFormatJsonMapping.SOURCE_REGISTRATION_HEADER));
1062                 }
1063             }
1064         }
1065 
1066         return expiryTimes;
1067     }
1068 
getFlexEndTimes(JSONObject eventReportWindows)1069     private static Set<Long> getFlexEndTimes(JSONObject eventReportWindows) throws JSONException {
1070         Set<Long> endTimes = new HashSet<>();
1071         JSONArray endTimesArray = eventReportWindows.getJSONArray("end_times");
1072         for (int i = 0; i < endTimesArray.length(); i++) {
1073             endTimes.add(endTimesArray.getLong(i));
1074         }
1075         return endTimes;
1076     }
1077 
roundSecondsToWholeDays(long seconds)1078     private static long roundSecondsToWholeDays(long seconds) {
1079         long remainder = seconds % TimeUnit.DAYS.toSeconds(1);
1080         boolean roundUp = remainder >= TimeUnit.DAYS.toSeconds(1) / 2L;
1081         return seconds - remainder + (roundUp ? TimeUnit.DAYS.toSeconds(1) : 0);
1082     }
1083 
maybeAddEventReportingJobTimes(boolean isEventType, long sourceTime, Collection<List<Map<String, List<String>>>> responseHeaders)1084     private static Set<Action> maybeAddEventReportingJobTimes(boolean isEventType,
1085             long sourceTime, Collection<List<Map<String, List<String>>>> responseHeaders)
1086             throws JSONException {
1087         Set<Action> reportingJobsActions = new HashSet<>();
1088         Set<Long> expiryTimes = getExpiryTimesFrom(responseHeaders);
1089         for (Long expiry : expiryTimes) {
1090             long validExpiry = expiry;
1091             if (expiry > MEASUREMENT_MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS) {
1092                 validExpiry = MEASUREMENT_MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS;
1093             } else if (expiry < MEASUREMENT_MIN_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS) {
1094                 validExpiry = MEASUREMENT_MIN_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS;
1095             }
1096             if (isEventType) {
1097                 validExpiry = roundSecondsToWholeDays(validExpiry);
1098             }
1099 
1100             long jobTime = sourceTime + 1000 * validExpiry + 3600000L;
1101 
1102             reportingJobsActions.add(new EventReportingJob(jobTime));
1103             // Add a job two days earlier for interop tests
1104             reportingJobsActions.add(new EventReportingJob(jobTime - TimeUnit.DAYS.toMillis(2)));
1105         }
1106 
1107         return reportingJobsActions;
1108     }
1109 
preprocessTestJson(String json)1110     static String preprocessTestJson(String json) {
1111         return json.replaceAll("\\.test(?=[\"\\/,])", ".com");
1112     }
1113 
1114     /**
1115      * Builds and returns test cases from a JSON InputStream to be used by JUnit parameterized
1116      * tests.
1117      *
1118      * @return A collection of Object arrays, each with
1119      * {@code [Collection<Object> actions, ReportObjects expectedOutput,
1120      * ParamsProvider paramsProvider, String name]}
1121      */
getTestCasesFrom( List<InputStream> inputStreams, String[] filenames, Function<String, String> preprocessor, Map<String, String> apiConfigPhFlags)1122     private static Collection<Object[]> getTestCasesFrom(
1123             List<InputStream> inputStreams,
1124             String[] filenames,
1125             Function<String, String> preprocessor,
1126             Map<String, String> apiConfigPhFlags) throws IOException, JSONException {
1127         List<Object[]> testCases = new ArrayList<>();
1128 
1129         for (int i = 0; i < inputStreams.size(); i++) {
1130             String name = filenames[i];
1131             if (name.equals(TestFormatJsonMapping.DEFAULT_CONFIG_FILENAME)) {
1132                 continue;
1133             }
1134             int size = inputStreams.get(i).available();
1135             byte[] buffer = new byte[size];
1136             inputStreams.get(i).read(buffer);
1137             inputStreams.get(i).close();
1138             String json = new String(buffer, StandardCharsets.UTF_8);
1139 
1140             JSONObject testObj = new JSONObject(preprocessor.apply(json));
1141             JSONObject input = testObj.getJSONObject(TestFormatJsonMapping.TEST_INPUT_KEY);
1142             JSONObject output = testObj.getJSONObject(TestFormatJsonMapping.TEST_OUTPUT_KEY);
1143 
1144             // "Actions" are source or trigger registrations, or a reporting job.
1145             List<Action> actions = new ArrayList<>();
1146 
1147             actions.addAll(createSourceBasedActions(input));
1148             actions.addAll(createTriggerBasedActions(input));
1149             actions.addAll(createInstallActions(input));
1150             actions.addAll(createUninstallActions(input));
1151 
1152             actions.sort(Comparator.comparing(Action::getComparable));
1153 
1154             ReportObjects expectedOutput = getExpectedOutput(output);
1155 
1156             JSONObject apiConfigObj = testObj.isNull(TestFormatJsonMapping.API_CONFIG_KEY)
1157                     ? new JSONObject()
1158                     : testObj.getJSONObject(TestFormatJsonMapping.API_CONFIG_KEY);
1159 
1160             ParamsProvider paramsProvider = new ParamsProvider(apiConfigObj);
1161 
1162             testCases.add(
1163                     new Object[] {
1164                         actions,
1165                         expectedOutput,
1166                         paramsProvider,
1167                         name,
1168                         extractPhFlags(testObj, apiConfigObj, apiConfigPhFlags)
1169                     });
1170         }
1171 
1172         return testCases;
1173     }
1174 
extractPhFlags(JSONObject testObj, JSONObject apiConfigObj, Map<String, String> apiConfigPhFlags)1175     private static Map<String, String> extractPhFlags(JSONObject testObj, JSONObject apiConfigObj,
1176             // Interop tests may have some configurations in the "api_config" field that correspond
1177             // with Ph Flags.
1178             Map<String, String> apiConfigPhFlags) {
1179         Map<String, String> phFlagsMap = new HashMap<>();
1180         apiConfigPhFlags.keySet().forEach((key) -> {
1181             if (!apiConfigObj.isNull(key)) {
1182                 // Interop test configuration uses a single key for both event and aggregate
1183                 // level attribution limits.
1184                 if (key.equals("rate_limit_max_attributions")) {
1185                     phFlagsMap.put(
1186                             FlagsConstants.KEY_MEASUREMENT_MAX_EVENT_ATTRIBUTION_PER_RATE_LIMIT_WINDOW,
1187                             apiConfigObj.optString(key));
1188                     phFlagsMap.put(
1189                             FlagsConstants.KEY_MEASUREMENT_MAX_AGGREGATE_ATTRIBUTION_PER_RATE_LIMIT_WINDOW,
1190                             apiConfigObj.optString(key));
1191                 } else {
1192                     phFlagsMap.put(apiConfigPhFlags.get(key),
1193                             apiConfigObj.optString(key));
1194                 }
1195             }
1196         });
1197         if (testObj.isNull(TestFormatJsonMapping.PH_FLAGS_OVERRIDE_KEY)) {
1198             return phFlagsMap;
1199         }
1200 
1201         JSONObject phFlagsObject =
1202                 testObj.optJSONObject(TestFormatJsonMapping.PH_FLAGS_OVERRIDE_KEY);
1203         phFlagsObject.keySet().forEach((key) -> phFlagsMap.put(key, phFlagsObject.optString(key)));
1204         return phFlagsMap;
1205     }
1206 
isSourceRegistration(JSONObject obj)1207     private static boolean isSourceRegistration(JSONObject obj) throws JSONException {
1208         JSONObject request = obj.getJSONObject(TestFormatJsonMapping.REGISTRATION_REQUEST_KEY);
1209         return !request.isNull(TestFormatJsonMapping.INPUT_EVENT_KEY)
1210                 || !request.isNull(TestFormatJsonMapping.INTEROP_INPUT_EVENT_KEY);
1211     }
1212 
addSourceRegistration(JSONObject sourceObj, List<Action> actions, Set<Action> eventReportingJobActions)1213     private static void addSourceRegistration(JSONObject sourceObj, List<Action> actions,
1214             Set<Action> eventReportingJobActions) throws JSONException {
1215         RegisterSource sourceRegistration = new RegisterSource(sourceObj);
1216         actions.add(sourceRegistration);
1217         // Add corresponding reporting job time actions
1218         eventReportingJobActions.addAll(
1219                 maybeAddEventReportingJobTimes(
1220                         sourceRegistration.mRegistrationRequest.getInputEvent() == null,
1221                         sourceRegistration.mTimestamp,
1222                         sourceRegistration.mUriToResponseHeadersMap.values()));
1223     }
1224 
createSourceBasedActions(JSONObject input)1225     private static List<Action> createSourceBasedActions(JSONObject input) throws JSONException {
1226         List<Action> actions = new ArrayList<>();
1227         // Set avoids duplicate reporting times across sources to do attribution upon.
1228         Set<Action> eventReportingJobActions = new HashSet<>();
1229 
1230         // Interop tests have all registration types in one list
1231         if (!input.isNull(TestFormatJsonMapping.REGISTRATIONS_KEY)) {
1232             JSONArray registrationArray = input.getJSONArray(
1233                     TestFormatJsonMapping.REGISTRATIONS_KEY);
1234             for (int i = 0; i < registrationArray.length(); i++) {
1235                 if (registrationArray.isNull(i)) {
1236                     continue;
1237                 }
1238                 JSONObject obj = registrationArray.getJSONObject(i);
1239                 if (isSourceRegistration(obj)) {
1240                     addSourceRegistration(obj, actions, eventReportingJobActions);
1241                 }
1242             }
1243         }
1244 
1245         if (!input.isNull(TestFormatJsonMapping.SOURCE_REGISTRATIONS_KEY)) {
1246             JSONArray sourceRegistrationArray = input.getJSONArray(
1247                     TestFormatJsonMapping.SOURCE_REGISTRATIONS_KEY);
1248             for (int j = 0; j < sourceRegistrationArray.length(); j++) {
1249                 if (sourceRegistrationArray.isNull(j)) {
1250                     continue;
1251                 }
1252                 addSourceRegistration(sourceRegistrationArray.getJSONObject(j),
1253                         actions, eventReportingJobActions);
1254             }
1255         }
1256 
1257         if (!input.isNull(TestFormatJsonMapping.WEB_SOURCES_KEY)) {
1258             JSONArray webSourceRegistrationArray =
1259                     input.getJSONArray(TestFormatJsonMapping.WEB_SOURCES_KEY);
1260             for (int j = 0; j < webSourceRegistrationArray.length(); j++) {
1261                 RegisterWebSource webSource =
1262                         new RegisterWebSource(webSourceRegistrationArray.getJSONObject(j));
1263                 actions.add(webSource);
1264                 // Add corresponding reporting job time actions
1265                 eventReportingJobActions.addAll(
1266                         maybeAddEventReportingJobTimes(
1267                                 webSource.mRegistrationRequest.getSourceRegistrationRequest()
1268                                         .getInputEvent() == null,
1269                                 webSource.mTimestamp,
1270                                 webSource.mUriToResponseHeadersMap.values()));
1271             }
1272         }
1273 
1274         if (!input.isNull(TestFormatJsonMapping.LIST_SOURCES_KEY)) {
1275             JSONArray listSourceRegistrationArray =
1276                     input.getJSONArray(TestFormatJsonMapping.LIST_SOURCES_KEY);
1277             for (int j = 0; j < listSourceRegistrationArray.length(); j++) {
1278                 RegisterListSources listSources =
1279                         new RegisterListSources(listSourceRegistrationArray.getJSONObject(j));
1280                 actions.add(listSources);
1281                 // Add corresponding reporting job time actions
1282                 eventReportingJobActions.addAll(
1283                         maybeAddEventReportingJobTimes(
1284                                 listSources
1285                                                 .mRegistrationRequest
1286                                                 .getSourceRegistrationRequest()
1287                                                 .getInputEvent()
1288                                         == null,
1289                                 listSources.mTimestamp,
1290                                 listSources.mUriToResponseHeadersMap.values()));
1291             }
1292         }
1293 
1294         actions.addAll(eventReportingJobActions);
1295         return actions;
1296     }
1297 
createTriggerBasedActions(JSONObject input)1298     private static List<Action> createTriggerBasedActions(JSONObject input) throws JSONException {
1299         List<Action> actions = new ArrayList<>();
1300         List<Action> aggregateReportingJobActions = new ArrayList<>();
1301 
1302         // Interop tests have all registration types in one list
1303         if (!input.isNull(TestFormatJsonMapping.REGISTRATIONS_KEY)) {
1304             JSONArray registrationArray = input.getJSONArray(
1305                     TestFormatJsonMapping.REGISTRATIONS_KEY);
1306             for (int i = 0; i < registrationArray.length(); i++) {
1307                 if (registrationArray.isNull(i)) {
1308                     continue;
1309                 }
1310                 JSONObject obj = registrationArray.getJSONObject(i);
1311                 if (!isSourceRegistration(obj)) {
1312                     RegisterTrigger triggerRegistration = new RegisterTrigger(obj);
1313                     actions.add(triggerRegistration);
1314                     aggregateReportingJobActions.add(new AggregateReportingJob(
1315                             triggerRegistration.mTimestamp + AGGREGATE_REPORT_DELAY));
1316                 }
1317             }
1318         }
1319 
1320         if (!input.isNull(TestFormatJsonMapping.TRIGGERS_KEY)) {
1321             JSONArray triggerRegistrationArray =
1322                     input.getJSONArray(TestFormatJsonMapping.TRIGGERS_KEY);
1323             for (int j = 0; j < triggerRegistrationArray.length(); j++) {
1324                 RegisterTrigger triggerRegistration =
1325                         new RegisterTrigger(triggerRegistrationArray.getJSONObject(j));
1326                 actions.add(triggerRegistration);
1327                 aggregateReportingJobActions.add(new AggregateReportingJob(
1328                         triggerRegistration.mTimestamp + AGGREGATE_REPORT_DELAY));
1329             }
1330         }
1331 
1332         if (!input.isNull(TestFormatJsonMapping.WEB_TRIGGERS_KEY)) {
1333             JSONArray webTriggerRegistrationArray =
1334                     input.getJSONArray(TestFormatJsonMapping.WEB_TRIGGERS_KEY);
1335             for (int j = 0; j < webTriggerRegistrationArray.length(); j++) {
1336                 RegisterWebTrigger webTrigger =
1337                         new RegisterWebTrigger(webTriggerRegistrationArray.getJSONObject(j));
1338                 actions.add(webTrigger);
1339                 aggregateReportingJobActions.add(new AggregateReportingJob(
1340                         webTrigger.mTimestamp + AGGREGATE_REPORT_DELAY));
1341             }
1342         }
1343 
1344         actions.addAll(aggregateReportingJobActions);
1345         return actions;
1346     }
1347 
createInstallActions(JSONObject input)1348     private static List<Action> createInstallActions(JSONObject input) throws JSONException {
1349         List<Action> actions = new ArrayList<>();
1350         if (!input.isNull(TestFormatJsonMapping.INSTALLS_KEY)) {
1351             JSONArray installsArray = input.getJSONArray(TestFormatJsonMapping.INSTALLS_KEY);
1352             for (int j = 0; j < installsArray.length(); j++) {
1353                 InstallApp installApp = new InstallApp(installsArray.getJSONObject(j));
1354                 actions.add(installApp);
1355             }
1356         }
1357 
1358         return actions;
1359     }
1360 
createUninstallActions(JSONObject input)1361     private static List<Action> createUninstallActions(JSONObject input) throws JSONException {
1362         List<Action> actions = new ArrayList<>();
1363         if (!input.isNull(TestFormatJsonMapping.UNINSTALLS_KEY)) {
1364             JSONArray uninstallsArray = input.getJSONArray(TestFormatJsonMapping.UNINSTALLS_KEY);
1365             for (int j = 0; j < uninstallsArray.length(); j++) {
1366                 UninstallApp uninstallApp = new UninstallApp(uninstallsArray.getJSONObject(j));
1367                 actions.add(uninstallApp);
1368             }
1369         }
1370 
1371         return actions;
1372     }
1373 
getExpectedOutput(JSONObject output)1374     private static ReportObjects getExpectedOutput(JSONObject output) throws JSONException {
1375         List<JSONObject> eventReportObjects = new ArrayList<>();
1376         List<JSONObject> aggregateReportObjects = new ArrayList<>();
1377         List<JSONObject> debugEventReportObjects = new ArrayList<>();
1378         List<JSONObject> debugAggregateReportObjects = new ArrayList<>();
1379         List<JSONObject> debugReportObjects = new ArrayList<>();
1380 
1381         // Interop tests have all report types in one list
1382         if (!output.isNull(TestFormatJsonMapping.REPORTS_OBJECTS_KEY)) {
1383             // We compare the suffixes of the different reporting URLs with the report URL to
1384             // determine the report type. This assumes the longest suffix match corresponds with the
1385             // report type.
1386             String[] eventUrlTokens = EVENT_ATTRIBUTION_REPORT_URI_PATH.split("/");
1387             String[] debugEventUrlTokens = DEBUG_EVENT_ATTRIBUTION_REPORT_URI_PATH.split("/");
1388             String[] aggregateUrlTokens = AGGREGATE_ATTRIBUTION_REPORT_URI_PATH.split("/");
1389             String[] debugAggregateUrlTokens =
1390                 DEBUG_AGGREGATE_ATTRIBUTION_REPORT_URI_PATH.split("/");
1391             String[] debugUrlTokens = DEBUG_REPORT_URI_PATH.split("/");
1392 
1393             JSONArray reportsObjectsArray = output.getJSONArray(
1394                     TestFormatJsonMapping.REPORTS_OBJECTS_KEY);
1395 
1396             for (int i = 0; i < reportsObjectsArray.length(); i++) {
1397                 JSONObject obj = reportsObjectsArray.getJSONObject(i);
1398                 String[] urlTokens = obj.getString(TestFormatJsonMapping.REPORT_TO_KEY).split("/");
1399                 // Collect reports to different lists (event, aggregate, debug, etc.) based on the
1400                 // reporting URL.
1401                 if (urlTokens[urlTokens.length - 1].equals(
1402                         debugUrlTokens[debugUrlTokens.length - 1])) {
1403                     debugReportObjects.add(obj);
1404                 } else if (urlTokens[urlTokens.length - 1].equals(
1405                         eventUrlTokens[eventUrlTokens.length - 1])) {
1406                     if (urlTokens[urlTokens.length - 2].equals(
1407                             debugEventUrlTokens[debugEventUrlTokens.length - 2])) {
1408                         debugEventReportObjects.add(obj);
1409                     } else {
1410                         eventReportObjects.add(obj);
1411                     }
1412                 } else if (urlTokens[urlTokens.length - 1].equals(
1413                         aggregateUrlTokens[aggregateUrlTokens.length - 1])) {
1414                     if (urlTokens[urlTokens.length - 2].equals(
1415                             debugAggregateUrlTokens[debugAggregateUrlTokens.length - 2])) {
1416                         debugAggregateReportObjects.add(obj);
1417                     } else {
1418                         aggregateReportObjects.add(obj);
1419                     }
1420                 }
1421             }
1422         }
1423 
1424         if (!output.isNull(TestFormatJsonMapping.EVENT_REPORT_OBJECTS_KEY)) {
1425             JSONArray eventReportObjectsArray = output.getJSONArray(
1426                     TestFormatJsonMapping.EVENT_REPORT_OBJECTS_KEY);
1427             for (int i = 0; i < eventReportObjectsArray.length(); i++) {
1428                 eventReportObjects.add(eventReportObjectsArray.getJSONObject(i));
1429             }
1430         }
1431 
1432         if (!output.isNull(TestFormatJsonMapping.AGGREGATE_REPORT_OBJECTS_KEY)) {
1433             JSONArray aggregateReportObjectsArray =
1434                     output.getJSONArray(TestFormatJsonMapping.AGGREGATE_REPORT_OBJECTS_KEY);
1435             for (int i = 0; i < aggregateReportObjectsArray.length(); i++) {
1436                 aggregateReportObjects.add(aggregateReportObjectsArray.getJSONObject(i));
1437             }
1438         }
1439 
1440         if (!output.isNull(TestFormatJsonMapping.DEBUG_EVENT_REPORT_OBJECTS_KEY)) {
1441             JSONArray debugEventReportObjectsArray =
1442                     output.getJSONArray(TestFormatJsonMapping.DEBUG_EVENT_REPORT_OBJECTS_KEY);
1443             for (int i = 0; i < debugEventReportObjectsArray.length(); i++) {
1444                 debugEventReportObjects.add(debugEventReportObjectsArray.getJSONObject(i));
1445             }
1446         }
1447 
1448         if (!output.isNull(TestFormatJsonMapping.DEBUG_AGGREGATE_REPORT_OBJECTS_KEY)) {
1449             JSONArray debugAggregateReportObjectsArray =
1450                     output.getJSONArray(TestFormatJsonMapping.DEBUG_AGGREGATE_REPORT_OBJECTS_KEY);
1451             for (int i = 0; i < debugAggregateReportObjectsArray.length(); i++) {
1452                 debugAggregateReportObjects.add(debugAggregateReportObjectsArray.getJSONObject(i));
1453             }
1454         }
1455 
1456         if (!output.isNull(TestFormatJsonMapping.VERBOSE_DEBUG_OBJECTS_KEY)) {
1457             JSONArray debugReportObjectsArray =
1458                     output.getJSONArray(TestFormatJsonMapping.VERBOSE_DEBUG_OBJECTS_KEY);
1459             for (int i = 0; i < debugReportObjectsArray.length(); i++) {
1460                 debugReportObjects.add(debugReportObjectsArray.getJSONObject(i));
1461             }
1462         }
1463 
1464         return new ReportObjects(
1465                 eventReportObjects,
1466                 aggregateReportObjects,
1467                 debugEventReportObjects,
1468                 debugAggregateReportObjects,
1469                 debugReportObjects);
1470     }
1471 
1472     /**
1473      * Empties measurement database tables, used for test cleanup.
1474      */
emptyTables(SQLiteDatabase db)1475     private static void emptyTables(SQLiteDatabase db) {
1476         db.delete("msmt_source", null, null);
1477         db.delete("msmt_trigger", null, null);
1478         db.delete("msmt_event_report", null, null);
1479         db.delete("msmt_attribution", null, null);
1480         db.delete("msmt_aggregate_report", null, null);
1481         db.delete("msmt_async_registration_contract", null, null);
1482     }
1483 
processAction(RegisterSource sourceRegistration)1484     abstract void processAction(RegisterSource sourceRegistration)
1485             throws IOException, JSONException;
1486 
processAction(RegisterWebSource sourceRegistration)1487     abstract void processAction(RegisterWebSource sourceRegistration)
1488             throws IOException, JSONException;
1489 
processAction(RegisterListSources sourceRegistration)1490     abstract void processAction(RegisterListSources sourceRegistration)
1491             throws IOException, JSONException;
1492 
processAction(RegisterTrigger triggerRegistration)1493     abstract void processAction(RegisterTrigger triggerRegistration)
1494             throws IOException, JSONException;
1495 
processAction(RegisterWebTrigger triggerRegistration)1496     abstract void processAction(RegisterWebTrigger triggerRegistration)
1497             throws IOException, JSONException;
1498 
processAction(InstallApp installApp)1499     abstract void processAction(InstallApp installApp);
1500 
processAction(UninstallApp uninstallApp)1501     abstract void processAction(UninstallApp uninstallApp);
1502 
evaluateResults()1503     void evaluateResults() throws JSONException {
1504         sortEventReportObjects(OutputType.EXPECTED, mExpectedOutput.mEventReportObjects);
1505         sortEventReportObjects(OutputType.ACTUAL, mActualOutput.mEventReportObjects);
1506         sortAggregateReportObjects(OutputType.EXPECTED, mExpectedOutput.mAggregateReportObjects);
1507         sortAggregateReportObjects(OutputType.ACTUAL, mActualOutput.mAggregateReportObjects);
1508         sortEventReportObjects(OutputType.EXPECTED, mExpectedOutput.mDebugEventReportObjects);
1509         sortEventReportObjects(OutputType.ACTUAL, mActualOutput.mDebugEventReportObjects);
1510         sortAggregateReportObjects(
1511                 OutputType.EXPECTED, mExpectedOutput.mDebugAggregateReportObjects);
1512         sortAggregateReportObjects(OutputType.ACTUAL, mActualOutput.mDebugAggregateReportObjects);
1513         Assert.assertTrue(getTestFailureMessage(mExpectedOutput, mActualOutput),
1514                 areEqual(mExpectedOutput, mActualOutput));
1515     }
1516 
setupDeviceConfigForPhFlags()1517     private void setupDeviceConfigForPhFlags() {
1518         mPhFlagsMap
1519                 .keySet()
1520                 .forEach(
1521                         key -> {
1522                                 log(String.format(
1523                                         "Setting PhFlag %s to %s", key, mPhFlagsMap.get(key)));
1524                                 DeviceConfig.setProperty(
1525                                         DeviceConfig.NAMESPACE_ADSERVICES,
1526                                         key,
1527                                         mPhFlagsMap.get(key),
1528                                         false);
1529                         });
1530     }
1531 }
1532