1 /*
2  * Copyright (C) 2023 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.attribution;
18 
19 import static com.android.adservices.service.Flags.MEASUREMENT_MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS;
20 import static com.android.adservices.service.Flags.MEASUREMENT_MIN_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS;
21 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
22 
23 import static org.junit.Assert.assertEquals;
24 import static org.mockito.Mockito.when;
25 
26 import android.util.Pair;
27 
28 import com.android.adservices.LogUtil;
29 import com.android.adservices.service.Flags;
30 import com.android.adservices.service.measurement.AttributionConfig;
31 import com.android.adservices.service.measurement.FilterMap;
32 import com.android.adservices.service.measurement.Source;
33 import com.android.adservices.service.measurement.SourceFixture;
34 import com.android.adservices.service.measurement.Trigger;
35 import com.android.adservices.service.measurement.TriggerFixture;
36 import com.android.adservices.service.measurement.util.Filter;
37 import com.android.adservices.service.measurement.util.UnsignedLong;
38 
39 import org.json.JSONArray;
40 import org.json.JSONException;
41 import org.json.JSONObject;
42 import org.junit.Before;
43 import org.junit.Test;
44 import org.junit.runner.RunWith;
45 import org.mockito.Mock;
46 import org.mockito.junit.MockitoJUnitRunner;
47 
48 import java.util.Arrays;
49 import java.util.Collections;
50 import java.util.Comparator;
51 import java.util.List;
52 import java.util.UUID;
53 import java.util.concurrent.TimeUnit;
54 
55 @RunWith(MockitoJUnitRunner.class)
56 public class XnaSourceCreatorTest {
57     private static final UnsignedLong SHARED_DEBUG_KEY_1 = new UnsignedLong(1786463L);
58     private static final String AD_ID = "abc-def-ghi";
59     private static final String JOIN_KEY = "join-abc-def-ghi";
60     private static final long LOOKBACK_WINDOW_VALUE = 10L;
61 
62     @Mock private Flags mFlags;
63     private Filter mFilter;
64 
65     @Before
setup()66     public void setup() {
67         doReturn(true).when(mFlags).getMeasurementEnableSharedSourceDebugKey();
68         doReturn(true).when(mFlags).getMeasurementEnableSharedFilterDataKeysXNA();
69         when(mFlags.getMeasurementMinReportingRegisterSourceExpirationInSeconds())
70                 .thenReturn(MEASUREMENT_MIN_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS);
71         when(mFlags.getMeasurementMaxReportingRegisterSourceExpirationInSeconds())
72                 .thenReturn(MEASUREMENT_MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS);
73         mFilter = new Filter(mFlags);
74     }
75 
76     @Test
generateDerivedSources_withVarietyOfSources_filtersAndGeneratesSources()77     public void generateDerivedSources_withVarietyOfSources_filtersAndGeneratesSources()
78             throws JSONException {
79         // Setup
80         String enrollment1 = "enrollment1";
81         JSONArray filters =
82                 new JSONArray(
83                         Collections.singletonList(new JSONObject(buildMatchingFilterData(false))));
84         AttributionConfig attributionConfig1 =
85                 new AttributionConfig.Builder()
86                         .setSourceAdtech(enrollment1)
87                         .setSourceFilters(mFilter.deserializeFilterSet(filters))
88                         .setSourceNotFilters(null)
89                         .setFilterData(
90                                 new FilterMap.Builder()
91                                         .buildFilterData(
92                                                 new JSONObject(buildMatchingFilterData(false)))
93                                         .build())
94                         .setExpiry(50L)
95                         .setPriority(50L)
96                         .setPostInstallExclusivityWindow(5L)
97                         .setSourcePriorityRange(new Pair<>(1L, 100L))
98                         .build();
99         String enrollment2 = "enrollment2";
100         AttributionConfig attributionConfig2 =
101                 new AttributionConfig.Builder()
102                         .setSourceAdtech(enrollment2)
103                         .setSourceFilters(null)
104                         .setSourceNotFilters(
105                                 mFilter.deserializeFilterSet(
106                                         new JSONArray(
107                                                 Collections.singletonList(
108                                                         new JSONObject(
109                                                                 buildNonMatchingFilterData())))))
110                         .setSourcePriorityRange(new Pair<>(101L, 200L))
111                         .setSourceExpiryOverride(TimeUnit.DAYS.toSeconds(10L))
112                         .build();
113 
114         AttributionConfig attributionConfig1_copy =
115                 new AttributionConfig.Builder()
116                         .setSourceAdtech(enrollment1)
117                         .setSourceFilters(mFilter.deserializeFilterSet(filters))
118                         .setSourceNotFilters(null)
119                         .setFilterData(
120                                 new FilterMap.Builder()
121                                         .buildFilterData(
122                                                 new JSONObject(buildMatchingFilterData(false)))
123                                         .build())
124                         .setExpiry(60L)
125                         .setPriority(70L)
126                         .setPostInstallExclusivityWindow(50L)
127                         .setSourcePriorityRange(new Pair<>(1L, 100L))
128                         .build();
129 
130         String attributionConfigsArray =
131                 new JSONArray(
132                                 Arrays.asList(
133                                         attributionConfig1.serializeAsJson(mFlags),
134                                         attributionConfig2.serializeAsJson(mFlags),
135                                         // This ensures that already consumed sources aren't reused
136                                         attributionConfig1_copy.serializeAsJson(mFlags)))
137                         .toString();
138         Trigger trigger =
139                 TriggerFixture.getValidTriggerBuilder()
140                         .setAttributionConfig(attributionConfigsArray)
141                         .build();
142 
143         // Aggregate source
144         JSONObject aggregatableSource = new JSONObject();
145         aggregatableSource.put("key1", "0x159");
146         aggregatableSource.put("key2", "0x1");
147         aggregatableSource.put("key3", "0x2");
148 
149         // enrollment1 sources
150         Source source1Matches =
151                 createValidSourceBuilder()
152                         .setId(UUID.randomUUID().toString())
153                         .setEnrollmentId(enrollment1)
154                         .setPriority(1L)
155                         .setAggregateSource(aggregatableSource.toString())
156                         .setSharedAggregationKeys(
157                                 new JSONArray(Arrays.asList("key2", "key3")).toString())
158                         .setSharedDebugKey(SHARED_DEBUG_KEY_1)
159                         .setDebugAdId(AD_ID)
160                         .setDebugJoinKey(JOIN_KEY)
161                         .build();
162 
163         JSONObject derivedAggregatableSource1 = new JSONObject();
164         derivedAggregatableSource1.put("key2", "0x1");
165         derivedAggregatableSource1.put("key3", "0x2");
166         Source expectedDerivedSource1 =
167                 createValidSourceBuilder()
168                         .setId(UUID.randomUUID().toString())
169                         .setEnrollmentId(enrollment1)
170                         .setPriority(attributionConfig1.getPriority())
171                         .setFilterDataString(
172                                 attributionConfig1
173                                         .getFilterData()
174                                         .serializeAsJson(mFlags)
175                                         .toString())
176                         .setExpiryTime(source1Matches.getExpiryTime())
177                         .setInstallCooldownWindow(
178                                 attributionConfig1.getPostInstallExclusivityWindow())
179                         .setParentId(source1Matches.getId())
180                         .setSharedAggregationKeys(
181                                 new JSONArray(Arrays.asList("key2", "key3")).toString())
182                         .setAggregateSource(derivedAggregatableSource1.toString())
183                         // shared_debug_key is shared as debug_key on the derived source
184                         .setDebugKey(SHARED_DEBUG_KEY_1)
185                         // adId isn't shared
186                         .setDebugAdId(null)
187                         // join key isn't shared
188                         .setDebugJoinKey(null)
189                         .build();
190         Source source2Matches =
191                 createValidSourceBuilder()
192                         .setId(UUID.randomUUID().toString())
193                         .setEnrollmentId(enrollment1)
194                         .setPriority(100L)
195                         .setFilterDataString(buildMatchingFilterData(false))
196                         .setAggregateSource(aggregatableSource.toString())
197                         .setSharedAggregationKeys(
198                                 new JSONArray(Collections.singletonList("key1")).toString())
199                         .build();
200 
201         JSONObject derivedAggregatableSource2 = new JSONObject();
202         derivedAggregatableSource2.put("key1", "0x159");
203         Source expectedDerivedSource2;
204         expectedDerivedSource2 =
205                 createValidSourceBuilder()
206                         .setId(UUID.randomUUID().toString())
207                         .setEnrollmentId(enrollment1)
208                         .setPriority(attributionConfig1.getPriority())
209                         .setFilterDataString(
210                                 attributionConfig1
211                                         .getFilterData()
212                                         .serializeAsJson(mFlags)
213                                         .toString())
214                         .setExpiryTime(source2Matches.getExpiryTime())
215                         .setInstallCooldownWindow(
216                                 attributionConfig1.getPostInstallExclusivityWindow())
217                         .setParentId(source2Matches.getId())
218                         .setAggregateSource(derivedAggregatableSource2.toString())
219                         .setSharedAggregationKeys(
220                                 new JSONArray(Collections.singletonList("key1")).toString())
221                         .build();
222         Source source3OutOfPriorityRange =
223                 createValidSourceBuilder()
224                         .setId(UUID.randomUUID().toString())
225                         .setEnrollmentId(enrollment1)
226                         .setPriority(101L)
227                         .setFilterDataString(buildMatchingFilterData(false))
228                         .build();
229         Source source4NonMatchingFilter =
230                 createValidSourceBuilder()
231                         .setId(UUID.randomUUID().toString())
232                         .setEnrollmentId(enrollment1)
233                         .setPriority(50L)
234                         .setFilterDataString(buildNonMatchingFilterData())
235                         .build();
236 
237         // enrollment2 sources
238         Source source5Matches =
239                 createValidSourceBuilder()
240                         .setId(UUID.randomUUID().toString())
241                         .setEnrollmentId(enrollment2)
242                         .setPriority(120L)
243                         .setInstallCooldownWindow(0L)
244                         .setSharedDebugKey(SHARED_DEBUG_KEY_1)
245                         .build();
246         Source expectedDerivedSource5 =
247                 createValidSourceBuilder()
248                         .setId(UUID.randomUUID().toString())
249                         .setEnrollmentId(enrollment2)
250                         .setPriority(120L)
251                         .setFilterDataString(source5Matches.getFilterDataString())
252                         .setExpiryTime(source5Matches.getExpiryTime())
253                         .setInstallCooldownWindow(0L)
254                         .setParentId(source5Matches.getId())
255                         .setAggregateSource(new JSONObject().toString())
256                         .setDebugKey(SHARED_DEBUG_KEY_1)
257                         .build();
258         Source source6NoFiltersIssue =
259                 createValidSourceBuilder()
260                         .setId(UUID.randomUUID().toString())
261                         .setEnrollmentId(enrollment2)
262                         .setPriority(130L)
263                         .setFilterDataString(buildNonMatchingFilterData())
264                         .build();
265         Source source7ExpiresBeforeTriggerTime =
266                 createValidSourceBuilder()
267                         .setId(UUID.randomUUID().toString())
268                         .setEnrollmentId(enrollment2)
269                         .setPriority(120L)
270                         .setEventTime(
271                                 trigger.getTriggerTime()
272                                         - TimeUnit.DAYS.toMillis(10L)
273                                         - 50L /* random value so that eventTime+override is before
274                                               trigger time*/)
275                         .build();
276 
277         // enrollmentX source - is not considered since to attribution config is present for this
278         Source source8NoAttributionConfig =
279                 createValidSourceBuilder()
280                         .setId(UUID.randomUUID().toString())
281                         .setEnrollmentId("enrollmentX")
282                         .setPriority(120L)
283                         .setInstallCooldownWindow(0L)
284                         .build();
285 
286         List<Source> expectedDerivedSources =
287                 Arrays.asList(
288                         expectedDerivedSource1, expectedDerivedSource2, expectedDerivedSource5);
289         Comparator<Source> sorter = Comparator.comparing(Source::getParentId);
290         expectedDerivedSources.sort(sorter);
291 
292         // Execution
293         XnaSourceCreator xnaSourceCreator = new XnaSourceCreator(mFlags);
294         List<Source> parentSources =
295                 Arrays.asList(
296                         source1Matches,
297                         source2Matches,
298                         source3OutOfPriorityRange,
299                         source4NonMatchingFilter,
300                         source5Matches,
301                         source6NoFiltersIssue,
302                         source7ExpiresBeforeTriggerTime,
303                         source8NoAttributionConfig);
304         List<Source> actualDerivedSources =
305                 xnaSourceCreator.generateDerivedSources(trigger, parentSources);
306         actualDerivedSources.sort(sorter);
307 
308         // Assertion
309         assertEquals(expectedDerivedSources, actualDerivedSources);
310     }
311 
312     @Test
generateDerivedSources_sharedDebugKeyDisabled_doesntAddDebugKeyToDerivedSource()313     public void generateDerivedSources_sharedDebugKeyDisabled_doesntAddDebugKeyToDerivedSource()
314             throws JSONException {
315         // Setup
316         doReturn(false).when(mFlags).getMeasurementEnableSharedSourceDebugKey();
317         String enrollment1 = "enrollment1";
318         JSONArray filters =
319                 new JSONArray(
320                         Collections.singletonList(new JSONObject(buildMatchingFilterData(false))));
321         AttributionConfig attributionConfig1 =
322                 new AttributionConfig.Builder()
323                         .setSourceAdtech(enrollment1)
324                         .setSourceFilters(mFilter.deserializeFilterSet(filters))
325                         .setSourceNotFilters(null)
326                         .setFilterData(
327                                 new FilterMap.Builder()
328                                         .buildFilterData(
329                                                 new JSONObject(buildMatchingFilterData(false)))
330                                         .build())
331                         .setExpiry(50L)
332                         .setPriority(50L)
333                         .setPostInstallExclusivityWindow(5L)
334                         .setSourcePriorityRange(new Pair<>(1L, 100L))
335                         .build();
336         String attributionConfigsArray =
337                 new JSONArray(Collections.singletonList(attributionConfig1.serializeAsJson(mFlags)))
338                         .toString();
339         Trigger trigger =
340                 TriggerFixture.getValidTriggerBuilder()
341                         .setAttributionConfig(attributionConfigsArray)
342                         .build();
343 
344         // Aggregate source
345         JSONObject aggregatableSource = new JSONObject();
346         aggregatableSource.put("key1", "0x159");
347         aggregatableSource.put("key2", "0x1");
348         aggregatableSource.put("key3", "0x2");
349 
350         // enrollment1 sources
351         Source source1Matches =
352                 createValidSourceBuilder()
353                         .setId(UUID.randomUUID().toString())
354                         .setEnrollmentId(enrollment1)
355                         .setPriority(1L)
356                         .setAggregateSource(aggregatableSource.toString())
357                         .setSharedAggregationKeys(
358                                 new JSONArray(Arrays.asList("key2", "key3")).toString())
359                         .setSharedDebugKey(SHARED_DEBUG_KEY_1)
360                         .setDebugAdId(AD_ID)
361                         .setDebugJoinKey(JOIN_KEY)
362                         .build();
363 
364         JSONObject derivedAggregatableSource1 = new JSONObject();
365         derivedAggregatableSource1.put("key2", "0x1");
366         derivedAggregatableSource1.put("key3", "0x2");
367         Source expectedDerivedSource1 =
368                 createValidSourceBuilder()
369                         .setId(UUID.randomUUID().toString())
370                         .setEnrollmentId(enrollment1)
371                         .setPriority(attributionConfig1.getPriority())
372                         .setFilterDataString(
373                                 attributionConfig1
374                                         .getFilterData()
375                                         .serializeAsJson(mFlags)
376                                         .toString())
377                         .setExpiryTime(source1Matches.getExpiryTime())
378                         .setInstallCooldownWindow(
379                                 attributionConfig1.getPostInstallExclusivityWindow())
380                         .setParentId(source1Matches.getId())
381                         .setSharedAggregationKeys(
382                                 new JSONArray(Arrays.asList("key2", "key3")).toString())
383                         .setAggregateSource(derivedAggregatableSource1.toString())
384                         // shared_debug_key is not shared when the feature is disabled
385                         .setDebugKey(null)
386                         // adId isn't shared
387                         .setDebugAdId(null)
388                         // join key isn't shared
389                         .setDebugJoinKey(null)
390                         .build();
391 
392         List<Source> expectedDerivedSources = Collections.singletonList(expectedDerivedSource1);
393 
394         // Execution
395         XnaSourceCreator xnaSourceCreator = new XnaSourceCreator(mFlags);
396         List<Source> parentSources = Collections.singletonList(source1Matches);
397         List<Source> actualDerivedSources =
398                 xnaSourceCreator.generateDerivedSources(trigger, parentSources);
399 
400         // Assertion
401         assertEquals(expectedDerivedSources, actualDerivedSources);
402     }
403 
404     @Test
generateDerivedSources_withSharedFilterDataKeys_filtersAndGeneratesSources()405     public void generateDerivedSources_withSharedFilterDataKeys_filtersAndGeneratesSources()
406             throws JSONException {
407         // Setup
408         doReturn(false).when(mFlags).getMeasurementEnableSharedSourceDebugKey();
409         String enrollment1 = "enrollment1";
410 
411         JSONArray filters =
412                 new JSONArray(
413                         Collections.singletonList(new JSONObject(buildMatchingSharedFilterData())));
414         AttributionConfig attributionConfig1 =
415                 new AttributionConfig.Builder()
416                         .setSourceAdtech(enrollment1)
417                         .setSourceFilters(mFilter.deserializeFilterSet(filters))
418                         .setSourceNotFilters(null)
419                         .setFilterData(
420                                 new FilterMap.Builder()
421                                         .buildFilterData(
422                                                 new JSONObject(buildMatchingSharedFilterData()))
423                                         .build())
424                         .setExpiry(50L)
425                         .setPriority(50L)
426                         .setPostInstallExclusivityWindow(5L)
427                         .setSourcePriorityRange(new Pair<>(1L, 100L))
428                         .build();
429         String attributionConfigsArray =
430                 new JSONArray(Collections.singletonList(attributionConfig1.serializeAsJson(mFlags)))
431                         .toString();
432         Trigger trigger =
433                 TriggerFixture.getValidTriggerBuilder()
434                         .setAttributionConfig(attributionConfigsArray)
435                         .build();
436 
437         // Aggregate source
438         JSONObject aggregatableSource = new JSONObject();
439         aggregatableSource.put("key1", "0x159");
440         aggregatableSource.put("key2", "0x1");
441         aggregatableSource.put("key3", "0x2");
442 
443         // enrollment1 sources
444         Source source1Matches =
445                 createValidSourceBuilder()
446                         .setId(UUID.randomUUID().toString())
447                         .setEnrollmentId(enrollment1)
448                         .setPriority(1L)
449                         .setAggregateSource(aggregatableSource.toString())
450                         .setSharedAggregationKeys(
451                                 new JSONArray(Arrays.asList("key2", "key3")).toString())
452                         .setFilterDataString(buildMatchingSharedFilterData())
453                         .setSharedFilterDataKeys(new JSONArray(Arrays.asList("product")).toString())
454                         .setSharedDebugKey(SHARED_DEBUG_KEY_1)
455                         .setDebugAdId(AD_ID)
456                         .setDebugJoinKey(JOIN_KEY)
457                         .build();
458 
459         JSONObject derivedAggregatableSource1 = new JSONObject();
460         derivedAggregatableSource1.put("key2", "0x1");
461         derivedAggregatableSource1.put("key3", "0x2");
462         Source expectedDerivedSource1 =
463                 createValidSourceBuilder()
464                         .setId(UUID.randomUUID().toString())
465                         .setEnrollmentId(enrollment1)
466                         .setPriority(attributionConfig1.getPriority())
467                         .setFilterDataString(
468                                 attributionConfig1
469                                         .getFilterData()
470                                         .serializeAsJson(mFlags)
471                                         .toString())
472                         .setExpiryTime(source1Matches.getExpiryTime())
473                         .setInstallCooldownWindow(
474                                 attributionConfig1.getPostInstallExclusivityWindow())
475                         .setParentId(source1Matches.getId())
476                         .setFilterDataString("{\"product\":[\"123\"]}")
477                         .setSharedFilterDataKeys(null)
478                         .setSharedAggregationKeys(
479                                 new JSONArray(Arrays.asList("key2", "key3")).toString())
480                         .setAggregateSource(derivedAggregatableSource1.toString())
481                         // shared_debug_key is not shared when the feature is disabled
482                         .setDebugKey(null)
483                         // adId isn't shared
484                         .setDebugAdId(null)
485                         // join key isn't shared
486                         .setDebugJoinKey(null)
487                         .build();
488 
489         List<Source> expectedDerivedSources = Collections.singletonList(expectedDerivedSource1);
490 
491         // Execution
492         XnaSourceCreator xnaSourceCreator = new XnaSourceCreator(mFlags);
493         List<Source> parentSources = Collections.singletonList(source1Matches);
494         List<Source> actualDerivedSources =
495                 xnaSourceCreator.generateDerivedSources(trigger, parentSources);
496 
497         // Assertion
498         assertEquals(expectedDerivedSources, actualDerivedSources);
499     }
500 
501     @Test
generateDerivedSources_lookbackWindow_filtersAndGeneratesSources()502     public void generateDerivedSources_lookbackWindow_filtersAndGeneratesSources()
503             throws JSONException {
504         // Setup
505         doReturn(true).when(mFlags).getMeasurementEnableLookbackWindowFilter();
506         String enrollment1 = "enrollment1";
507 
508         JSONArray filters =
509                 new JSONArray(
510                         Collections.singletonList(new JSONObject(buildMatchingFilterData(true))));
511         JSONArray sourceFilters =
512                 new JSONArray(
513                         Collections.singletonList(new JSONObject(buildMatchingFilterData(false))));
514         AttributionConfig attributionConfig1 =
515                 new AttributionConfig.Builder()
516                         .setSourceAdtech(enrollment1)
517                         .setSourceFilters(mFilter.deserializeFilterSet(filters))
518                         .setSourceNotFilters(null)
519                         .setFilterData(
520                                 new FilterMap.Builder()
521                                         .buildFilterDataV2(
522                                                 new JSONObject(buildMatchingFilterData(false)))
523                                         .build())
524                         .setExpiry(50L)
525                         .setPriority(50L)
526                         .setPostInstallExclusivityWindow(5L)
527                         .setSourcePriorityRange(new Pair<>(1L, 100L))
528                         .build();
529         String attributionConfigsArray =
530                 new JSONArray(Collections.singletonList(attributionConfig1.serializeAsJson(mFlags)))
531                         .toString();
532         Trigger trigger =
533                 TriggerFixture.getValidTriggerBuilder()
534                         .setAttributionConfig(attributionConfigsArray)
535                         .setTriggerTime(20000L)
536                         .build();
537 
538         // Aggregate source
539         JSONObject aggregatableSource = new JSONObject();
540         aggregatableSource.put("key1", "0x159");
541         aggregatableSource.put("key2", "0x1");
542         aggregatableSource.put("key3", "0x2");
543 
544         // enrollment1 sources
545         Source sourceWithinLookbackWindow =
546                 createValidSourceBuilder()
547                         .setId(UUID.randomUUID().toString())
548                         .setEventTime(
549                                 trigger.getTriggerTime()
550                                         - TimeUnit.SECONDS.toMillis(LOOKBACK_WINDOW_VALUE - 1))
551                         .setEnrollmentId(enrollment1)
552                         .setPriority(1L)
553                         .setSharedAggregationKeys(
554                                 new JSONArray(Arrays.asList("key2", "key3")).toString())
555                         .setAggregateSource(aggregatableSource.toString())
556                         .setFilterDataString(buildMatchingFilterData(false))
557                         .setSharedDebugKey(SHARED_DEBUG_KEY_1)
558                         .setDebugAdId(AD_ID)
559                         .setDebugJoinKey(JOIN_KEY)
560                         .build();
561 
562         Source sourceOutsideLookbackWindow =
563                 createValidSourceBuilder()
564                         .setId(UUID.randomUUID().toString())
565                         .setEventTime(
566                                 trigger.getTriggerTime()
567                                         - TimeUnit.SECONDS.toMillis(LOOKBACK_WINDOW_VALUE + 1))
568                         .setEnrollmentId(enrollment1)
569                         .setPriority(1L)
570                         .setSharedAggregationKeys(
571                                 new JSONArray(Arrays.asList("key2", "key3")).toString())
572                         .setAggregateSource(aggregatableSource.toString())
573                         .setFilterDataString(buildMatchingFilterData(false))
574                         .setSharedDebugKey(SHARED_DEBUG_KEY_1)
575                         .setDebugAdId(AD_ID)
576                         .setDebugJoinKey(JOIN_KEY)
577                         .build();
578 
579         JSONObject derivedAggregatableSource1 = new JSONObject();
580         derivedAggregatableSource1.put("key2", "0x1");
581         derivedAggregatableSource1.put("key3", "0x2");
582         Source expectedDerivedSource1 =
583                 createValidSourceBuilder()
584                         .setId(UUID.randomUUID().toString())
585                         .setEnrollmentId(enrollment1)
586                         .setPriority(attributionConfig1.getPriority())
587                         .setEventTime(sourceWithinLookbackWindow.getEventTime())
588                         .setFilterDataString(buildMatchingFilterData(false))
589                         .setExpiryTime(86411000L)
590                         .setInstallCooldownWindow(
591                                 attributionConfig1.getPostInstallExclusivityWindow())
592                         .setParentId(sourceWithinLookbackWindow.getId())
593                         .setSharedFilterDataKeys(null)
594                         .setSharedAggregationKeys(
595                                 new JSONArray(Arrays.asList("key2", "key3")).toString())
596                         .setInstallAttributed(true)
597                         .setAggregateSource(derivedAggregatableSource1.toString())
598                         // shared_debug_key is not shared when the feature is disabled
599                         .setDebugKey(SHARED_DEBUG_KEY_1)
600                         // adId isn't shared
601                         .setDebugAdId(null)
602                         // join key isn't shared
603                         .setDebugJoinKey(null)
604                         .build();
605 
606         List<Source> expectedDerivedSources = Collections.singletonList(expectedDerivedSource1);
607 
608         // Execution
609         XnaSourceCreator xnaSourceCreator = new XnaSourceCreator(mFlags);
610         List<Source> parentSources =
611                 Arrays.asList(sourceWithinLookbackWindow, sourceOutsideLookbackWindow);
612         List<Source> actualDerivedSources =
613                 xnaSourceCreator.generateDerivedSources(trigger, parentSources);
614 
615         // Assertion
616         assertEquals(expectedDerivedSources, actualDerivedSources);
617     }
618 
createValidSourceBuilder()619     private Source.Builder createValidSourceBuilder() {
620         return new Source.Builder()
621                 .setEventId(SourceFixture.ValidSourceParams.SOURCE_EVENT_ID)
622                 .setPublisher(SourceFixture.ValidSourceParams.PUBLISHER)
623                 .setAppDestinations(SourceFixture.ValidSourceParams.ATTRIBUTION_DESTINATIONS)
624                 .setWebDestinations(SourceFixture.ValidSourceParams.WEB_DESTINATIONS)
625                 .setEnrollmentId(SourceFixture.ValidSourceParams.ENROLLMENT_ID)
626                 .setRegistrant(SourceFixture.ValidSourceParams.REGISTRANT)
627                 .setEventTime(SourceFixture.ValidSourceParams.SOURCE_EVENT_TIME)
628                 .setExpiryTime(SourceFixture.ValidSourceParams.EXPIRY_TIME)
629                 .setPriority(SourceFixture.ValidSourceParams.PRIORITY)
630                 .setSourceType(SourceFixture.ValidSourceParams.SOURCE_TYPE)
631                 .setInstallAttributionWindow(
632                         SourceFixture.ValidSourceParams.INSTALL_ATTRIBUTION_WINDOW)
633                 .setInstallCooldownWindow(SourceFixture.ValidSourceParams.INSTALL_COOLDOWN_WINDOW)
634                 .setAttributionMode(SourceFixture.ValidSourceParams.ATTRIBUTION_MODE)
635                 .setAggregateSource(SourceFixture.ValidSourceParams.buildAggregateSource())
636                 .setFilterDataString(buildMatchingFilterData(false))
637                 .setIsDebugReporting(true)
638                 .setRegistrationId(SourceFixture.ValidSourceParams.REGISTRATION_ID)
639                 .setSharedAggregationKeys(SourceFixture.ValidSourceParams.SHARED_AGGREGATE_KEYS)
640                 .setInstallTime(SourceFixture.ValidSourceParams.INSTALL_TIME)
641                 .setRegistrationOrigin(SourceFixture.ValidSourceParams.REGISTRATION_ORIGIN);
642     }
643 
buildMatchingFilterData(boolean addLookbackWindowFilter)644     private String buildMatchingFilterData(boolean addLookbackWindowFilter) {
645         try {
646             JSONObject filterMap = new JSONObject();
647             filterMap.put(
648                     "conversion_subdomain",
649                     new JSONArray(Collections.singletonList("electronics.megastore")));
650             if (addLookbackWindowFilter) {
651                 filterMap.put(FilterMap.LOOKBACK_WINDOW, LOOKBACK_WINDOW_VALUE);
652             }
653             return filterMap.toString();
654         } catch (JSONException e) {
655             LogUtil.e("JSONException when building aggregate filter data.");
656         }
657         return null;
658     }
659 
buildNonMatchingFilterData()660     private String buildNonMatchingFilterData() {
661         try {
662             JSONObject filterMap = new JSONObject();
663             filterMap.put(
664                     "conversion_subdomain",
665                     new JSONArray(Collections.singletonList("non.matching")));
666             return filterMap.toString();
667         } catch (JSONException e) {
668             LogUtil.e("JSONException when building aggregate filter data.");
669         }
670         return null;
671     }
672 
buildMatchingSharedFilterData()673     private String buildMatchingSharedFilterData() {
674         try {
675             JSONObject filterMap = new JSONObject();
676             filterMap.put(
677                     "conversion_subdomain",
678                     new JSONArray(Collections.singletonList("electronics.megastore")));
679             filterMap.put("product", new JSONArray(Collections.singletonList("123")));
680             return filterMap.toString();
681         } catch (JSONException e) {
682             LogUtil.e("JSONException when building aggregate filter data.");
683         }
684         return null;
685     }
686 }
687