Implement custom audience validator and limit checker
Test: atest
Bug: 237679543
Bug: 221861025
Change-Id: I0c3212f32fd2fc285d7cfe98265b17187747f357
diff --git a/adservices/framework/java/android/adservices/customaudience/CustomAudience.java b/adservices/framework/java/android/adservices/customaudience/CustomAudience.java
index 77efdab..ebc0592 100644
--- a/adservices/framework/java/android/adservices/customaudience/CustomAudience.java
+++ b/adservices/framework/java/android/adservices/customaudience/CustomAudience.java
@@ -23,9 +23,6 @@
import android.os.Parcel;
import android.os.Parcelable;
-
-import com.android.internal.util.Preconditions;
-
import java.time.Instant;
import java.util.List;
import java.util.Objects;
@@ -472,23 +469,11 @@
Objects.requireNonNull(mDailyUpdateUrl);
Objects.requireNonNull(mBiddingLogicUrl);
- if (mExpirationTime != null) {
- Preconditions.checkArgument(mExpirationTime.isAfter(Instant.now()),
- "Expiration time must be in the future.");
- }
-
- if (mActivationTime != null && mExpirationTime != null) {
- Preconditions.checkArgument(mExpirationTime.isAfter(mActivationTime),
- "Expiration time must be before activation time.");
- }
-
// To pass the API lint, we should not allow null Collection.
if (mAds == null) {
mAds = List.of();
}
- // TODO(b/231997523): Add JSON field validation for user bidding signals.
-
return new CustomAudience(this);
}
}
diff --git a/adservices/service-core/java/com/android/adservices/data/customaudience/DBCustomAudience.java b/adservices/service-core/java/com/android/adservices/data/customaudience/DBCustomAudience.java
index a292c35..ff9d66e 100644
--- a/adservices/service-core/java/com/android/adservices/data/customaudience/DBCustomAudience.java
+++ b/adservices/service-core/java/com/android/adservices/data/customaudience/DBCustomAudience.java
@@ -54,10 +54,6 @@
@TypeConverters({DBCustomAudience.Converters.class})
public class DBCustomAudience {
public static final String TABLE_NAME = "custom_audience";
- // Default to 60-day expiry
- private static final Duration DEFAULT_EXPIRE_IN = Duration.ofDays(60);
- private static final Duration MAX_ACTIVATE_IN = Duration.ofDays(60);
- private static final Duration MAX_EXPIRE_IN = Duration.ofDays(60);
@ColumnInfo(name = "owner", index = true)
@NonNull
@@ -141,17 +137,22 @@
/**
* Parse parcelable {@link CustomAudience} to storage model {@link DBCustomAudience}.
*
- * @param parcelable the service model.
+ * @param parcelable the service model.
* @param callingAppName the app name where the call came from.
- * @param currentTime the timestamp when calling the method.
+ * @param currentTime the timestamp when calling the method.
+ * @param defaultExpireIn the default expiration from activation.
* @return storage model
*/
@NonNull
- public static DBCustomAudience fromServiceObject(@NonNull CustomAudience parcelable,
- @NonNull String callingAppName, @NonNull Instant currentTime) {
+ public static DBCustomAudience fromServiceObject(
+ @NonNull CustomAudience parcelable,
+ @NonNull String callingAppName,
+ @NonNull Instant currentTime,
+ @NonNull Duration defaultExpireIn) {
Objects.requireNonNull(parcelable);
Objects.requireNonNull(callingAppName);
Objects.requireNonNull(currentTime);
+ Objects.requireNonNull(defaultExpireIn);
String owner = Optional.ofNullable(parcelable.getOwner()).orElse(callingAppName);
@@ -159,15 +160,14 @@
// Make it easier at query for activated CAs.
Instant activationTime = Optional.ofNullable(parcelable.getActivationTime()).orElse(
currentTime);
+
if (activationTime.isBefore(currentTime)) {
activationTime = currentTime;
}
- Preconditions.checkArgument(!activationTime.isAfter(currentTime.plus(getMaxActivateIn())));
- Instant expirationTime = Optional.ofNullable(parcelable.getExpirationTime())
- .orElse(activationTime.plus(getDefaultExpireIn()));
- Preconditions.checkArgument(expirationTime.isAfter(activationTime));
- Preconditions.checkArgument(!expirationTime.isAfter(activationTime.plus(getMaxExpireIn())));
+ Instant expirationTime =
+ Optional.ofNullable(parcelable.getExpirationTime())
+ .orElse(activationTime.plus(defaultExpireIn));
Instant lastAdsAndBiddingDataUpdatedTime = parcelable.getAds().isEmpty()
|| parcelable.getTrustedBiddingData() == null
@@ -230,36 +230,6 @@
}
/**
- * See {@link #getExpirationTime()} for more information about expiration times.
- *
- * @return the default amount of time (in seconds) a {@link CustomAudience} object will live
- * before being expiring and being removed
- */
- public static Duration getDefaultExpireIn() {
- return DEFAULT_EXPIRE_IN;
- }
-
- /**
- * See {@link #getActivationTime()} for more information about activation times.
- *
- * @return the maximum permitted difference in seconds between the {@link CustomAudience}
- * object's creation time and its activation time
- */
- public static Duration getMaxActivateIn() {
- return MAX_ACTIVATE_IN;
- }
-
- /**
- * See {@link #getExpirationTime()} for more information about expiration times.
- *
- * @return the maximum permitted difference in seconds between the {@link CustomAudience}
- * object's creation time and its activation time
- */
- public static Duration getMaxExpireIn() {
- return MAX_EXPIRE_IN;
- }
-
- /**
* The App that adds the user to this CustomAudience.
* <p>Value must be <App UID>-<package name>.
*/
diff --git a/adservices/service-core/java/com/android/adservices/service/adselection/AdSelectionConfigValidator.java b/adservices/service-core/java/com/android/adservices/service/adselection/AdSelectionConfigValidator.java
index bcbabbe..b50dbe3 100644
--- a/adservices/service-core/java/com/android/adservices/service/adselection/AdSelectionConfigValidator.java
+++ b/adservices/service-core/java/com/android/adservices/service/adselection/AdSelectionConfigValidator.java
@@ -22,8 +22,10 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.adservices.service.common.Validator;
import com.android.internal.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.net.InternetDomainName;
@@ -55,17 +57,15 @@
+ " consistent.";
@Override
- public ImmutableList<String> getValidationViolations(
- @NonNull AdSelectionConfig adSelectionConfig) {
- ImmutableList.Builder<String> violations = new ImmutableList.Builder<>();
+ public void addValidation(
+ @NonNull AdSelectionConfig adSelectionConfig,
+ @NonNull ImmutableCollection.Builder<String> violations) {
if (Objects.isNull(adSelectionConfig)) {
violations.add("The adSelectionConfig should not be null.");
}
violations.addAll(
validateSellerAndSellerDecisionUrls(
adSelectionConfig.getSeller(), adSelectionConfig.getDecisionLogicUri()));
-
- return violations.build();
}
/**
diff --git a/adservices/service-core/java/com/android/adservices/service/common/AdDataValidator.java b/adservices/service-core/java/com/android/adservices/service/common/AdDataValidator.java
new file mode 100644
index 0000000..2ded206
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/common/AdDataValidator.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.adservices.service.common;
+
+import android.adservices.common.AdData;
+import android.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
+
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/** Validator for a ad data instance. */
+public class AdDataValidator implements Validator<AdData> {
+ @VisibleForTesting public static final String VIOLATION_FORMAT = "For %s, %s";
+
+ @VisibleForTesting public static final String AD_DATA_CLASS_NAME = AdData.class.getName();
+ @VisibleForTesting public static final String RENDER_URI_FIELD_NAME = "render uri";
+ @VisibleForTesting public static final String METADATA_FIELD_NAME = "metadata";
+
+ @NonNull private final AdTechUriValidator mUriValidator;
+ @NonNull private final JsonValidator mMetadataValidator;
+
+ public AdDataValidator(@NonNull String adTechRole, @NonNull String adTechIdentifier) {
+ Objects.requireNonNull(adTechRole);
+ Objects.requireNonNull(adTechIdentifier);
+
+ mUriValidator =
+ new AdTechUriValidator(
+ adTechRole, adTechIdentifier, AD_DATA_CLASS_NAME, RENDER_URI_FIELD_NAME);
+ mMetadataValidator = new JsonValidator(AD_DATA_CLASS_NAME, METADATA_FIELD_NAME);
+ }
+
+ /**
+ * Validate an ad data is valid.
+ *
+ * @param adData the ad data to be validated.
+ */
+ @Override
+ public void addValidation(
+ @NonNull AdData adData, @NonNull ImmutableCollection.Builder<String> violations) {
+ Objects.requireNonNull(adData);
+ Objects.requireNonNull(violations);
+
+ ImmutableCollection.Builder<String> adDataViolations = new ImmutableList.Builder<>();
+ mUriValidator.addValidation(adData.getRenderUri(), adDataViolations);
+ mMetadataValidator.addValidation(adData.getMetadata(), adDataViolations);
+
+ violations.addAll(
+ adDataViolations.build().stream()
+ .map(violation -> String.format(VIOLATION_FORMAT, adData, violation))
+ .collect(Collectors.toList()));
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/common/AdTechIdentifierValidator.java b/adservices/service-core/java/com/android/adservices/service/common/AdTechIdentifierValidator.java
new file mode 100644
index 0000000..4b4c329
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/common/AdTechIdentifierValidator.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.adservices.service.common;
+
+import static com.android.adservices.service.common.ValidatorUtil.HTTPS_SCHEME;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Uri;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.net.InternetDomainName;
+
+import java.util.Objects;
+
+/** Validation utility class for Ad Tech Identifier. */
+// TODO(b/239729221): Apply this to AdSelection
+public class AdTechIdentifierValidator implements Validator<String> {
+ @VisibleForTesting
+ public static final String IDENTIFIER_SHOULD_NOT_BE_NULL_OR_EMPTY =
+ "The %s's %s should not be null nor empty.";
+
+ @VisibleForTesting
+ public static final String IDENTIFIER_HAS_MISSING_DOMAIN_NAME =
+ "The %s's %s has missing domain name.";
+
+ @VisibleForTesting
+ public static final String IDENTIFIER_IS_AN_INVALID_DOMAIN_NAME =
+ "The %s's %s is an invalid domain name.";
+
+ @NonNull private final String mClassName;
+ @NonNull private final String mAdTechRole;
+
+ /**
+ * Constructs a validator which validates an ad tech identifier.
+ *
+ * @param className the class the name of the field came from, used in error string
+ * construction.
+ * @param adTechRole the identifier's field name, used in error string construction.
+ */
+ public AdTechIdentifierValidator(@NonNull String className, @NonNull String adTechRole) {
+ Objects.requireNonNull(className);
+ Objects.requireNonNull(adTechRole);
+
+ mClassName = className;
+ mAdTechRole = adTechRole;
+ }
+
+ /**
+ * Validate an ad tech identifier:
+ *
+ * <ul>
+ * <li>The identifier should not be empty or null.
+ * <li>The identifier should not have a valid domain name.
+ * <li>The identifier should be a domain name.
+ * </ul>
+ *
+ * @param adTechIdentifier the identifier to be validated
+ */
+ @Override
+ public void addValidation(
+ @Nullable String adTechIdentifier,
+ @NonNull ImmutableCollection.Builder<String> violations) {
+ Objects.requireNonNull(violations);
+
+ String host = Uri.parse(HTTPS_SCHEME + "://" + adTechIdentifier).getHost();
+ if (ValidatorUtil.isStringNullOrEmpty(adTechIdentifier)) {
+ violations.add(
+ String.format(IDENTIFIER_SHOULD_NOT_BE_NULL_OR_EMPTY, mClassName, mAdTechRole));
+ } else if (Objects.isNull(host) || host.isEmpty()) {
+ violations.add(
+ String.format(IDENTIFIER_HAS_MISSING_DOMAIN_NAME, mClassName, mAdTechRole));
+ } else if (!Objects.equals(host, adTechIdentifier)
+ || !InternetDomainName.isValid(adTechIdentifier)) {
+ violations.add(
+ String.format(IDENTIFIER_IS_AN_INVALID_DOMAIN_NAME, mClassName, mAdTechRole));
+ }
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/common/AdTechUriValidator.java b/adservices/service-core/java/com/android/adservices/service/common/AdTechUriValidator.java
new file mode 100644
index 0000000..8da99ce
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/common/AdTechUriValidator.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.adservices.service.common;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Uri;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.common.collect.ImmutableCollection;
+
+import java.util.Objects;
+
+/** Validates an ad tech uri against an ad tech identifier. */
+// TODO(b/239729221): Apply this to AdSelection
+public class AdTechUriValidator implements Validator<Uri> {
+
+ @VisibleForTesting
+ public static final String URI_SHOULD_BE_SPECIFIED = "The %s's %s should be specified.";
+
+ @VisibleForTesting
+ public static final String URI_SHOULD_HAVE_PRESENT_HOST =
+ "The %s's %s should have present host.";
+
+ @VisibleForTesting
+ public static final String URI_SHOULD_USE_HTTPS = "The %s's %s should use HTTPS.";
+
+ @VisibleForTesting
+ public static final String IDENTIFIER_AND_URL_ARE_INCONSISTENT =
+ "The %s host name %s and the %s-provided %s's host name %s are not consistent.";
+
+ @NonNull public final String mAdTechRole;
+ @NonNull public final String mAdTechIdentifier;
+ @NonNull public final String mClassName;
+ @NonNull public final String mUriFieldName;
+
+ public AdTechUriValidator(
+ @NonNull String adTechRole,
+ @NonNull String adTechIdentifier,
+ @NonNull String className,
+ @NonNull String uriFieldName) {
+ Objects.requireNonNull(adTechRole);
+ Objects.requireNonNull(adTechIdentifier);
+ Objects.requireNonNull(className);
+ Objects.requireNonNull(uriFieldName);
+
+ mAdTechRole = adTechRole;
+ mAdTechIdentifier = adTechIdentifier;
+ mClassName = className;
+ mUriFieldName = uriFieldName;
+ }
+
+ /**
+ * Validate an uri uses HTTPS and under the ad tech identifier domain.
+ *
+ * @param uri the uri to be validated.
+ */
+ @Override
+ public void addValidation(
+ @Nullable Uri uri, @NonNull ImmutableCollection.Builder<String> violations) {
+ Objects.requireNonNull(violations);
+
+ if (Objects.isNull(uri)) {
+ violations.add(String.format(URI_SHOULD_BE_SPECIFIED, mClassName, mUriFieldName));
+ } else {
+ String uriHost = uri.getHost();
+ if (ValidatorUtil.isStringNullOrEmpty(uriHost)) {
+ violations.add(
+ String.format(URI_SHOULD_HAVE_PRESENT_HOST, mClassName, mUriFieldName));
+ } else if (!ValidatorUtil.isStringNullOrEmpty(mAdTechIdentifier)
+ && !mAdTechIdentifier.equalsIgnoreCase(uriHost)) {
+ violations.add(
+ String.format(
+ IDENTIFIER_AND_URL_ARE_INCONSISTENT,
+ mAdTechRole,
+ mAdTechIdentifier,
+ mAdTechRole,
+ mUriFieldName,
+ uriHost));
+ } else if (!ValidatorUtil.HTTPS_SCHEME.equalsIgnoreCase(uri.getScheme())) {
+ violations.add(String.format(URI_SHOULD_USE_HTTPS, mClassName, mUriFieldName));
+ }
+ }
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/common/JsonValidator.java b/adservices/service-core/java/com/android/adservices/service/common/JsonValidator.java
new file mode 100644
index 0000000..5fbe00d
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/common/JsonValidator.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.adservices.service.common;
+
+import android.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import com.google.common.collect.ImmutableCollection;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.Objects;
+
+/** Validation utility class for JSON fields. */
+public class JsonValidator implements Validator<String> {
+ @VisibleForTesting
+ public static final String SHOULD_BE_A_VALID_JSON = "%s's %s should be a valid json.";
+
+ @NonNull private final String mClassName;
+ @NonNull private final String mFieldName;
+
+ /**
+ * Constructs a validator which validates an ad tech identifier.
+ *
+ * @param className the class the field belong to
+ * @param fieldName the field name of the json
+ */
+ public JsonValidator(@NonNull String className, @NonNull String fieldName) {
+ Preconditions.checkStringNotEmpty(className);
+ Preconditions.checkStringNotEmpty(fieldName);
+
+ mClassName = className;
+ mFieldName = fieldName;
+ }
+
+ /**
+ * Validate a string field is a valid json.
+ *
+ * @param json the field to be validated
+ */
+ @Override
+ public void addValidation(
+ @NonNull String json, @NonNull ImmutableCollection.Builder<String> violations) {
+ Objects.requireNonNull(json);
+ Objects.requireNonNull(violations);
+
+ try {
+ new JSONObject(json);
+ } catch (JSONException jsonException) {
+ violations.add(String.format(SHOULD_BE_A_VALID_JSON, mClassName, mFieldName));
+ }
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/adselection/Validator.java b/adservices/service-core/java/com/android/adservices/service/common/Validator.java
similarity index 70%
rename from adservices/service-core/java/com/android/adservices/service/adselection/Validator.java
rename to adservices/service-core/java/com/android/adservices/service/common/Validator.java
index 7c13910..7cea706 100644
--- a/adservices/service-core/java/com/android/adservices/service/adselection/Validator.java
+++ b/adservices/service-core/java/com/android/adservices/service/common/Validator.java
@@ -14,7 +14,10 @@
* limitations under the License.
*/
-package com.android.adservices.service.adselection;
+package com.android.adservices.service.common;
+
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
import java.util.Collection;
@@ -24,6 +27,8 @@
* @param <T> is the type name of the object instance to be validated.
*/
public interface Validator<T> {
+ String EXCEPTION_MESSAGE_FORMAT = "Invalid object of type %s. The violations are: %s";
+
/**
* Validate the object instance of type T.
*
@@ -36,8 +41,7 @@
if (!violations.isEmpty()) {
throw new IllegalArgumentException(
String.format(
- "Invalid object of type %s. The violations are: %s",
- object.getClass().getName(), violations));
+ EXCEPTION_MESSAGE_FORMAT, object.getClass().getName(), violations));
}
}
@@ -48,5 +52,12 @@
* @return an empty collection if the object is valid or a collection of strings describing all
* the encountered violations.
*/
- Collection<String> getValidationViolations(T object);
+ default Collection<String> getValidationViolations(T object) {
+ ImmutableCollection.Builder<String> violations = new ImmutableList.Builder<>();
+ addValidation(object, violations);
+ return violations.build();
+ }
+
+ /** Validates the object and populate the violations. */
+ void addValidation(T object, ImmutableCollection.Builder<String> violations);
}
diff --git a/adservices/service-core/java/com/android/adservices/service/common/ValidatorUtil.java b/adservices/service-core/java/com/android/adservices/service/common/ValidatorUtil.java
index c7fe1bb..4601aff 100644
--- a/adservices/service-core/java/com/android/adservices/service/common/ValidatorUtil.java
+++ b/adservices/service-core/java/com/android/adservices/service/common/ValidatorUtil.java
@@ -16,10 +16,21 @@
package com.android.adservices.service.common;
+import androidx.annotation.Nullable;
+
+import java.util.Objects;
+
/** Constants and utility method for data validation. */
public class ValidatorUtil {
+ public static final String AD_TECH_ROLE_BUYER = "buyer";
+ public static final String AD_TECH_ROLE_SELLER = "seller";
public static final String HTTPS_SCHEME = "https";
+ /** Returns true if the given string is null or empty or vise versa. */
+ public static boolean isStringNullOrEmpty(@Nullable String str) {
+ return Objects.isNull(str) || str.isEmpty();
+ }
+
private ValidatorUtil() {}
}
diff --git a/adservices/service-core/java/com/android/adservices/service/customaudience/CustomAudienceImpl.java b/adservices/service-core/java/com/android/adservices/service/customaudience/CustomAudienceImpl.java
index e5d942c..95306a1 100644
--- a/adservices/service-core/java/com/android/adservices/service/customaudience/CustomAudienceImpl.java
+++ b/adservices/service-core/java/com/android/adservices/service/customaudience/CustomAudienceImpl.java
@@ -24,11 +24,15 @@
import com.android.adservices.data.customaudience.CustomAudienceDao;
import com.android.adservices.data.customaudience.CustomAudienceDatabase;
import com.android.adservices.data.customaudience.DBCustomAudience;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.common.Validator;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import java.time.Clock;
+import java.time.Duration;
import java.time.Instant;
import java.util.Objects;
import java.util.Optional;
@@ -45,12 +49,24 @@
private static CustomAudienceImpl sSingleton;
private final CustomAudienceDao mCustomAudienceDao;
+ private final CustomAudienceQuantityChecker mCustomAudienceQuantityChecker;
+ private final Validator<CustomAudience> mCustomAudienceValidator;
private final Clock mClock;
+ private final Duration mCustomAudienceDefaultExpireIn;
@VisibleForTesting
- public CustomAudienceImpl(@NonNull CustomAudienceDao customAudienceDao, @NonNull Clock clock) {
+ public CustomAudienceImpl(
+ @NonNull CustomAudienceDao customAudienceDao,
+ @NonNull CustomAudienceQuantityChecker customAudienceQuantityChecker,
+ @NonNull Validator<CustomAudience> customAudienceValidator,
+ @NonNull Clock clock,
+ @NonNull Flags flags) {
mCustomAudienceDao = customAudienceDao;
+ mCustomAudienceQuantityChecker = customAudienceQuantityChecker;
+ mCustomAudienceValidator = customAudienceValidator;
mClock = clock;
+ mCustomAudienceDefaultExpireIn =
+ Duration.ofMillis(flags.getFledgeCustomAudienceDefaultExpireInMs());
}
/**
@@ -63,10 +79,16 @@
Objects.requireNonNull(context, "Context must be provided.");
synchronized (SINGLETON_LOCK) {
if (sSingleton == null) {
+ Flags flags = FlagsFactory.getFlags();
+ CustomAudienceDao customAudienceDao =
+ CustomAudienceDatabase.getInstance(context).customAudienceDao();
sSingleton =
new CustomAudienceImpl(
- CustomAudienceDatabase.getInstance(context).customAudienceDao(),
- Clock.systemUTC());
+ customAudienceDao,
+ new CustomAudienceQuantityChecker(customAudienceDao, flags),
+ CustomAudienceValidator.getInstance(context),
+ Clock.systemUTC(),
+ flags);
}
return sSingleton;
}
@@ -81,10 +103,15 @@
Objects.requireNonNull(customAudience);
Instant currentTime = mClock.instant();
- // TODO(b/231997523): Add JSON field validation.
+ mCustomAudienceQuantityChecker.check(customAudience);
+ mCustomAudienceValidator.validate(customAudience);
+
DBCustomAudience dbCustomAudience =
DBCustomAudience.fromServiceObject(
- customAudience, "not.implemented.yet", currentTime);
+ customAudience,
+ "not.implemented.yet",
+ currentTime,
+ mCustomAudienceDefaultExpireIn);
mCustomAudienceDao.insertOrOverwriteCustomAudience(
dbCustomAudience, customAudience.getDailyUpdateUrl());
diff --git a/adservices/service-core/java/com/android/adservices/service/customaudience/CustomAudienceQuantityChecker.java b/adservices/service-core/java/com/android/adservices/service/customaudience/CustomAudienceQuantityChecker.java
new file mode 100644
index 0000000..97fa635
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/customaudience/CustomAudienceQuantityChecker.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.adservices.service.customaudience;
+
+import android.adservices.customaudience.CustomAudience;
+import android.annotation.NonNull;
+
+import com.android.adservices.data.customaudience.CustomAudienceDao;
+import com.android.adservices.service.Flags;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/** Checks custom audience storage usage. */
+public class CustomAudienceQuantityChecker {
+ @VisibleForTesting
+ static final String CUSTOM_AUDIENCE_QUANTITY_CHECK_FAILED =
+ "Custom audience quantity check failed. %s";
+
+ @VisibleForTesting
+ static final String THE_MAX_NUMBER_OF_OWNER_ALLOWED_FOR_THE_DEVICE_HAD_REACHED =
+ "The max number of owner allowed for the device had reached.";
+
+ @VisibleForTesting
+ static final String THE_MAX_NUMBER_OF_CUSTOM_AUDIENCE_FOR_THE_DEVICE_HAD_REACHED =
+ "The max number of custom audience for the device had reached.";
+
+ @VisibleForTesting
+ static final String THE_MAX_NUMBER_OF_CUSTOM_AUDIENCE_FOR_THE_OWNER_HAD_REACHED =
+ "The max number of custom audience for the owner had reached.";
+
+ @NonNull private final CustomAudienceDao mCustomAudienceDao;
+ private final long mCustomAudienceMaxOwnerCount;
+ private final long mCustomAudienceMaxCount;
+ private final long mCustomAudiencePerAppMaxCount;
+
+ public CustomAudienceQuantityChecker(
+ @NonNull CustomAudienceDao customAudienceDao, @NonNull Flags flags) {
+ Objects.requireNonNull(customAudienceDao);
+ Objects.requireNonNull(flags);
+
+ mCustomAudienceDao = customAudienceDao;
+ mCustomAudienceMaxOwnerCount = flags.getFledgeCustomAudienceMaxOwnerCount();
+ mCustomAudienceMaxCount = flags.getFledgeCustomAudienceMaxCount();
+ mCustomAudiencePerAppMaxCount = flags.getFledgeCustomAudiencePerAppMaxCount();
+ }
+
+ /**
+ * Validates custom audience quantity:
+ *
+ * <ol>
+ * <li>The total number of custom audience does not exceed max allowed.
+ * <li>The total number of custom audience of an owner does not exceed max allowed.
+ * <li>The total number of custom audience owner does not exceed max allowed.
+ * </ol>
+ *
+ * @param customAudience The custom audience really to be validated against.
+ */
+ public void check(@NonNull CustomAudience customAudience) {
+ Objects.requireNonNull(customAudience);
+
+ List<String> violations = new ArrayList<>();
+ CustomAudienceDao.CustomAudienceStats customAudienceStats =
+ mCustomAudienceDao.getCustomAudienceStats(customAudience.getOwner());
+ if (customAudienceStats.getPerOwnerCount() == 0
+ && customAudienceStats.getOwnerCount() >= mCustomAudienceMaxOwnerCount) {
+ violations.add(THE_MAX_NUMBER_OF_OWNER_ALLOWED_FOR_THE_DEVICE_HAD_REACHED);
+ }
+ if (customAudienceStats.getTotalCount() >= mCustomAudienceMaxCount) {
+ violations.add(THE_MAX_NUMBER_OF_CUSTOM_AUDIENCE_FOR_THE_DEVICE_HAD_REACHED);
+ }
+ if (customAudienceStats.getPerOwnerCount() >= mCustomAudiencePerAppMaxCount) {
+ violations.add(THE_MAX_NUMBER_OF_CUSTOM_AUDIENCE_FOR_THE_OWNER_HAD_REACHED);
+ }
+ if (!violations.isEmpty()) {
+ throw new IllegalArgumentException(
+ String.format(CUSTOM_AUDIENCE_QUANTITY_CHECK_FAILED, violations));
+ }
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/customaudience/CustomAudienceTimestampValidator.java b/adservices/service-core/java/com/android/adservices/service/customaudience/CustomAudienceTimestampValidator.java
new file mode 100644
index 0000000..0b60244
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/customaudience/CustomAudienceTimestampValidator.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.adservices.service.customaudience;
+
+import android.adservices.customaudience.CustomAudience;
+import android.annotation.NonNull;
+
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.common.Validator;
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.common.collect.ImmutableCollection;
+
+import java.time.Clock;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Objects;
+
+/** Validates custom audience activation and expiration time */
+public class CustomAudienceTimestampValidator implements Validator<CustomAudience> {
+ @VisibleForTesting
+ static final String VIOLATION_EXPIRE_BEFORE_CURRENT_TIME =
+ "Custom audience must expire in the future, provided time is %s.";
+
+ @VisibleForTesting
+ static final String VIOLATION_EXPIRE_BEFORE_ACTIVATION =
+ "Custom audience activation time %s should not be after expiration time %s.";
+
+ @VisibleForTesting
+ static final String VIOLATION_EXPIRE_AFTER_MAX_EXPIRE_TIME =
+ "Custom audience expiration time should be within %s of activation time %s or"
+ + " current time %s, which ever is later, provided time is %s.";
+
+ @VisibleForTesting
+ static final String VIOLATION_ACTIVATE_AFTER_MAX_ACTIVATE =
+ "Custom audience activation time should be within %s from current time %s, "
+ + "provided time is %s.";
+
+ @NonNull private final Clock mClock;
+ @NonNull private final Duration mCustomAudienceMaxActivateIn;
+ @NonNull private final Duration mCustomAudienceMaxExpireIn;
+
+ public CustomAudienceTimestampValidator(@NonNull Clock clock, @NonNull Flags flags) {
+ Objects.requireNonNull(clock);
+ Objects.requireNonNull(flags);
+
+ mClock = clock;
+ mCustomAudienceMaxActivateIn =
+ Duration.ofMillis(flags.getFledgeCustomAudienceMaxActivationDelayInMs());
+ mCustomAudienceMaxExpireIn =
+ Duration.ofMillis(flags.getFledgeCustomAudienceMaxExpireInMs());
+ }
+
+ /**
+ * Validates custom audience activation time and expiration time.
+ *
+ * <ol>
+ * <li>The activation delay should not beyond limit.
+ * <li>The expiration time must be later then custom audience activation time.
+ * <li>The expiration time must be in the future.
+ * <li>The expiration time should not beyond certain time from activation.
+ * </ol>
+ *
+ * @param customAudience The custom audience need to be validated.
+ */
+ @Override
+ public void addValidation(
+ @NonNull CustomAudience customAudience,
+ @NonNull ImmutableCollection.Builder<String> violations) {
+ Objects.requireNonNull(customAudience);
+ Objects.requireNonNull(violations);
+
+ Instant currentTime = mClock.instant();
+ Instant activationTime = customAudience.getActivationTime();
+ Instant calculatedActivationTime =
+ activationTime.isBefore(currentTime) ? currentTime : activationTime;
+ Instant maxActivationTime = currentTime.plus(mCustomAudienceMaxActivateIn);
+ if (calculatedActivationTime.isAfter(maxActivationTime)) {
+ violations.add(
+ String.format(
+ VIOLATION_ACTIVATE_AFTER_MAX_ACTIVATE,
+ mCustomAudienceMaxActivateIn,
+ currentTime,
+ customAudience.getActivationTime()));
+ }
+ Instant expirationTime = customAudience.getExpirationTime();
+ if (!customAudience.getExpirationTime().isAfter(currentTime)) {
+ violations.add(String.format(VIOLATION_EXPIRE_BEFORE_CURRENT_TIME, expirationTime));
+ } else if (!expirationTime.isAfter(activationTime)) {
+ violations.add(
+ String.format(
+ VIOLATION_EXPIRE_BEFORE_ACTIVATION, activationTime, expirationTime));
+ } else if (expirationTime.isAfter(
+ calculatedActivationTime.plus(mCustomAudienceMaxExpireIn))) {
+ violations.add(
+ String.format(
+ VIOLATION_EXPIRE_AFTER_MAX_EXPIRE_TIME,
+ mCustomAudienceMaxExpireIn,
+ customAudience.getActivationTime(),
+ currentTime,
+ expirationTime));
+ }
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/customaudience/CustomAudienceValidator.java b/adservices/service-core/java/com/android/adservices/service/customaudience/CustomAudienceValidator.java
new file mode 100644
index 0000000..4d92123
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/customaudience/CustomAudienceValidator.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.adservices.service.customaudience;
+
+import android.adservices.common.AdData;
+import android.adservices.customaudience.CustomAudience;
+import android.annotation.NonNull;
+import android.content.Context;
+
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.common.AdDataValidator;
+import com.android.adservices.service.common.AdTechIdentifierValidator;
+import com.android.adservices.service.common.AdTechUriValidator;
+import com.android.adservices.service.common.JsonValidator;
+import com.android.adservices.service.common.Validator;
+import com.android.adservices.service.common.ValidatorUtil;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.common.collect.ImmutableCollection;
+
+import java.time.Clock;
+import java.util.Objects;
+
+/** Validator for Custom Audience. */
+public class CustomAudienceValidator implements Validator<CustomAudience> {
+ @VisibleForTesting
+ static final String CUSTOM_AUDIENCE_CLASS_NAME = CustomAudience.class.getName();
+
+ @VisibleForTesting static final String DAILY_UPDATE_URI_FIELD_NAME = "daily update uri";
+ @VisibleForTesting static final String BIDDING_LOGIC_URI_FIELD_NAME = "bidding logic uri";
+ @VisibleForTesting static final String USER_BIDDING_SIGNALS_FIELD_NAME = "user bidding signals";
+
+ private static final Object SINGLETON_LOCK = new Object();
+
+ @GuardedBy("SINGLETON_LOCK")
+ private static CustomAudienceValidator sSingleton;
+
+ @NonNull private final CustomAudienceTimestampValidator mCustomAudienceTimestampValidator;
+ @NonNull private final AdTechIdentifierValidator mBuyerValidator;
+ @NonNull private final JsonValidator mUserBiddingSignalsValidator;
+
+ @VisibleForTesting
+ public CustomAudienceValidator(
+ @NonNull CustomAudienceTimestampValidator customAudienceTimestampValidator,
+ @NonNull AdTechIdentifierValidator buyerValidator,
+ @NonNull JsonValidator userBiddingSignalsValidator) {
+ Objects.requireNonNull(customAudienceTimestampValidator);
+ Objects.requireNonNull(buyerValidator);
+ Objects.requireNonNull(userBiddingSignalsValidator);
+
+ mCustomAudienceTimestampValidator = customAudienceTimestampValidator;
+ mBuyerValidator = buyerValidator;
+ mUserBiddingSignalsValidator = userBiddingSignalsValidator;
+ }
+
+ @VisibleForTesting
+ public CustomAudienceValidator(@NonNull Clock clock, @NonNull Flags flags) {
+ this(
+ new CustomAudienceTimestampValidator(clock, flags),
+ new AdTechIdentifierValidator(
+ CUSTOM_AUDIENCE_CLASS_NAME, ValidatorUtil.AD_TECH_ROLE_BUYER),
+ new JsonValidator(CUSTOM_AUDIENCE_CLASS_NAME, USER_BIDDING_SIGNALS_FIELD_NAME));
+ }
+
+ /**
+ * Gets an instance of {@link CustomAudienceValidator} to be used.
+ *
+ * <p>If no instance has been initialized yet, a new one will be created. Otherwise, the
+ * existing instance will be returned.
+ */
+ @NonNull
+ public static CustomAudienceValidator getInstance(@NonNull Context context) {
+ Objects.requireNonNull(context, "Context must be provided.");
+ synchronized (SINGLETON_LOCK) {
+ if (sSingleton == null) {
+ Flags flags = FlagsFactory.getFlags();
+ sSingleton = new CustomAudienceValidator(Clock.systemUTC(), flags);
+ }
+ return sSingleton;
+ }
+ }
+
+ /**
+ * Validates the custom audience.
+ *
+ * @param customAudience the instance to be validated.
+ */
+ @Override
+ public void addValidation(
+ @NonNull CustomAudience customAudience,
+ @NonNull ImmutableCollection.Builder<String> violations) {
+ Objects.requireNonNull(customAudience);
+ Objects.requireNonNull(violations);
+
+ validateFieldFormat(customAudience, violations);
+ mCustomAudienceTimestampValidator.addValidation(customAudience, violations);
+ }
+
+ private void validateFieldFormat(
+ CustomAudience customAudience, ImmutableCollection.Builder<String> violations) {
+ String buyer = customAudience.getBuyer();
+ mBuyerValidator.addValidation(buyer, violations);
+ new AdTechUriValidator(
+ ValidatorUtil.AD_TECH_ROLE_BUYER,
+ buyer,
+ CUSTOM_AUDIENCE_CLASS_NAME,
+ DAILY_UPDATE_URI_FIELD_NAME)
+ .addValidation(customAudience.getDailyUpdateUrl(), violations);
+ new AdTechUriValidator(
+ ValidatorUtil.AD_TECH_ROLE_BUYER,
+ buyer,
+ CUSTOM_AUDIENCE_CLASS_NAME,
+ BIDDING_LOGIC_URI_FIELD_NAME)
+ .addValidation(customAudience.getBiddingLogicUrl(), violations);
+ if (customAudience.getUserBiddingSignals() != null) {
+ mUserBiddingSignalsValidator.addValidation(
+ customAudience.getUserBiddingSignals(), violations);
+ }
+ if (customAudience.getTrustedBiddingData() != null) {
+ new TrustedBiddingDataValidator(buyer)
+ .addValidation(customAudience.getTrustedBiddingData(), violations);
+ }
+ AdDataValidator adDataValidator =
+ new AdDataValidator(ValidatorUtil.AD_TECH_ROLE_BUYER, buyer);
+ for (AdData adData : customAudience.getAds()) {
+ adDataValidator.addValidation(adData, violations);
+ }
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/customaudience/TrustedBiddingDataValidator.java b/adservices/service-core/java/com/android/adservices/service/customaudience/TrustedBiddingDataValidator.java
new file mode 100644
index 0000000..f7669a2
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/customaudience/TrustedBiddingDataValidator.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.adservices.service.customaudience;
+
+import android.adservices.customaudience.TrustedBiddingData;
+import android.annotation.NonNull;
+
+import com.android.adservices.service.common.AdTechUriValidator;
+import com.android.adservices.service.common.Validator;
+import com.android.adservices.service.common.ValidatorUtil;
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.common.collect.ImmutableCollection;
+
+import java.util.Objects;
+
+/** Validator for a trusted bidding data. */
+public class TrustedBiddingDataValidator implements Validator<TrustedBiddingData> {
+ @VisibleForTesting
+ static final String TRUSTED_BIDDING_DATA_CLASS_NAME = TrustedBiddingData.class.getName();
+
+ @VisibleForTesting static final String TRUSTED_BIDDING_URI_FIELD_NAME = "trusted bidding uri";
+
+ @NonNull private final AdTechUriValidator mTrustedBiddingUriValidator;
+
+ public TrustedBiddingDataValidator(@NonNull String buyer) {
+ Objects.requireNonNull(buyer);
+
+ mTrustedBiddingUriValidator =
+ new AdTechUriValidator(
+ ValidatorUtil.AD_TECH_ROLE_BUYER,
+ buyer,
+ TRUSTED_BIDDING_DATA_CLASS_NAME,
+ TRUSTED_BIDDING_URI_FIELD_NAME);
+ }
+
+ /**
+ * Validate a trusted bidding data, the trusted bidding uri should be in buyer domain and should
+ * be a valid uri.
+ *
+ * @param trustedBiddingData the trusted bidding data to be validated.
+ */
+ @Override
+ public void addValidation(
+ @NonNull TrustedBiddingData trustedBiddingData,
+ @NonNull ImmutableCollection.Builder<String> violations) {
+ Objects.requireNonNull(trustedBiddingData);
+ Objects.requireNonNull(violations);
+
+ mTrustedBiddingUriValidator.addValidation(
+ trustedBiddingData.getTrustedBiddingUrl(), violations);
+ }
+}
diff --git a/adservices/tests/unittest/framework/src/android/adservices/customaudience/CustomAudienceTest.java b/adservices/tests/unittest/framework/src/android/adservices/customaudience/CustomAudienceTest.java
index c22154f..57c204c 100644
--- a/adservices/tests/unittest/framework/src/android/adservices/customaudience/CustomAudienceTest.java
+++ b/adservices/tests/unittest/framework/src/android/adservices/customaudience/CustomAudienceTest.java
@@ -207,33 +207,6 @@
}
@Test
- public void testSetInvalidBeforeNowExpirationTimeCustomAudienceFails() {
- assertThrows(
- IllegalArgumentException.class,
- () -> {
- // The expiry is in the past
- CustomAudienceFixture.getValidBuilderForBuyer(CommonFixture.VALID_BUYER)
- .setExpirationTime(
- CustomAudienceFixture.INVALID_BEFORE_NOW_EXPIRATION_TIME)
- .build();
- });
- }
-
- @Test
- public void testSetInvalidBeforeDelayedExpirationTimeCustomAudienceFails() {
- assertThrows(
- IllegalArgumentException.class,
- () -> {
- // The activation time is delayed, but the CA expires before it activates
- CustomAudienceFixture.getValidBuilderForBuyer(CommonFixture.VALID_BUYER)
- .setActivationTime(CustomAudienceFixture.VALID_DELAYED_ACTIVATION_TIME)
- .setExpirationTime(
- CustomAudienceFixture.INVALID_BEFORE_DELAYED_EXPIRATION_TIME)
- .build();
- });
- }
-
- @Test
public void testBuildNullAdsCustomAudienceSuccess() {
// Ads are not set, so the CustomAudience gets built with empty list.
CustomAudience nullAdsCustomAudience =
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/data/customaudience/DBCustomAudienceTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/data/customaudience/DBCustomAudienceTest.java
index 8865d2c..e92282e 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/data/customaudience/DBCustomAudienceTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/data/customaudience/DBCustomAudienceTest.java
@@ -53,7 +53,8 @@
CustomAudienceFixture.getValidBuilderForBuyer(CommonFixture.VALID_BUYER)
.build(),
CALLING_APP_NAME,
- CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI));
+ CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI,
+ DEFAULT_EXPIRE_IN));
}
@Test
@@ -64,7 +65,8 @@
DBCustomAudience.fromServiceObject(
null,
CALLING_APP_NAME,
- CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI));
+ CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI,
+ DEFAULT_EXPIRE_IN));
}
@Test
@@ -77,7 +79,8 @@
CommonFixture.VALID_BUYER)
.build(),
null,
- CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI));
+ CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI,
+ DEFAULT_EXPIRE_IN));
}
@Test
@@ -90,6 +93,21 @@
CommonFixture.VALID_BUYER)
.build(),
CALLING_APP_NAME,
+ null,
+ DEFAULT_EXPIRE_IN));
+ }
+
+ @Test
+ public void testFromServiceObject_nullDefaultExpireIn() {
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ DBCustomAudience.fromServiceObject(
+ CustomAudienceFixture.getValidBuilderForBuyer(
+ CommonFixture.VALID_BUYER)
+ .build(),
+ CALLING_APP_NAME,
+ CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI,
null));
}
@@ -105,7 +123,8 @@
.setAds(null)
.build(),
CALLING_APP_NAME,
- CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI));
+ CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI,
+ DEFAULT_EXPIRE_IN));
}
@Test
@@ -121,7 +140,8 @@
200))
.build(),
CALLING_APP_NAME,
- CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI));
+ CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI,
+ DEFAULT_EXPIRE_IN));
}
@Test
@@ -135,7 +155,8 @@
.setActivationTime(null)
.build(),
CALLING_APP_NAME,
- CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI));
+ CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI,
+ DEFAULT_EXPIRE_IN));
}
@Test
@@ -150,7 +171,8 @@
.setExpirationTime(null)
.build(),
CALLING_APP_NAME,
- CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI));
+ CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI,
+ DEFAULT_EXPIRE_IN));
}
@Test
@@ -164,7 +186,8 @@
.setOwner(null)
.build(),
CALLING_APP_NAME,
- CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI));
+ CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI,
+ DEFAULT_EXPIRE_IN));
}
@Test
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/AdDataValidatorTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/AdDataValidatorTest.java
new file mode 100644
index 0000000..e83d470
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/AdDataValidatorTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.adservices.service.common;
+
+import android.adservices.common.AdData;
+import android.adservices.common.AdDataFixture;
+import android.adservices.common.CommonFixture;
+import android.net.Uri;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class AdDataValidatorTest {
+
+ AdDataValidator mValidator =
+ new AdDataValidator(ValidatorUtil.AD_TECH_ROLE_BUYER, CommonFixture.VALID_BUYER);
+
+ @Test
+ public void testValidAdData() {
+ Assert.assertTrue(
+ mValidator
+ .getValidationViolations(
+ AdDataFixture.getValidAdsByBuyer(CommonFixture.VALID_BUYER).get(0))
+ .isEmpty());
+ }
+
+ @Test
+ public void testInvalidUri() {
+ String uriHost = "b.com";
+ AdData adData =
+ new AdData.Builder()
+ .setRenderUri(Uri.parse("https://" + uriHost + "/aaa"))
+ .setMetadata("{\"a\":1}")
+ .build();
+ ValidatorTestUtil.assertViolationContaiinsOnly(
+ mValidator.getValidationViolations(adData),
+ String.format(
+ AdDataValidator.VIOLATION_FORMAT,
+ adData,
+ String.format(
+ AdTechUriValidator.IDENTIFIER_AND_URL_ARE_INCONSISTENT,
+ ValidatorUtil.AD_TECH_ROLE_BUYER,
+ CommonFixture.VALID_BUYER,
+ ValidatorUtil.AD_TECH_ROLE_BUYER,
+ AdDataValidator.RENDER_URI_FIELD_NAME,
+ uriHost)));
+ }
+
+ @Test
+ public void testInvalidMetadata() {
+ AdData adData =
+ new AdData.Builder()
+ .setRenderUri(Uri.parse("https://" + CommonFixture.VALID_BUYER + "/aaa"))
+ .setMetadata("invalid[json]field")
+ .build();
+ ValidatorTestUtil.assertViolationContaiinsOnly(
+ mValidator.getValidationViolations(adData),
+ String.format(
+ AdDataValidator.VIOLATION_FORMAT,
+ adData,
+ String.format(
+ JsonValidator.SHOULD_BE_A_VALID_JSON,
+ AdDataValidator.AD_DATA_CLASS_NAME,
+ AdDataValidator.METADATA_FIELD_NAME)));
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/AdTechIdentifierValidatorTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/AdTechIdentifierValidatorTest.java
new file mode 100644
index 0000000..0934b2c
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/AdTechIdentifierValidatorTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.adservices.service.common;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class AdTechIdentifierValidatorTest {
+ private static final String CLASS_NAME = "class";
+ private static final String FIELD_NAME = "field";
+
+ private final AdTechIdentifierValidator mAdTechIdentifierValidator =
+ new AdTechIdentifierValidator(CLASS_NAME, FIELD_NAME);
+
+ @Test
+ public void testValidIdentifier() {
+ Assert.assertTrue(
+ mAdTechIdentifierValidator.getValidationViolations("domain.com").isEmpty());
+ }
+
+ @Test
+ public void testNullIdentifier() {
+ ValidatorTestUtil.assertViolationContaiinsOnly(
+ mAdTechIdentifierValidator.getValidationViolations(null),
+ String.format(
+ AdTechIdentifierValidator.IDENTIFIER_SHOULD_NOT_BE_NULL_OR_EMPTY,
+ CLASS_NAME,
+ FIELD_NAME));
+ }
+
+ @Test
+ public void testEmptyIdentifier() {
+ ValidatorTestUtil.assertViolationContaiinsOnly(
+ mAdTechIdentifierValidator.getValidationViolations(""),
+ String.format(
+ AdTechIdentifierValidator.IDENTIFIER_SHOULD_NOT_BE_NULL_OR_EMPTY,
+ CLASS_NAME,
+ FIELD_NAME));
+ }
+
+ @Test
+ public void testMissingHost() {
+ ValidatorTestUtil.assertViolationContaiinsOnly(
+ mAdTechIdentifierValidator.getValidationViolations("test@"),
+ String.format(
+ AdTechIdentifierValidator.IDENTIFIER_HAS_MISSING_DOMAIN_NAME,
+ CLASS_NAME,
+ FIELD_NAME));
+ }
+
+ @Test
+ public void testMissingDomainIdentifier() {
+ ValidatorTestUtil.assertViolationContaiinsOnly(
+ mAdTechIdentifierValidator.getValidationViolations("a/b/c"),
+ String.format(
+ AdTechIdentifierValidator.IDENTIFIER_IS_AN_INVALID_DOMAIN_NAME,
+ CLASS_NAME,
+ FIELD_NAME));
+ }
+
+ @Test
+ public void testDomainHasPort() {
+ ValidatorTestUtil.assertViolationContaiinsOnly(
+ mAdTechIdentifierValidator.getValidationViolations("localhost:3000"),
+ String.format(
+ AdTechIdentifierValidator.IDENTIFIER_IS_AN_INVALID_DOMAIN_NAME,
+ CLASS_NAME,
+ FIELD_NAME));
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/AdTechUriValidatorTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/AdTechUriValidatorTest.java
new file mode 100644
index 0000000..c8ebd83
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/AdTechUriValidatorTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.adservices.service.common;
+
+import android.adservices.common.CommonFixture;
+import android.net.Uri;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class AdTechUriValidatorTest {
+ private static final String CLASS_NAME = "class";
+ private static final String URI_FIELD_NAME = "field";
+
+ private final AdTechUriValidator mValidator =
+ new AdTechUriValidator(
+ ValidatorUtil.AD_TECH_ROLE_BUYER,
+ CommonFixture.VALID_BUYER,
+ CLASS_NAME,
+ URI_FIELD_NAME);
+
+ @Test
+ public void testValidUri() {
+ Assert.assertTrue(
+ mValidator
+ .getValidationViolations(
+ Uri.parse("https://" + CommonFixture.VALID_BUYER + "/valid/uri"))
+ .isEmpty());
+ }
+
+ @Test
+ public void testNullUri() {
+ ValidatorTestUtil.assertViolationContaiinsOnly(
+ mValidator.getValidationViolations(null),
+ String.format(
+ AdTechUriValidator.URI_SHOULD_BE_SPECIFIED, CLASS_NAME, URI_FIELD_NAME));
+ }
+
+ @Test
+ public void testNoHostUri() {
+ ValidatorTestUtil.assertViolationContaiinsOnly(
+ mValidator.getValidationViolations(Uri.parse("/a/b/c")),
+ String.format(
+ AdTechUriValidator.URI_SHOULD_HAVE_PRESENT_HOST,
+ CLASS_NAME,
+ URI_FIELD_NAME));
+ }
+
+ @Test
+ public void testNotMatchHost() {
+ String uriHost = "buy.com";
+ ValidatorTestUtil.assertViolationContaiinsOnly(
+ mValidator.getValidationViolations(Uri.parse("https://" + uriHost + "/not/match")),
+ String.format(
+ AdTechUriValidator.IDENTIFIER_AND_URL_ARE_INCONSISTENT,
+ ValidatorUtil.AD_TECH_ROLE_BUYER,
+ CommonFixture.VALID_BUYER,
+ ValidatorUtil.AD_TECH_ROLE_BUYER,
+ URI_FIELD_NAME,
+ uriHost));
+ }
+
+ @Test
+ public void testNotHttpsHost() {
+ ValidatorTestUtil.assertViolationContaiinsOnly(
+ mValidator.getValidationViolations(
+ Uri.parse("http://" + CommonFixture.VALID_BUYER + "/not/https/")),
+ String.format(AdTechUriValidator.URI_SHOULD_USE_HTTPS, CLASS_NAME, URI_FIELD_NAME));
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/FledgeE2ETest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/FledgeE2ETest.java
index fa18cac..a397d8f 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/FledgeE2ETest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/FledgeE2ETest.java
@@ -58,7 +58,9 @@
import com.android.adservices.service.FlagsFactory;
import com.android.adservices.service.adselection.AdSelectionServiceImpl;
import com.android.adservices.service.customaudience.CustomAudienceImpl;
+import com.android.adservices.service.customaudience.CustomAudienceQuantityChecker;
import com.android.adservices.service.customaudience.CustomAudienceServiceImpl;
+import com.android.adservices.service.customaudience.CustomAudienceValidator;
import com.android.adservices.service.devapi.DevContext;
import com.android.adservices.service.devapi.DevContextFilter;
import com.android.adservices.service.stats.AdServicesLogger;
@@ -170,7 +172,12 @@
new CustomAudienceServiceImpl(
CONTEXT,
new CustomAudienceImpl(
- mCustomAudienceDao, CommonFixture.FIXED_CLOCK_TRUNCATED_TO_MILLI),
+ mCustomAudienceDao,
+ new CustomAudienceQuantityChecker(mCustomAudienceDao, mFlags),
+ new CustomAudienceValidator(
+ CommonFixture.FIXED_CLOCK_TRUNCATED_TO_MILLI, mFlags),
+ CommonFixture.FIXED_CLOCK_TRUNCATED_TO_MILLI,
+ mFlags),
mDevContextFilter,
MoreExecutors.newDirectExecutorService(),
mAdServicesLogger);
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/JsonValidatorTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/JsonValidatorTest.java
new file mode 100644
index 0000000..f7b872e
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/JsonValidatorTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.adservices.service.common;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class JsonValidatorTest {
+ private static final String CLASS_NAME = "class";
+ private static final String FIELD_NAME = "field";
+
+ private final JsonValidator mJsonValidator = new JsonValidator(CLASS_NAME, FIELD_NAME);
+
+ @Test
+ public void testNullJson() {
+ assertThrows(
+ NullPointerException.class,
+ () -> {
+ mJsonValidator.getValidationViolations(null);
+ });
+ }
+
+ @Test
+ public void testValidJsonObject() {
+ assertTrue(mJsonValidator.getValidationViolations("{\"a\":5}").isEmpty());
+ }
+
+ @Test
+ public void testValidJsonArray() {
+ ValidatorTestUtil.assertViolationContaiinsOnly(
+ mJsonValidator.getValidationViolations("[\"a\", \"b\"]"),
+ String.format(JsonValidator.SHOULD_BE_A_VALID_JSON, CLASS_NAME, FIELD_NAME));
+ }
+
+ @Test
+ public void testInvalidJson() {
+ ValidatorTestUtil.assertViolationContaiinsOnly(
+ mJsonValidator.getValidationViolations("invalid[json]field"),
+ String.format(JsonValidator.SHOULD_BE_A_VALID_JSON, CLASS_NAME, FIELD_NAME));
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/ValidatorTestUtil.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/ValidatorTestUtil.java
new file mode 100644
index 0000000..10dbca6
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/ValidatorTestUtil.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.adservices.service.common;
+
+import org.junit.Assert;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+public class ValidatorTestUtil {
+ public static void assertViolationContaiinsOnly(
+ Collection<String> errors, String... expectedErrors) {
+ List<String> expectedErrorsList = Arrays.asList(expectedErrors);
+ Assert.assertEquals(
+ String.format("expected %s / actual %s", expectedErrorsList, errors),
+ expectedErrors.length,
+ errors.size());
+ Assert.assertTrue(
+ String.format("expected %s / actual %s", expectedErrorsList, errors),
+ errors.containsAll(expectedErrorsList));
+ Assert.assertTrue(
+ String.format("expected %s / actual %s", expectedErrorsList, errors),
+ expectedErrorsList.containsAll(errors));
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceImplTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceImplTest.java
index 990a41e..b8c5a5a 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceImplTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceImplTest.java
@@ -28,6 +28,7 @@
import com.android.adservices.customaudience.DBCustomAudienceFixture;
import com.android.adservices.data.customaudience.CustomAudienceDao;
import com.android.adservices.data.customaudience.DBCustomAudience;
+import com.android.adservices.service.common.Validator;
import org.junit.Before;
import org.junit.Test;
@@ -47,13 +48,21 @@
@Mock
private CustomAudienceDao mCustomAudienceDao;
+ @Mock private CustomAudienceQuantityChecker mCustomAudienceQuantityChecker;
+ @Mock private Validator<CustomAudience> mCustomAudienceValidator;
@Mock private Clock mClock;
public CustomAudienceImpl mImpl;
@Before
public void setup() {
- mImpl = new CustomAudienceImpl(mCustomAudienceDao, mClock);
+ mImpl =
+ new CustomAudienceImpl(
+ mCustomAudienceDao,
+ mCustomAudienceQuantityChecker,
+ mCustomAudienceValidator,
+ mClock,
+ CommonFixture.FLAGS_FOR_TEST);
}
@Test
@@ -69,7 +78,9 @@
CustomAudienceFixture.getValidDailyUpdateUriByBuyer(
CommonFixture.VALID_BUYER));
verify(mClock).instant();
- verifyNoMoreInteractions(mClock, mCustomAudienceDao);
+ verify(mCustomAudienceQuantityChecker).check(VALID_CUSTOM_AUDIENCE);
+ verify(mCustomAudienceValidator).validate(VALID_CUSTOM_AUDIENCE);
+ verifyNoMoreInteractions(mClock, mCustomAudienceDao, mCustomAudienceValidator);
}
@Test
@@ -85,6 +96,10 @@
CommonFixture.VALID_BUYER,
CustomAudienceFixture.VALID_NAME);
- verifyNoMoreInteractions(mClock, mCustomAudienceDao);
+ verifyNoMoreInteractions(
+ mClock,
+ mCustomAudienceDao,
+ mCustomAudienceQuantityChecker,
+ mCustomAudienceValidator);
}
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceQuantityCheckerTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceQuantityCheckerTest.java
new file mode 100644
index 0000000..68fd667
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceQuantityCheckerTest.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.adservices.service.customaudience;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+import android.adservices.common.CommonFixture;
+import android.adservices.customaudience.CustomAudienceFixture;
+
+import com.android.adservices.data.customaudience.CustomAudienceDao;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.List;
+
+@RunWith(MockitoJUnitRunner.class)
+public class CustomAudienceQuantityCheckerTest {
+ private static final Flags FLAGS = FlagsFactory.getFlagsForTest();
+
+ @Mock private CustomAudienceDao mCustomAudienceDao;
+
+ private CustomAudienceQuantityChecker mChecker;
+
+ @Before
+ public void setup() {
+ mChecker = new CustomAudienceQuantityChecker(mCustomAudienceDao, FLAGS);
+ }
+
+ @Test
+ public void testNullCustomAudience_throwNPE() {
+ assertThrows(
+ NullPointerException.class,
+ () -> {
+ mChecker.check(null);
+ });
+ verifyNoMoreInteractions(mCustomAudienceDao);
+ }
+
+ @Test
+ public void testExistOwnerAndOwnerReachMax_success() {
+ when(mCustomAudienceDao.getCustomAudienceStats(CustomAudienceFixture.VALID_OWNER))
+ .thenReturn(
+ new CustomAudienceDao.CustomAudienceStats(
+ CustomAudienceFixture.VALID_OWNER,
+ 20L,
+ 1L,
+ FLAGS.getFledgeCustomAudienceMaxOwnerCount()));
+ mChecker.check(
+ CustomAudienceFixture.getValidBuilderForBuyer(CommonFixture.VALID_BUYER).build());
+ verify(mCustomAudienceDao).getCustomAudienceStats(CustomAudienceFixture.VALID_OWNER);
+ verifyNoMoreInteractions(mCustomAudienceDao);
+ }
+
+ @Test
+ public void testOwnerExceedMax() {
+ when(mCustomAudienceDao.getCustomAudienceStats(CustomAudienceFixture.VALID_OWNER))
+ .thenReturn(
+ new CustomAudienceDao.CustomAudienceStats(
+ CustomAudienceFixture.VALID_OWNER,
+ 20L,
+ 0L,
+ FLAGS.getFledgeCustomAudienceMaxOwnerCount()));
+
+ assertViolations(
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ mChecker.check(
+ CustomAudienceFixture.getValidBuilderForBuyer(
+ CommonFixture.VALID_BUYER)
+ .build());
+ }),
+ CustomAudienceQuantityChecker
+ .THE_MAX_NUMBER_OF_OWNER_ALLOWED_FOR_THE_DEVICE_HAD_REACHED);
+
+ verify(mCustomAudienceDao).getCustomAudienceStats(CustomAudienceFixture.VALID_OWNER);
+ verifyNoMoreInteractions(mCustomAudienceDao);
+ }
+
+ @Test
+ public void testTotalCountExceedMax() {
+ when(mCustomAudienceDao.getCustomAudienceStats(CustomAudienceFixture.VALID_OWNER))
+ .thenReturn(
+ new CustomAudienceDao.CustomAudienceStats(
+ CustomAudienceFixture.VALID_OWNER,
+ FLAGS.getFledgeCustomAudienceMaxCount(),
+ 0L,
+ 1L));
+ assertViolations(
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ mChecker.check(
+ CustomAudienceFixture.getValidBuilderForBuyer(
+ CommonFixture.VALID_BUYER)
+ .build())),
+ CustomAudienceQuantityChecker
+ .THE_MAX_NUMBER_OF_CUSTOM_AUDIENCE_FOR_THE_DEVICE_HAD_REACHED);
+ verify(mCustomAudienceDao).getCustomAudienceStats(CustomAudienceFixture.VALID_OWNER);
+ verifyNoMoreInteractions(mCustomAudienceDao);
+ }
+
+ @Test
+ public void testPerOwnerCountExceedMax() {
+ when(mCustomAudienceDao.getCustomAudienceStats(CustomAudienceFixture.VALID_OWNER))
+ .thenReturn(
+ new CustomAudienceDao.CustomAudienceStats(
+ CustomAudienceFixture.VALID_OWNER,
+ 20L,
+ FLAGS.getFledgeCustomAudiencePerAppMaxCount(),
+ 1L));
+ assertViolations(
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ mChecker.check(
+ CustomAudienceFixture.getValidBuilderForBuyer(
+ CommonFixture.VALID_BUYER)
+ .build());
+ }),
+ CustomAudienceQuantityChecker
+ .THE_MAX_NUMBER_OF_CUSTOM_AUDIENCE_FOR_THE_OWNER_HAD_REACHED);
+
+ verify(mCustomAudienceDao).getCustomAudienceStats(CustomAudienceFixture.VALID_OWNER);
+ verifyNoMoreInteractions(mCustomAudienceDao);
+ }
+
+ @Test
+ public void testAllGood() {
+ when(mCustomAudienceDao.getCustomAudienceStats(CustomAudienceFixture.VALID_OWNER))
+ .thenReturn(
+ new CustomAudienceDao.CustomAudienceStats(
+ CustomAudienceFixture.VALID_OWNER, 0L, 0L, 0L));
+ mChecker.check(
+ CustomAudienceFixture.getValidBuilderForBuyer(CommonFixture.VALID_BUYER).build());
+
+ verify(mCustomAudienceDao).getCustomAudienceStats(CustomAudienceFixture.VALID_OWNER);
+ verifyNoMoreInteractions(mCustomAudienceDao);
+ }
+
+ private void assertViolations(Exception exception, String... violations) {
+ assertEquals(
+ String.format(
+ CustomAudienceQuantityChecker.CUSTOM_AUDIENCE_QUANTITY_CHECK_FAILED,
+ List.of(violations)),
+ exception.getMessage());
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceServiceEndToEndTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceServiceEndToEndTest.java
index 37f4118..0d8ed22 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceServiceEndToEndTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceServiceEndToEndTest.java
@@ -120,11 +120,22 @@
.build()
.customAudienceDao();
+ CustomAudienceQuantityChecker customAudienceQuantityChecker =
+ new CustomAudienceQuantityChecker(mCustomAudienceDao, CommonFixture.FLAGS_FOR_TEST);
+
+ CustomAudienceValidator customAudienceValidator =
+ new CustomAudienceValidator(
+ CommonFixture.FIXED_CLOCK_TRUNCATED_TO_MILLI, CommonFixture.FLAGS_FOR_TEST);
+
mService =
new CustomAudienceServiceImpl(
CONTEXT,
new CustomAudienceImpl(
- mCustomAudienceDao, CommonFixture.FIXED_CLOCK_TRUNCATED_TO_MILLI),
+ mCustomAudienceDao,
+ customAudienceQuantityChecker,
+ customAudienceValidator,
+ CommonFixture.FIXED_CLOCK_TRUNCATED_TO_MILLI,
+ CommonFixture.FLAGS_FOR_TEST),
mDevContextFilter,
MoreExecutors.newDirectExecutorService(),
mAdServicesLogger);
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceTimestampValidatorTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceTimestampValidatorTest.java
new file mode 100644
index 0000000..e0baf20
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceTimestampValidatorTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.adservices.service.customaudience;
+
+import android.adservices.common.CommonFixture;
+import android.adservices.customaudience.CustomAudienceFixture;
+
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.common.ValidatorTestUtil;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class CustomAudienceTimestampValidatorTest {
+ private static final Flags FLAGS = FlagsFactory.getFlagsForTest();
+ private final CustomAudienceTimestampValidator mValidator =
+ new CustomAudienceTimestampValidator(
+ CommonFixture.FIXED_CLOCK_TRUNCATED_TO_MILLI, FLAGS);
+
+ @Test
+ public void testAllValidTimes() {
+ Assert.assertTrue(
+ mValidator
+ .getValidationViolations(
+ CustomAudienceFixture.getValidBuilderForBuyer(
+ CommonFixture.VALID_BUYER)
+ .build())
+ .isEmpty());
+ }
+
+ @Test
+ public void testActivationTimeBeyondMaxAndExpireBeforeActivation() {
+
+ ValidatorTestUtil.assertViolationContaiinsOnly(
+ mValidator.getValidationViolations(
+ CustomAudienceFixture.getValidBuilderForBuyer(CommonFixture.VALID_BUYER)
+ .setActivationTime(
+ CustomAudienceFixture.INVALID_DELAYED_ACTIVATION_TIME)
+ .build()),
+ String.format(
+ CustomAudienceTimestampValidator.VIOLATION_ACTIVATE_AFTER_MAX_ACTIVATE,
+ CustomAudienceFixture.CUSTOM_AUDIENCE_MAX_ACTIVATION_DELAY_IN,
+ CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI,
+ CustomAudienceFixture.INVALID_DELAYED_ACTIVATION_TIME),
+ String.format(
+ CustomAudienceTimestampValidator.VIOLATION_EXPIRE_BEFORE_ACTIVATION,
+ CustomAudienceFixture.INVALID_DELAYED_ACTIVATION_TIME,
+ CustomAudienceFixture.VALID_EXPIRATION_TIME));
+ }
+
+ @Test
+ public void testExpirationTimeBeforeNow() {
+ ValidatorTestUtil.assertViolationContaiinsOnly(
+ mValidator.getValidationViolations(
+ CustomAudienceFixture.getValidBuilderForBuyer(CommonFixture.VALID_BUYER)
+ .setExpirationTime(
+ CustomAudienceFixture.INVALID_BEFORE_NOW_EXPIRATION_TIME)
+ .build()),
+ String.format(
+ CustomAudienceTimestampValidator.VIOLATION_EXPIRE_BEFORE_CURRENT_TIME,
+ CustomAudienceFixture.INVALID_BEFORE_NOW_EXPIRATION_TIME));
+ }
+
+ @Test
+ public void testExpirationTimeBeforeActivation() {
+ ValidatorTestUtil.assertViolationContaiinsOnly(
+ mValidator.getValidationViolations(
+ CustomAudienceFixture.getValidBuilderForBuyer(CommonFixture.VALID_BUYER)
+ .setExpirationTime(
+ CustomAudienceFixture
+ .INVALID_BEFORE_DELAYED_EXPIRATION_TIME)
+ .setActivationTime(
+ CustomAudienceFixture.VALID_DELAYED_ACTIVATION_TIME)
+ .build()),
+ String.format(
+ CustomAudienceTimestampValidator.VIOLATION_EXPIRE_BEFORE_ACTIVATION,
+ CustomAudienceFixture.VALID_DELAYED_ACTIVATION_TIME,
+ CustomAudienceFixture.INVALID_BEFORE_DELAYED_EXPIRATION_TIME));
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceValidatorTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceValidatorTest.java
new file mode 100644
index 0000000..7273f58
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceValidatorTest.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.adservices.service.customaudience;
+
+import android.adservices.common.AdData;
+import android.adservices.common.AdDataFixture;
+import android.adservices.common.CommonFixture;
+import android.adservices.customaudience.CustomAudienceFixture;
+import android.adservices.customaudience.TrustedBiddingDataFixture;
+
+import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.common.AdDataValidator;
+import com.android.adservices.service.common.AdTechIdentifierValidator;
+import com.android.adservices.service.common.AdTechUriValidator;
+import com.android.adservices.service.common.JsonValidator;
+import com.android.adservices.service.common.ValidatorTestUtil;
+import com.android.adservices.service.common.ValidatorUtil;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.List;
+
+public class CustomAudienceValidatorTest {
+ private static final String ANOTHER_BUYER = "b.com";
+
+ private final CustomAudienceValidator mValidator =
+ new CustomAudienceValidator(
+ CommonFixture.FIXED_CLOCK_TRUNCATED_TO_MILLI, FlagsFactory.getFlagsForTest());
+
+ @Test
+ public void testValidCustomAudience() {
+ Assert.assertTrue(
+ mValidator
+ .getValidationViolations(
+ CustomAudienceFixture.getValidBuilderForBuyer(
+ CommonFixture.VALID_BUYER)
+ .build())
+ .isEmpty());
+ }
+
+ @Test
+ public void testValidCustomAudienceWithNullFields() {
+ Assert.assertTrue(
+ mValidator
+ .getValidationViolations(
+ CustomAudienceFixture.getValidBuilderForBuyer(
+ CommonFixture.VALID_BUYER)
+ .setTrustedBiddingData(null)
+ .setUserBiddingSignals(null)
+ .build())
+ .isEmpty());
+ }
+
+ @Test
+ public void testInvalidBuyer() {
+ String buyerWithPath = CommonFixture.VALID_BUYER + "/path";
+ List<AdData> adDataList = AdDataFixture.getValidAdsByBuyer(buyerWithPath);
+
+ ValidatorTestUtil.assertViolationContaiinsOnly(
+ mValidator.getValidationViolations(
+ CustomAudienceFixture.getValidBuilderForBuyer(buyerWithPath).build()),
+ String.format(
+ AdTechIdentifierValidator.IDENTIFIER_IS_AN_INVALID_DOMAIN_NAME,
+ CustomAudienceValidator.CUSTOM_AUDIENCE_CLASS_NAME,
+ ValidatorUtil.AD_TECH_ROLE_BUYER),
+ String.format(
+ AdTechUriValidator.IDENTIFIER_AND_URL_ARE_INCONSISTENT,
+ ValidatorUtil.AD_TECH_ROLE_BUYER,
+ buyerWithPath,
+ ValidatorUtil.AD_TECH_ROLE_BUYER,
+ CustomAudienceValidator.DAILY_UPDATE_URI_FIELD_NAME,
+ CommonFixture.VALID_BUYER),
+ String.format(
+ AdTechUriValidator.IDENTIFIER_AND_URL_ARE_INCONSISTENT,
+ ValidatorUtil.AD_TECH_ROLE_BUYER,
+ buyerWithPath,
+ ValidatorUtil.AD_TECH_ROLE_BUYER,
+ CustomAudienceValidator.BIDDING_LOGIC_URI_FIELD_NAME,
+ CommonFixture.VALID_BUYER),
+ String.format(
+ AdTechUriValidator.IDENTIFIER_AND_URL_ARE_INCONSISTENT,
+ ValidatorUtil.AD_TECH_ROLE_BUYER,
+ buyerWithPath,
+ ValidatorUtil.AD_TECH_ROLE_BUYER,
+ TrustedBiddingDataValidator.TRUSTED_BIDDING_URI_FIELD_NAME,
+ CommonFixture.VALID_BUYER),
+ String.format(
+ AdDataValidator.VIOLATION_FORMAT,
+ adDataList.get(0),
+ String.format(
+ AdTechUriValidator.IDENTIFIER_AND_URL_ARE_INCONSISTENT,
+ ValidatorUtil.AD_TECH_ROLE_BUYER,
+ buyerWithPath,
+ ValidatorUtil.AD_TECH_ROLE_BUYER,
+ AdDataValidator.RENDER_URI_FIELD_NAME,
+ CommonFixture.VALID_BUYER)),
+ String.format(
+ AdDataValidator.VIOLATION_FORMAT,
+ adDataList.get(1),
+ String.format(
+ AdTechUriValidator.IDENTIFIER_AND_URL_ARE_INCONSISTENT,
+ ValidatorUtil.AD_TECH_ROLE_BUYER,
+ buyerWithPath,
+ ValidatorUtil.AD_TECH_ROLE_BUYER,
+ AdDataValidator.RENDER_URI_FIELD_NAME,
+ CommonFixture.VALID_BUYER)),
+ String.format(
+ AdDataValidator.VIOLATION_FORMAT,
+ adDataList.get(2),
+ String.format(
+ AdTechUriValidator.IDENTIFIER_AND_URL_ARE_INCONSISTENT,
+ ValidatorUtil.AD_TECH_ROLE_BUYER,
+ buyerWithPath,
+ ValidatorUtil.AD_TECH_ROLE_BUYER,
+ AdDataValidator.RENDER_URI_FIELD_NAME,
+ CommonFixture.VALID_BUYER)),
+ String.format(
+ AdDataValidator.VIOLATION_FORMAT,
+ adDataList.get(3),
+ String.format(
+ AdTechUriValidator.IDENTIFIER_AND_URL_ARE_INCONSISTENT,
+ ValidatorUtil.AD_TECH_ROLE_BUYER,
+ buyerWithPath,
+ ValidatorUtil.AD_TECH_ROLE_BUYER,
+ AdDataValidator.RENDER_URI_FIELD_NAME,
+ CommonFixture.VALID_BUYER)));
+ }
+
+ @Test
+ public void testInvalidDailyUpdateUriAndBiddingLogicUri() {
+ ValidatorTestUtil.assertViolationContaiinsOnly(
+ mValidator.getValidationViolations(
+ CustomAudienceFixture.getValidBuilderForBuyer(CommonFixture.VALID_BUYER)
+ .setDailyUpdateUrl(
+ CustomAudienceFixture.getValidDailyUpdateUriByBuyer(
+ ANOTHER_BUYER))
+ .setBiddingLogicUrl(
+ CustomAudienceFixture.getValidBiddingLogicUrlByBuyer(
+ ANOTHER_BUYER))
+ .build()),
+ String.format(
+ AdTechUriValidator.IDENTIFIER_AND_URL_ARE_INCONSISTENT,
+ ValidatorUtil.AD_TECH_ROLE_BUYER,
+ CommonFixture.VALID_BUYER,
+ ValidatorUtil.AD_TECH_ROLE_BUYER,
+ CustomAudienceValidator.DAILY_UPDATE_URI_FIELD_NAME,
+ ANOTHER_BUYER),
+ String.format(
+ AdTechUriValidator.IDENTIFIER_AND_URL_ARE_INCONSISTENT,
+ ValidatorUtil.AD_TECH_ROLE_BUYER,
+ CommonFixture.VALID_BUYER,
+ ValidatorUtil.AD_TECH_ROLE_BUYER,
+ CustomAudienceValidator.BIDDING_LOGIC_URI_FIELD_NAME,
+ ANOTHER_BUYER));
+ }
+
+ @Test
+ public void testInvalidTrustedBiddingData() {
+ ValidatorTestUtil.assertViolationContaiinsOnly(
+ mValidator.getValidationViolations(
+ CustomAudienceFixture.getValidBuilderForBuyer(CommonFixture.VALID_BUYER)
+ .setTrustedBiddingData(
+ TrustedBiddingDataFixture.getValidTrustedBiddingDataByBuyer(
+ ANOTHER_BUYER))
+ .build()),
+ String.format(
+ AdTechUriValidator.IDENTIFIER_AND_URL_ARE_INCONSISTENT,
+ ValidatorUtil.AD_TECH_ROLE_BUYER,
+ CommonFixture.VALID_BUYER,
+ ValidatorUtil.AD_TECH_ROLE_BUYER,
+ TrustedBiddingDataValidator.TRUSTED_BIDDING_URI_FIELD_NAME,
+ ANOTHER_BUYER));
+ }
+
+ @Test
+ public void testInvalidUserBiddingSignals() {
+ ValidatorTestUtil.assertViolationContaiinsOnly(
+ mValidator.getValidationViolations(
+ CustomAudienceFixture.getValidBuilderForBuyer(CommonFixture.VALID_BUYER)
+ .setUserBiddingSignals("Not[A]VALID[JSON]")
+ .build()),
+ String.format(
+ JsonValidator.SHOULD_BE_A_VALID_JSON,
+ CustomAudienceValidator.CUSTOM_AUDIENCE_CLASS_NAME,
+ CustomAudienceValidator.USER_BIDDING_SIGNALS_FIELD_NAME));
+ }
+
+ @Test
+ public void testActivationTimeBeyondMaxAndExpireBeforeActivation() {
+
+ ValidatorTestUtil.assertViolationContaiinsOnly(
+ mValidator.getValidationViolations(
+ CustomAudienceFixture.getValidBuilderForBuyer(CommonFixture.VALID_BUYER)
+ .setActivationTime(
+ CustomAudienceFixture.INVALID_DELAYED_ACTIVATION_TIME)
+ .build()),
+ String.format(
+ CustomAudienceTimestampValidator.VIOLATION_ACTIVATE_AFTER_MAX_ACTIVATE,
+ CustomAudienceFixture.CUSTOM_AUDIENCE_MAX_ACTIVATION_DELAY_IN,
+ CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI,
+ CustomAudienceFixture.INVALID_DELAYED_ACTIVATION_TIME),
+ String.format(
+ CustomAudienceTimestampValidator.VIOLATION_EXPIRE_BEFORE_ACTIVATION,
+ CustomAudienceFixture.INVALID_DELAYED_ACTIVATION_TIME,
+ CustomAudienceFixture.VALID_EXPIRATION_TIME));
+ }
+
+ @Test
+ public void testExpirationTimeBeforeNow() {
+ ValidatorTestUtil.assertViolationContaiinsOnly(
+ mValidator.getValidationViolations(
+ CustomAudienceFixture.getValidBuilderForBuyer(CommonFixture.VALID_BUYER)
+ .setExpirationTime(
+ CustomAudienceFixture.INVALID_BEFORE_NOW_EXPIRATION_TIME)
+ .build()),
+ String.format(
+ CustomAudienceTimestampValidator.VIOLATION_EXPIRE_BEFORE_CURRENT_TIME,
+ CustomAudienceFixture.INVALID_BEFORE_NOW_EXPIRATION_TIME));
+ }
+
+ @Test
+ public void testExpirationTimeBeforeActivation() {
+ ValidatorTestUtil.assertViolationContaiinsOnly(
+ mValidator.getValidationViolations(
+ CustomAudienceFixture.getValidBuilderForBuyer(CommonFixture.VALID_BUYER)
+ .setExpirationTime(
+ CustomAudienceFixture
+ .INVALID_BEFORE_DELAYED_EXPIRATION_TIME)
+ .setActivationTime(
+ CustomAudienceFixture.VALID_DELAYED_ACTIVATION_TIME)
+ .build()),
+ String.format(
+ CustomAudienceTimestampValidator.VIOLATION_EXPIRE_BEFORE_ACTIVATION,
+ CustomAudienceFixture.VALID_DELAYED_ACTIVATION_TIME,
+ CustomAudienceFixture.INVALID_BEFORE_DELAYED_EXPIRATION_TIME));
+ }
+
+ @Test
+ public void testInvalidAdData() {
+ AdData invalidAdDataWithAnotherBuyer =
+ new AdData.Builder()
+ .setRenderUri(AdDataFixture.getValidRenderUrlByBuyer(ANOTHER_BUYER, 1))
+ .setMetadata("{\"a\":1}")
+ .build();
+ AdData invalidAdDataWithInvalidMetadata =
+ new AdData.Builder()
+ .setRenderUri(
+ AdDataFixture.getValidRenderUrlByBuyer(
+ CommonFixture.VALID_BUYER, 2))
+ .setMetadata("not[valid]json")
+ .build();
+ AdData validAdData =
+ new AdData.Builder()
+ .setRenderUri(
+ AdDataFixture.getValidRenderUrlByBuyer(
+ CommonFixture.VALID_BUYER, 3))
+ .setMetadata("{\"a\":1}")
+ .build();
+ ValidatorTestUtil.assertViolationContaiinsOnly(
+ mValidator.getValidationViolations(
+ CustomAudienceFixture.getValidBuilderForBuyer(CommonFixture.VALID_BUYER)
+ .setAds(
+ List.of(
+ invalidAdDataWithAnotherBuyer,
+ invalidAdDataWithInvalidMetadata,
+ validAdData))
+ .build()),
+ String.format(
+ AdDataValidator.VIOLATION_FORMAT,
+ invalidAdDataWithAnotherBuyer,
+ String.format(
+ AdTechUriValidator.IDENTIFIER_AND_URL_ARE_INCONSISTENT,
+ ValidatorUtil.AD_TECH_ROLE_BUYER,
+ CommonFixture.VALID_BUYER,
+ ValidatorUtil.AD_TECH_ROLE_BUYER,
+ AdDataValidator.RENDER_URI_FIELD_NAME,
+ ANOTHER_BUYER)),
+ String.format(
+ AdDataValidator.VIOLATION_FORMAT,
+ invalidAdDataWithInvalidMetadata,
+ String.format(
+ JsonValidator.SHOULD_BE_A_VALID_JSON,
+ AdDataValidator.AD_DATA_CLASS_NAME,
+ AdDataValidator.METADATA_FIELD_NAME)));
+ }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/TrustedBiddingDataValidatorTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/TrustedBiddingDataValidatorTest.java
new file mode 100644
index 0000000..bb8b1d3
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/TrustedBiddingDataValidatorTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.adservices.service.customaudience;
+
+import android.adservices.common.CommonFixture;
+import android.adservices.customaudience.TrustedBiddingDataFixture;
+
+import com.android.adservices.service.common.AdTechUriValidator;
+import com.android.adservices.service.common.ValidatorTestUtil;
+import com.android.adservices.service.common.ValidatorUtil;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TrustedBiddingDataValidatorTest {
+ private final TrustedBiddingDataValidator mValidator =
+ new TrustedBiddingDataValidator(CommonFixture.VALID_BUYER);
+
+ @Test
+ public void testValidTrustedBiddingData() {
+ Assert.assertTrue(
+ mValidator
+ .getValidationViolations(
+ TrustedBiddingDataFixture.getValidTrustedBiddingDataByBuyer(
+ CommonFixture.VALID_BUYER))
+ .isEmpty());
+ }
+
+ @Test
+ public void testInvalidUri() {
+ String buyer = "b.com";
+ ValidatorTestUtil.assertViolationContaiinsOnly(
+ mValidator.getValidationViolations(
+ TrustedBiddingDataFixture.getValidTrustedBiddingDataByBuyer(buyer)),
+ String.format(
+ AdTechUriValidator.IDENTIFIER_AND_URL_ARE_INCONSISTENT,
+ ValidatorUtil.AD_TECH_ROLE_BUYER,
+ CommonFixture.VALID_BUYER,
+ ValidatorUtil.AD_TECH_ROLE_BUYER,
+ TrustedBiddingDataValidator.TRUSTED_BIDDING_URI_FIELD_NAME,
+ buyer));
+ }
+}