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