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