From ebcdae736155e62dc0465c803537de1a0ff2eb64 Mon Sep 17 00:00:00 2001 From: jianglai Date: Fri, 10 Mar 2017 11:05:02 -0800 Subject: [PATCH] Return all applicable reserved list entries associated with a label Instead of only returning the most severe one, return all applicable ones. This is because the reserved list has grown to a list of types that are not strictly comparable but orthogonal to each other. We can no longer depend on the fact that the most severe type incorporates all properties of those beneath it. Therefore returning all of them and treat them one by one in the calling site is the correct behavior. Due to constraint imposed in eppcom.xsd, during domain checks the response can only contain a reservation reason of fewer than 32 characters, therefore we are returning the message for the type with highest severity, in case of multiple reservation types for a label. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=149776106 --- .../flows/domain/DomainAllocateFlow.java | 36 ++++---- .../flows/domain/DomainCheckFlow.java | 10 ++- .../flows/domain/DomainFlowUtils.java | 18 ++-- .../StaticPremiumListPricingEngine.java | 4 +- .../model/registry/label/ReservationType.java | 22 ++++- .../model/registry/label/ReservedList.java | 86 ++++++++++++------- .../flows/domain/DomainCheckFlowTest.java | 31 +++++++ .../registry/label/ReservedListTest.java | 78 ++++++++++++----- 8 files changed, 199 insertions(+), 86 deletions(-) diff --git a/java/google/registry/flows/domain/DomainAllocateFlow.java b/java/google/registry/flows/domain/DomainAllocateFlow.java index 51deff155..19e0eee6d 100644 --- a/java/google/registry/flows/domain/DomainAllocateFlow.java +++ b/java/google/registry/flows/domain/DomainAllocateFlow.java @@ -21,7 +21,7 @@ import static google.registry.flows.ResourceFlowUtils.verifyResourceDoesNotExist import static google.registry.flows.domain.DomainFlowUtils.cloneAndLinkReferences; import static google.registry.flows.domain.DomainFlowUtils.createFeeCreateResponse; import static google.registry.flows.domain.DomainFlowUtils.failfastForCreate; -import static google.registry.flows.domain.DomainFlowUtils.getReservationType; +import static google.registry.flows.domain.DomainFlowUtils.getReservationTypes; import static google.registry.flows.domain.DomainFlowUtils.prepareMarkedLrpTokenEntity; import static google.registry.flows.domain.DomainFlowUtils.validateCreateCommandContactsAndNameservers; import static google.registry.flows.domain.DomainFlowUtils.validateDomainName; @@ -85,6 +85,7 @@ import google.registry.model.registry.Registry; import google.registry.model.registry.label.ReservationType; import google.registry.model.reporting.HistoryEntry; import google.registry.tmch.LordnTask; +import java.util.Set; import javax.inject.Inject; import org.joda.time.DateTime; @@ -169,7 +170,7 @@ public class DomainAllocateFlow implements TransactionalFlow { .addGracePeriod(createGracePeriod( isSunrushAddGracePeriod, getOnly(billsAndPolls, BillingEvent.OneTime.class))) // Names on the collision list will not be delegated. Set server hold. - .setStatusValues(ReservationType.NAME_COLLISION == getReservationType(domainName) + .setStatusValues(getReservationTypes(domainName).contains(ReservationType.NAME_COLLISION) ? ImmutableSet.of(StatusValue.SERVER_HOLD) : ImmutableSet.of()) .setRegistrant(command.getRegistrant()) @@ -241,7 +242,7 @@ public class DomainAllocateFlow implements TransactionalFlow { BillingEvent.OneTime oneTimeBillingEvent = createOneTimeBillingEvent( application, historyEntry, isSunrushAddGracePeriod, registry, now, years); PollMessage.OneTime oneTimePollMessage = - createOneTimePollMessage(application, historyEntry, getReservationType(domainName), now); + createOneTimePollMessage(application, historyEntry, getReservationTypes(domainName), now); // Create a new autorenew billing event and poll message starting at the expiration time. BillingEvent.Recurring autorenewBillingEvent = createAutorenewBillingEvent(historyEntry, registrationExpirationTime); @@ -336,23 +337,26 @@ public class DomainAllocateFlow implements TransactionalFlow { private PollMessage.OneTime createOneTimePollMessage( DomainApplication application, HistoryEntry historyEntry, - ReservationType reservationType, + Set reservationTypes, DateTime now) { return new PollMessage.OneTime.Builder() .setClientId(historyEntry.getClientId()) .setEventTime(now) - .setMsg(reservationType == ReservationType.NAME_COLLISION - ? COLLISION_MESSAGE // Remind the registrar of the name collision policy. - : "Domain was allocated") - .setResponseData(ImmutableList.of( - DomainPendingActionNotificationResponse.create( - targetId, true, application.getCreationTrid(), now))) - .setResponseExtensions(ImmutableList.of( - new LaunchInfoResponseExtension.Builder() - .setApplicationId(application.getForeignKey()) - .setPhase(application.getPhase()) - .setApplicationStatus(ApplicationStatus.ALLOCATED) - .build())) + .setMsg( + reservationTypes.contains(ReservationType.NAME_COLLISION) + ? COLLISION_MESSAGE // Remind the registrar of the name collision policy. + : "Domain was allocated") + .setResponseData( + ImmutableList.of( + DomainPendingActionNotificationResponse.create( + targetId, true, application.getCreationTrid(), now))) + .setResponseExtensions( + ImmutableList.of( + new LaunchInfoResponseExtension.Builder() + .setApplicationId(application.getForeignKey()) + .setPhase(application.getPhase()) + .setApplicationStatus(ApplicationStatus.ALLOCATED) + .build())) .setParent(historyEntry) .build(); } diff --git a/java/google/registry/flows/domain/DomainCheckFlow.java b/java/google/registry/flows/domain/DomainCheckFlow.java index e42d27af5..5f85cc1ec 100644 --- a/java/google/registry/flows/domain/DomainCheckFlow.java +++ b/java/google/registry/flows/domain/DomainCheckFlow.java @@ -17,7 +17,7 @@ package google.registry.flows.domain; import static google.registry.flows.FlowUtils.validateClientIsLoggedIn; import static google.registry.flows.ResourceFlowUtils.verifyTargetIdCount; import static google.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld; -import static google.registry.flows.domain.DomainFlowUtils.getReservationType; +import static google.registry.flows.domain.DomainFlowUtils.getReservationTypes; import static google.registry.flows.domain.DomainFlowUtils.handleFeeRequest; import static google.registry.flows.domain.DomainFlowUtils.validateDomainName; import static google.registry.flows.domain.DomainFlowUtils.validateDomainNameWithIdnTables; @@ -25,6 +25,7 @@ import static google.registry.flows.domain.DomainFlowUtils.verifyNotInPredelegat import static google.registry.model.EppResourceUtils.checkResourcesExist; import static google.registry.model.index.DomainApplicationIndex.loadActiveApplicationsByDomainName; import static google.registry.model.registry.label.ReservationType.UNRESERVED; +import static google.registry.model.registry.label.ReservationType.getTypeOfHighestSeverity; import static google.registry.pricing.PricingEngineProxy.isDomainPremium; import com.google.common.base.Predicate; @@ -179,14 +180,15 @@ public final class DomainCheckFlow implements Flow { }})) { return "Pending allocation"; } - ReservationType reservationType = getReservationType(domainName); - if (reservationType == UNRESERVED + ImmutableSet reservationTypes = getReservationTypes(domainName); + if (reservationTypes.equals(ImmutableSet.of(UNRESERVED)) && isDomainPremium(domainName.toString(), now) && registry.getPremiumPriceAckRequired() && eppInput.getSingleExtension(FeeCheckCommandExtension.class) == null) { return "Premium names require EPP ext."; } - return reservationType.getMessageForCheck(); + + return getTypeOfHighestSeverity(reservationTypes).getMessageForCheck(); } /** Handle the fee check extension. */ diff --git a/java/google/registry/flows/domain/DomainFlowUtils.java b/java/google/registry/flows/domain/DomainFlowUtils.java index a3bc1b92b..1705c9c49 100644 --- a/java/google/registry/flows/domain/DomainFlowUtils.java +++ b/java/google/registry/flows/domain/DomainFlowUtils.java @@ -19,6 +19,7 @@ import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Predicates.equalTo; import static com.google.common.collect.Iterables.any; import static com.google.common.collect.Sets.difference; +import static com.google.common.collect.Sets.intersection; import static com.google.common.collect.Sets.union; import static google.registry.flows.domain.DomainPricingLogic.getMatchingLrpToken; import static google.registry.model.EppResourceUtils.loadByForeignKey; @@ -26,7 +27,6 @@ import static google.registry.model.domain.DomainResource.MAX_REGISTRATION_YEARS import static google.registry.model.domain.DomainResource.extendRegistrationWithCap; import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.model.registry.Registries.findTldForName; -import static google.registry.model.registry.label.ReservedList.getReservation; import static google.registry.pricing.PricingEngineProxy.isDomainPremium; import static google.registry.tldconfig.idn.IdnLabelValidator.findValidIdnTableForTld; import static google.registry.util.CollectionUtils.nullToEmpty; @@ -102,6 +102,7 @@ import google.registry.model.registrar.Registrar; import google.registry.model.registry.Registry; import google.registry.model.registry.Registry.TldState; import google.registry.model.registry.label.ReservationType; +import google.registry.model.registry.label.ReservedList; import google.registry.model.reporting.HistoryEntry; import google.registry.model.tmch.ClaimsListShard; import google.registry.util.Idn; @@ -351,16 +352,17 @@ public class DomainFlowUtils { } private static boolean isReserved(InternetDomainName domainName, boolean isSunrise) { - ReservationType type = getReservationType(domainName); - return type == ReservationType.FULLY_BLOCKED - || type == ReservationType.RESERVED_FOR_ANCHOR_TENANT - || (TYPES_ALLOWED_FOR_CREATE_ONLY_IN_SUNRISE.contains(type) && !isSunrise); + ImmutableSet types = getReservationTypes(domainName); + return types.contains(ReservationType.FULLY_BLOCKED) + || types.contains(ReservationType.RESERVED_FOR_ANCHOR_TENANT) + || !(isSunrise || intersection(TYPES_ALLOWED_FOR_CREATE_ONLY_IN_SUNRISE, types).isEmpty()); } - /** Returns an enum that encodes how and when this name is reserved in the current tld. */ - static ReservationType getReservationType(InternetDomainName domainName) { + /** Returns a set of {@link ReservationType}s for the given domain name. */ + static ImmutableSet getReservationTypes(InternetDomainName domainName) { // The TLD should always be the parent of the requested domain name. - return getReservation(domainName.parts().get(0), domainName.parent().toString()); + return ReservedList.getReservationTypes( + domainName.parts().get(0), domainName.parent().toString()); } /** Verifies that a launch extension's specified phase matches the specified registry's phase. */ diff --git a/java/google/registry/model/pricing/StaticPremiumListPricingEngine.java b/java/google/registry/model/pricing/StaticPremiumListPricingEngine.java index fbc2adc18..b284ea1e2 100644 --- a/java/google/registry/model/pricing/StaticPremiumListPricingEngine.java +++ b/java/google/registry/model/pricing/StaticPremiumListPricingEngine.java @@ -19,7 +19,7 @@ import static com.google.common.base.Strings.emptyToNull; import static google.registry.model.registry.Registry.TldState.SUNRISE; import static google.registry.model.registry.label.PremiumListUtils.getPremiumPrice; import static google.registry.model.registry.label.ReservationType.NAME_COLLISION; -import static google.registry.model.registry.label.ReservedList.getReservation; +import static google.registry.model.registry.label.ReservedList.getReservationTypes; import static google.registry.util.DomainNameUtils.getTldFromDomainName; import com.google.common.base.Joiner; @@ -46,7 +46,7 @@ public final class StaticPremiumListPricingEngine implements PremiumPricingEngin Optional premiumPrice = getPremiumPrice(label, registry); boolean isNameCollisionInSunrise = registry.getTldState(priceTime).equals(SUNRISE) - && getReservation(label, tld) == NAME_COLLISION; + && getReservationTypes(label, tld).contains(NAME_COLLISION); String feeClass = emptyToNull(Joiner.on('-').skipNulls().join( premiumPrice.isPresent() ? "premium" : null, isNameCollisionInSunrise ? "collision" : null)); diff --git a/java/google/registry/model/registry/label/ReservationType.java b/java/google/registry/model/registry/label/ReservationType.java index f778f21a3..3b408da2e 100644 --- a/java/google/registry/model/registry/label/ReservationType.java +++ b/java/google/registry/model/registry/label/ReservationType.java @@ -16,13 +16,16 @@ package google.registry.model.registry.label; import static com.google.common.base.Preconditions.checkState; +import java.util.Set; import javax.annotation.Nullable; /** Enum describing reservation on a label in a {@link ReservedList} */ public enum ReservationType { // We explicitly set the severity, even though we have a checkState that makes it equal to the - // ordinal, so that no one accidentally reorders these values and changes the sort order. + // ordinal, so that no one accidentally reorders these values and changes the sort order. If a + // label has multiple reservation types, its message is the that of the one with the highest + // severity. UNRESERVED(null, 0), ALLOWED_IN_SUNRISE("Reserved for non-sunrise", 1), @@ -43,4 +46,21 @@ public enum ReservationType { public String getMessageForCheck() { return messageForCheck; } + + /** + * Returns the {@code ReservationType} with the highest severity, used when a label has multiple + * reservation types and a reservation message is needed. + * @param types the set of reservation types that a label is associated with. + * @return the reservation type with the highest severity. + */ + public static ReservationType getTypeOfHighestSeverity(Set types) { + ReservationType mostSevereType = UNRESERVED; + + for (ReservationType type : types) { + if (type.compareTo(mostSevereType) > 0) { + mostSevereType = type; + } + } + return mostSevereType; + } } diff --git a/java/google/registry/model/registry/label/ReservedList.java b/java/google/registry/model/registry/label/ReservedList.java index 097e9f946..15bbc70f7 100644 --- a/java/google/registry/model/registry/label/ReservedList.java +++ b/java/google/registry/model/registry/label/ReservedList.java @@ -16,6 +16,8 @@ package google.registry.model.registry.label; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.Iterables.getOnlyElement; import static google.registry.config.RegistryConfig.getDomainLabelListCacheDuration; import static google.registry.model.common.EntityGroupRoot.getCrossTldKey; import static google.registry.model.ofy.ObjectifyService.ofy; @@ -26,6 +28,7 @@ import static google.registry.model.registry.label.ReservationType.UNRESERVED; import static google.registry.util.CollectionUtils.nullToEmpty; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Splitter; import com.google.common.cache.CacheBuilder; @@ -33,6 +36,7 @@ import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; import com.google.common.net.InternetDomainName; import com.google.common.util.concurrent.UncheckedExecutionException; import com.googlecode.objectify.Key; @@ -42,9 +46,10 @@ import com.googlecode.objectify.annotation.Entity; import com.googlecode.objectify.annotation.Mapify; import com.googlecode.objectify.mapper.Mapper; import google.registry.model.registry.Registry; +import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Objects; +import java.util.Set; import java.util.concurrent.ExecutionException; import javax.annotation.Nullable; @@ -72,9 +77,8 @@ public final class ReservedList ReservationType reservationType; /** - * Contains the auth code necessary to register a domain with this label. - * Note that this field will only ever be populated for entries with type - * RESERVED_FOR_ANCHOR_TENANT. + * Contains the auth code necessary to register a domain with this label. Note that this field + * will only ever be populated for entries with type RESERVED_FOR_ANCHOR_TENANT. */ String authCode; @@ -141,10 +145,10 @@ public final class ReservedList /** * Gets a ReservedList by name using the caching layer. * - * @return An Optional that has a value if a reserved list exists by the given - * name, or absent if not. + * @return An Optional that has a value if a reserved list exists by the given name, + * or absent if not. * @throws UncheckedExecutionException if some other error occurs while trying to load the - * ReservedList from the cache or Datastore. + * ReservedList from the cache or Datastore. */ public static Optional get(String listName) { return getFromCache(listName, cache); @@ -157,53 +161,69 @@ public final class ReservedList /** * Queries the set of all reserved lists associated with the specified tld and returns the - * reservation type of the first occurrence of label seen. If the label is in none of the lists, - * it returns UNRESERVED. + * reservation types of the label. If the label is in none of the lists, it returns a set that + * contains UNRESERVED. */ - public static ReservationType getReservation(String label, String tld) { + public static ImmutableSet getReservationTypes(String label, String tld) { checkNotNull(label, "label"); if (label.length() == 0) { - return FULLY_BLOCKED; + return ImmutableSet.of(FULLY_BLOCKED); } - ReservedListEntry entry = getReservedListEntry(label, tld); - return (entry != null) ? entry.reservationType : UNRESERVED; + ImmutableSet entries = getReservedListEntries(label, tld); + return entries.isEmpty() + ? ImmutableSet.of(UNRESERVED) + : ImmutableSet.copyOf( + Iterables.transform( + entries, + new Function() { + @Override + public ReservationType apply(ReservedListEntry reservedListEntry) { + return reservedListEntry.reservationType; + } + })); } /** - * Returns true if the given label and TLD is reserved for an anchor tenant, and the given - * auth code matches the one set on the reservation. + * Returns true if the given label and TLD is reserved for an anchor tenant, and the given auth + * code matches the one set on the reservation. If there are multiple anchor tenant entries fo + * this label, all the auth codes need to be the same and match the given one, otherwise an + * exception is thrown. */ public static boolean matchesAnchorTenantReservation( InternetDomainName domainName, String authCode) { - ReservedListEntry entry = - getReservedListEntry(domainName.parts().get(0), domainName.parent().toString()); - return entry != null - && entry.reservationType == RESERVED_FOR_ANCHOR_TENANT - && Objects.equals(entry.getAuthCode(), authCode); + ImmutableSet entries = + getReservedListEntries(domainName.parts().get(0), domainName.parent().toString()); + + Set domainAuthCodes = new HashSet<>(); + for (ReservedListEntry entry : entries) { + if (entry.reservationType == RESERVED_FOR_ANCHOR_TENANT) { + domainAuthCodes.add(entry.getAuthCode()); + } + } + checkState( + domainAuthCodes.size() <= 1, "There are conflicting auth codes for domain: %s", domainName); + + return !domainAuthCodes.isEmpty() && getOnlyElement(domainAuthCodes).equals(authCode); } /** - * Helper function to retrieve the entry associated with this label and TLD, or null if no such - * entry exists. + * Helper function to retrieve the entries associated with this label and TLD, or an empty set if + * no such entry exists. */ - @Nullable - private static ReservedListEntry getReservedListEntry(String label, String tld) { + private static ImmutableSet getReservedListEntries(String label, String tld) { Registry registry = Registry.get(checkNotNull(tld, "tld")); ImmutableSet> reservedLists = registry.getReservedLists(); ImmutableSet lists = loadReservedLists(reservedLists); - ReservedListEntry entry = null; + ImmutableSet.Builder entries = new ImmutableSet.Builder<>(); - // Loop through all reservation lists and check each one for the inputted label, and return - // the most severe ReservationType found. + // Loop through all reservation lists and add each of them. for (ReservedList rl : lists) { - Map entries = rl.getReservedListEntries(); - ReservedListEntry nextEntry = entries.get(label); - if (nextEntry != null - && (entry == null || nextEntry.reservationType.compareTo(entry.reservationType) > 0)) { - entry = nextEntry; + if (rl.getReservedListEntries().containsKey(label)) { + entries.add(rl.getReservedListEntries().get(label)); } } - return entry; + + return entries.build(); } private static ImmutableSet loadReservedLists( diff --git a/javatests/google/registry/flows/domain/DomainCheckFlowTest.java b/javatests/google/registry/flows/domain/DomainCheckFlowTest.java index 59ef82d90..b89ff0a8c 100644 --- a/javatests/google/registry/flows/domain/DomainCheckFlowTest.java +++ b/javatests/google/registry/flows/domain/DomainCheckFlowTest.java @@ -115,6 +115,24 @@ public class DomainCheckFlowTest create(true, "example3.tld", null)); } + @Test + public void testSuccess_domainWithMultipleReservationType_useMostSevereMessage() + throws Exception { + persistResource( + Registry.get("tld") + .asBuilder() + .setReservedLists( + createReservedList(), + persistReservedList("tld-collision", "allowedinsunrise,NAME_COLLISION")) + .build()); + setEppInput("domain_check_one_tld_reserved.xml"); + doCheckTest( + create(false, "reserved.tld", "Reserved"), + create(false, "allowedinsunrise.tld", "Cannot be delegated"), + create(true, "example2.tld", null), + create(true, "example3.tld", null)); + } + @Test public void testSuccess_anchorTenantReserved() throws Exception { setEppInput("domain_check_anchor.xml"); @@ -398,6 +416,19 @@ public class DomainCheckFlowTest runFlowAssertResponse(readFile("domain_check_fee_response_v06.xml")); } + @Test + public void testFeeExtension_multipleReservations() throws Exception { + persistResource( + Registry.get("tld") + .asBuilder() + .setReservedLists( + persistReservedList("example-sunrise", "allowedinsunrise,ALLOWED_IN_SUNRISE")) + .build()); + persistActiveDomain("example1.tld"); + setEppInput("domain_check_fee_v06.xml"); + runFlowAssertResponse(readFile("domain_check_fee_response_v06.xml")); + } + @Test public void testFeeExtension_v11() throws Exception { persistActiveDomain("example1.tld"); diff --git a/javatests/google/registry/model/registry/label/ReservedListTest.java b/javatests/google/registry/model/registry/label/ReservedListTest.java index 3940819ff..ff7f8b189 100644 --- a/javatests/google/registry/model/registry/label/ReservedListTest.java +++ b/javatests/google/registry/model/registry/label/ReservedListTest.java @@ -18,9 +18,10 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static google.registry.model.registry.label.ReservationType.ALLOWED_IN_SUNRISE; import static google.registry.model.registry.label.ReservationType.FULLY_BLOCKED; +import static google.registry.model.registry.label.ReservationType.NAME_COLLISION; import static google.registry.model.registry.label.ReservationType.RESERVED_FOR_ANCHOR_TENANT; import static google.registry.model.registry.label.ReservationType.UNRESERVED; -import static google.registry.model.registry.label.ReservedList.getReservation; +import static google.registry.model.registry.label.ReservedList.getReservationTypes; import static google.registry.model.registry.label.ReservedList.matchesAnchorTenantReservation; import static google.registry.testing.DatastoreHelper.createTld; import static google.registry.testing.DatastoreHelper.persistReservedList; @@ -71,20 +72,20 @@ public class ReservedListTest { @Test public void testGetReservation_allLabelsAreUnreserved_withNoReservedLists() throws Exception { assertThat(Registry.get("tld").getReservedLists()).isEmpty(); - assertThat(getReservation("doodle", "tld")).isEqualTo(UNRESERVED); - assertThat(getReservation("access", "tld")).isEqualTo(UNRESERVED); - assertThat(getReservation("rich", "tld")).isEqualTo(UNRESERVED); + assertThat(getReservationTypes("doodle", "tld")).containsExactly(UNRESERVED); + assertThat(getReservationTypes("access", "tld")).containsExactly(UNRESERVED); + assertThat(getReservationTypes("rich", "tld")).containsExactly(UNRESERVED); } @Test public void testZeroReservedLists_doesNotCauseError() throws Exception { - assertThat(getReservation("doodle", "tld")).isEqualTo(UNRESERVED); + assertThat(getReservationTypes("doodle", "tld")).containsExactly(UNRESERVED); } @Test public void testGetReservation_twoLetterCodesAreAvailable() { for (String sld : ImmutableList.of("aa", "az", "zz", "91", "1n", "j5")) { - assertThat(getReservation(sld, "tld")).isEqualTo(UNRESERVED); + assertThat(getReservationTypes(sld, "tld")).containsExactly(UNRESERVED); } } @@ -92,7 +93,7 @@ public class ReservedListTest { public void testGetReservation_singleCharacterDomainsAreAllowed() { // This isn't quite exhaustive but it's close. for (char c = 'a'; c <= 'z'; c++) { - assertThat(getReservation("" + c, "tld")).isEqualTo(UNRESERVED); + assertThat(getReservationTypes("" + c, "tld")).containsExactly(UNRESERVED); } } @@ -107,8 +108,8 @@ public class ReservedListTest { "lol,RESERVED_FOR_ANCHOR_TENANT,foobar1", "lol2,RESERVED_FOR_ANCHOR_TENANT,abcdefg # This is a comment"))) .build()); - assertThat(getReservation("lol", "tld")).isEqualTo(RESERVED_FOR_ANCHOR_TENANT); - assertThat(getReservation("lol2", "tld")).isEqualTo(RESERVED_FOR_ANCHOR_TENANT); + assertThat(getReservationTypes("lol", "tld")).containsExactly(RESERVED_FOR_ANCHOR_TENANT); + assertThat(getReservationTypes("lol2", "tld")).containsExactly(RESERVED_FOR_ANCHOR_TENANT); assertThat(matchesAnchorTenantReservation(InternetDomainName.from("lol.tld"), "foobar1")) .isTrue(); assertThat(matchesAnchorTenantReservation(InternetDomainName.from("lol.tld"), "foobar")) @@ -139,6 +140,21 @@ public class ReservedListTest { assertThat(matchesAnchorTenantReservation(InternetDomainName.from("lol5.tld"), "")).isFalse(); } + @Test + public void testMatchesAnchorTenantReservation_duplicatingAuthCodes() + throws Exception { + ReservedList rl1 = persistReservedList("reserved1", "lol,RESERVED_FOR_ANCHOR_TENANT,foo"); + ReservedList rl2 = persistReservedList("reserved2", "lol,RESERVED_FOR_ANCHOR_TENANT,foo"); + createTld("tld"); + persistResource(Registry.get("tld").asBuilder().setReservedLists(rl1, rl2).build()); + assertThat(matchesAnchorTenantReservation(InternetDomainName.from("lol.tld"), "foo")).isTrue(); + assertThat(matchesAnchorTenantReservation(InternetDomainName.from("lol.tld"), "bar")).isFalse(); + persistReservedList("reserved2", "lol,RESERVED_FOR_ANCHOR_TENANT,bar"); + thrown.expect( + IllegalStateException.class, "There are conflicting auth codes for domain: lol.tld"); + matchesAnchorTenantReservation(InternetDomainName.from("lol.tld"), "bar"); + } + @Test public void testGetReservation_concatsMultipleListsCorrectly() throws Exception { ReservedList rl1 = persistReservedList( @@ -152,13 +168,30 @@ public class ReservedListTest { createTld("tld"); persistResource(Registry.get("tld").asBuilder().setReservedLists(rl1, rl2).build()); - assertThat(getReservation("lol", "tld")).isEqualTo(FULLY_BLOCKED); - assertThat(getReservation("cat", "tld")).isEqualTo(FULLY_BLOCKED); - assertThat(getReservation("roflcopter", "tld")).isEqualTo(FULLY_BLOCKED); - assertThat(getReservation("snowcrash", "tld")).isEqualTo(FULLY_BLOCKED); - assertThat(getReservation("doge", "tld")).isEqualTo(UNRESERVED); + assertThat(getReservationTypes("lol", "tld")).containsExactly(FULLY_BLOCKED); + assertThat(getReservationTypes("cat", "tld")).containsExactly(FULLY_BLOCKED); + assertThat(getReservationTypes("roflcopter", "tld")).containsExactly(FULLY_BLOCKED); + assertThat(getReservationTypes("snowcrash", "tld")).containsExactly(FULLY_BLOCKED); + assertThat(getReservationTypes("doge", "tld")).containsExactly(UNRESERVED); } + @Test + public void testGetReservation_returnsAllReservationTypesFromMultipleListsForTheSameLabel() + throws Exception { + ReservedList rl1 = + persistReservedList("reserved1", "lol,NAME_COLLISION # yup", "cat,FULLY_BLOCKED"); + ReservedList rl2 = + persistReservedList("reserved2", "lol,ALLOWED_IN_SUNRISE", "snowcrash,FULLY_BLOCKED"); + createTld("tld"); + persistResource(Registry.get("tld").asBuilder().setReservedLists(rl1, rl2).build()); + + assertThat(getReservationTypes("lol", "tld")) + .containsExactly(NAME_COLLISION, ALLOWED_IN_SUNRISE); + assertThat(getReservationTypes("cat", "tld")).containsExactly(FULLY_BLOCKED); + assertThat(getReservationTypes("snowcrash", "tld")).containsExactly(FULLY_BLOCKED); + } + + @Test public void testGetReservation_worksAfterReservedListRemovedUsingSet() throws Exception { ReservedList rl1 = persistReservedList( @@ -167,23 +200,24 @@ public class ReservedListTest { "reserved2", "roflcopter,FULLY_BLOCKED", "snowcrash,FULLY_BLOCKED"); createTld("tld"); persistResource(Registry.get("tld").asBuilder().setReservedLists(rl1, rl2).build()); - assertThat(getReservation("roflcopter", "tld")).isEqualTo(FULLY_BLOCKED); + assertThat(getReservationTypes("roflcopter", "tld")).containsExactly(FULLY_BLOCKED); persistResource(Registry.get("tld").asBuilder().setReservedLists(rl1).build()); assertWithMessage( - "roflcopter.tld should be unreserved after unsetting the registry's second reserved list") - .that(getReservation("roflcopter", "tld")) - .isEqualTo(UNRESERVED); + "roflcopter.tld should be unreserved" + + " after unsetting the registry's second reserved list") + .that(getReservationTypes("roflcopter", "tld")) + .containsExactly(UNRESERVED); } @Test - public void testGetReservation_combinesMultipleLists_handlesSeverityCorrectly() throws Exception { + public void testGetReservation_combinesMultipleLists() throws Exception { ReservedList rl1 = persistReservedList( "reserved1", "lol,NAME_COLLISION", "roflcopter,ALLOWED_IN_SUNRISE"); ReservedList rl2 = persistReservedList("reserved2", "lol,FULLY_BLOCKED"); createTld("tld"); persistResource(Registry.get("tld").asBuilder().setReservedLists(rl1, rl2).build()); - assertThat(getReservation("lol", "tld")).isEqualTo(FULLY_BLOCKED); - assertThat(getReservation("roflcopter", "tld")).isEqualTo(ALLOWED_IN_SUNRISE); + assertThat(getReservationTypes("lol", "tld")).containsExactly(FULLY_BLOCKED, NAME_COLLISION); + assertThat(getReservationTypes("roflcopter", "tld")).containsExactly(ALLOWED_IN_SUNRISE); } @Test @@ -191,7 +225,7 @@ public class ReservedListTest { ReservedList rl = persistReservedList("tld-reserved", "lol,FULLY_BLOCKED # yup"); createTld("tld"); persistResource(Registry.get("tld").asBuilder().setReservedLists(rl).build()); - assertThat(getReservation("lol", "tld")).isEqualTo(FULLY_BLOCKED); + assertThat(getReservationTypes("lol", "tld")).containsExactly(FULLY_BLOCKED); } @Test