diff --git a/java/google/registry/rdap/RdapActionBase.java b/java/google/registry/rdap/RdapActionBase.java index 77c7322b2..0cf11abe0 100644 --- a/java/google/registry/rdap/RdapActionBase.java +++ b/java/google/registry/rdap/RdapActionBase.java @@ -37,6 +37,7 @@ import google.registry.request.Action; import google.registry.request.HttpException; import google.registry.request.HttpException.BadRequestException; import google.registry.request.HttpException.NotFoundException; +import google.registry.request.HttpException.UnprocessableEntityException; import google.registry.request.RequestMethod; import google.registry.request.RequestPath; import google.registry.request.Response; @@ -170,8 +171,9 @@ public abstract class RdapActionBase implements Runnable { * @param clazz the type of resource to be queried * @param filterField the database field of interest * @param partialStringQuery the details of the search string; if there is no wildcard, an - * equality query is used; if there is a wildcard, a range query is used instead; there - * should not be a search suffix + * equality query is used; if there is a wildcard, a range query is used instead; the + * initial string should not be empty, and any search suffix will be ignored, so the caller + * must filter the results if a suffix is specified * @param resultSetMaxSize the maximum number of results to return * @return the results of the query */ @@ -180,6 +182,13 @@ public abstract class RdapActionBase implements Runnable { String filterField, RdapSearchPattern partialStringQuery, int resultSetMaxSize) { + if (partialStringQuery.getInitialString().length() + < RdapSearchPattern.MIN_INITIAL_STRING_LENGTH) { + throw new UnprocessableEntityException( + String.format( + "Initial search string must be at least %d characters", + RdapSearchPattern.MIN_INITIAL_STRING_LENGTH)); + } if (!partialStringQuery.getHasWildcard()) { return ofy().load() .type(clazz) @@ -187,7 +196,7 @@ public abstract class RdapActionBase implements Runnable { .filter("deletionTime", END_OF_TIME) .limit(resultSetMaxSize); } else { - checkArgument(partialStringQuery.getSuffix() == null, "Unexpected search string suffix"); + // Ignore the suffix; the caller will need to filter on the suffix, if any. return ofy().load() .type(clazz) .filter(filterField + " >=", partialStringQuery.getInitialString()) diff --git a/java/google/registry/rdap/RdapDomainSearchAction.java b/java/google/registry/rdap/RdapDomainSearchAction.java index 9ac3ed2a6..03cf31c05 100644 --- a/java/google/registry/rdap/RdapDomainSearchAction.java +++ b/java/google/registry/rdap/RdapDomainSearchAction.java @@ -39,10 +39,12 @@ import google.registry.rdap.RdapJsonFormatter.OutputDataType; import google.registry.request.Action; import google.registry.request.HttpException.BadRequestException; import google.registry.request.HttpException.NotFoundException; +import google.registry.request.HttpException.UnprocessableEntityException; import google.registry.request.Parameter; import google.registry.request.auth.Auth; import google.registry.request.auth.AuthLevel; import google.registry.util.Clock; +import google.registry.util.FormattingLogger; import google.registry.util.Idn; import java.net.InetAddress; import java.util.ArrayList; @@ -72,6 +74,8 @@ public class RdapDomainSearchAction extends RdapActionBase { public static final int RESULT_SET_SIZE_SCALING_FACTOR = 30; + private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass(); + @Inject Clock clock; @Inject @Parameter("name") Optional nameParam; @Inject @Parameter("nsLdhName") Optional nsLdhNameParam; @@ -89,7 +93,11 @@ public class RdapDomainSearchAction extends RdapActionBase { return PATH; } - /** Parses the parameters and calls the appropriate search function. */ + /** + * Parses the parameters and calls the appropriate search function. + * + *

The RDAP spec allows for domain search by domain name, nameserver name or nameserver IP. + */ @Override public ImmutableMap getJsonObjectForResource( String pathSearchString, boolean isHeadRequest, String linkBase) { @@ -142,7 +150,15 @@ public class RdapDomainSearchAction extends RdapActionBase { return builder.build(); } - /** Searches for domains by domain name, returning a JSON array of domain info maps. */ + /** + * Searches for domains by domain name, returning a JSON array of domain info maps. + * + *

Domain query strings with wildcards are allowed to have a suffix after the wildcard, which + * must be a TLD. If the TLD is not present, the wildcard must be preceded by at least two + * characters (e.g. "ex*"), to avoid queries for all domains in the system. If the TLD is present, + * the initial string is not required (e.g. "*.tld" is valid), because the search will be + * restricted to a single TLD. + */ private RdapSearchResults searchByDomainName( final RdapSearchPattern partialStringQuery, final DateTime now) { // Handle queries without a wildcard -- just load by foreign key. @@ -152,16 +168,40 @@ public class RdapDomainSearchAction extends RdapActionBase { ImmutableList results = (domainResource == null) ? ImmutableList.of() : ImmutableList.of(domainResource); - return makeSearchResults(results, false, now); - // Handle queries with a wildcard. + return makeSearchResults(results, false /* isTruncated */, now); + // Handle queries with a wildcard and no initial string. + } else if (partialStringQuery.getInitialString().isEmpty()) { + if (partialStringQuery.getSuffix() == null) { + throw new UnprocessableEntityException( + "Initial search string is required for wildcard domain searches without a TLD suffix"); + } + // Since we aren't searching on fullyQualifiedDomainName, we can perform our one allowed + // inequality query on deletion time. + Query query = ofy().load() + .type(DomainResource.class) + .filter("tld", partialStringQuery.getSuffix()) + .filter("deletionTime >", now) + .limit(rdapResultSetMaxSize + 1); + return makeSearchResults(query.list(), false /* isTruncated */, now); + // Handle queries with a wildcard and an initial string. } else { + if ((partialStringQuery.getSuffix() == null) + && (partialStringQuery.getInitialString().length() + < RdapSearchPattern.MIN_INITIAL_STRING_LENGTH)) { + throw new UnprocessableEntityException( + String.format( + "Initial search string must be at least %d characters for wildcard domain searches" + + " without a TLD suffix", + RdapSearchPattern.MIN_INITIAL_STRING_LENGTH)); + } + // We can't query for undeleted domains as part of the query itself; that would require an // inequality query on deletion time, and we are already using inequality queries on // fullyQualifiedDomainName. So we instead pick an arbitrary limit of // RESULT_SET_SIZE_SCALING_FACTOR times the result set size limit, fetch up to that many, and // weed out all deleted domains. If there still isn't a full result set's worth of domains, we // give up and return just the ones we found. - // TODO(b/31546493): Add metrics to figure out how well this. + // TODO(b/31546493): Add metrics to figure out how well this works. List domainList = new ArrayList<>(); Query query = ofy().load() .type(DomainResource.class) @@ -175,16 +215,21 @@ public class RdapDomainSearchAction extends RdapActionBase { query.limit(RESULT_SET_SIZE_SCALING_FACTOR * rdapResultSetMaxSize)) { if (EppResourceUtils.isActive(domain, now)) { if (domainList.size() >= rdapResultSetMaxSize) { - return makeSearchResults(ImmutableList.copyOf(domainList), true, now); + return makeSearchResults(ImmutableList.copyOf(domainList), true /* isTruncated */, now); } domainList.add(domain); } } - return makeSearchResults(ImmutableList.copyOf(domainList), false, now); + return makeSearchResults(domainList, false /* isTruncated */, now); } } - /** Searches for domains by nameserver name, returning a JSON array of domain info maps. */ + /** + * Searches for domains by nameserver name, returning a JSON array of domain info maps. + * + *

This is a two-step process: get a list of host references by host name, and then look up + * domains by host reference. + */ private RdapSearchResults searchByNameserverLdhName( final RdapSearchPattern partialStringQuery, final DateTime now) { Iterable> hostKeys = getNameserverRefsByLdhName(partialStringQuery, now); @@ -194,7 +239,16 @@ public class RdapDomainSearchAction extends RdapActionBase { return searchByNameserverRefs(hostKeys, now); } - /** Assembles a list of {@link HostResource} keys by name. */ + /** + * Assembles a list of {@link HostResource} keys by name. + * + *

Nameserver query strings with wildcards are allowed to have a suffix after the wildcard, + * which must be a domain. If the domain is not specified, or is not an existing domain in one of + * our TLDs, the wildcard must be preceded by at least two characters (e.g. "ns*"), to avoid + * queries for all nameservers in the system. If the suffix specifies an existing domain, the + * initial string is not required (e.g. "*.example.tld" is valid), because we can look up the + * domain and just list all of its subordinate hosts. + */ private Iterable> getNameserverRefsByLdhName( final RdapSearchPattern partialStringQuery, final DateTime now) { // Handle queries without a wildcard; just load the host by foreign key in the usual way. @@ -206,40 +260,63 @@ public class RdapDomainSearchAction extends RdapActionBase { } else { return ImmutableList.of(hostKey); } - // Handle queries with a wildcard, but no suffix. Query the host resources themselves, rather - // than the foreign key index, because then we have an index on fully qualified host name and - // deletion time, so we can check the deletion status in the query itself. There are no pending - // deletes for hosts, so we can call queryUndeleted. - } else if (partialStringQuery.getSuffix() == null) { - // TODO (b/24463238): figure out how to limit the size of these queries effectively - return queryUndeleted(HostResource.class, "fullyQualifiedHostName", partialStringQuery, 1000) - .keys(); - // Handle queries with a wildcard and a suffix. In this case, it is more efficient to do things - // differently. We use the suffix to look up the domain, then loop through the subordinate hosts - // looking for matches. - // TODO(mountford): This might not be ok; it will only find nameservers on domains we control + // Handle queries with a wildcard. } else { - DomainResource domainResource = loadByForeignKey( - DomainResource.class, partialStringQuery.getSuffix(), now); - if (domainResource == null) { - throw new NotFoundException("No domain found for specified nameserver suffix"); - } - ImmutableList.Builder> builder = new ImmutableList.Builder<>(); - for (String fqhn : ImmutableSortedSet.copyOf(domainResource.getSubordinateHosts())) { - // We can't just check that the host name starts with the initial query string, because then - // the query ns.exam*.example.com would match against nameserver ns.example.com. - if (partialStringQuery.matches(fqhn)) { - Key hostKey = loadAndGetKey(HostResource.class, fqhn, now); - if (hostKey != null) { - builder.add(hostKey); + // If there is a suffix, it must be a domain. If it happens to be a domain that we manage, + // we can look up the domain and look through the subordinate hosts. This is more efficient, + // and lets us permit wildcard searches with no initial string. + if (partialStringQuery.getSuffix() != null) { + DomainResource domainResource = loadByForeignKey( + DomainResource.class, partialStringQuery.getSuffix(), now); + if (domainResource != null) { + ImmutableList.Builder> builder = new ImmutableList.Builder<>(); + for (String fqhn : ImmutableSortedSet.copyOf(domainResource.getSubordinateHosts())) { + // We can't just check that the host name starts with the initial query string, because + // then the query ns.exam*.example.com would match against nameserver ns.example.com. + if (partialStringQuery.matches(fqhn)) { + Key hostKey = loadAndGetKey(HostResource.class, fqhn, now); + if (hostKey != null) { + builder.add(hostKey); + } else { + logger.warningfmt("Host key unexpectedly null"); + } + } } + return builder.build(); } } - return builder.build(); + // If there's no suffix, or it isn't a domain we manage, query the host resources. Query the + // resources themselves, rather than the foreign key indexes, because then we have an index on + // fully qualified host name and deletion time, so we can check the deletion status in the + // query itself. There are no pending deletes for hosts, so we can call queryUndeleted. In + // this case, the initial string must be present, to avoid querying every host in the system. + // This restriction is enforced by queryUndeleted(). + // TODO (b/24463238): figure out how to limit the size of these queries effectively + Iterable> keys = + queryUndeleted(HostResource.class, "fullyQualifiedHostName", partialStringQuery, 1000) + .keys(); + // queryUndeleted() ignores suffixes, so if one was specified, we must filter on the partial + // string query. + if (partialStringQuery.getSuffix() == null) { + return keys; + } else { + ImmutableList.Builder> filteredKeys = new ImmutableList.Builder<>(); + for (Key key : keys) { + if (partialStringQuery.matches(key.getName())) { + filteredKeys.add(key); + } + } + return filteredKeys.build(); + } } } - /** Searches for domains by nameserver address, returning a JSON array of domain info maps. */ + /** + * Searches for domains by nameserver address, returning a JSON array of domain info maps. + * + *

This is a two-step process: get a list of host references by IP address, and then look up + * domains by host reference. + */ private RdapSearchResults searchByNameserverIp( final InetAddress inetAddress, final DateTime now) { // In theory, we could filter on the deletion time being in the future. But we can't do that in @@ -280,13 +357,13 @@ public class RdapDomainSearchAction extends RdapActionBase { .limit(rdapResultSetMaxSize + 1)) { if (!domains.contains(domain)) { if (domains.size() >= rdapResultSetMaxSize) { - return makeSearchResults(ImmutableList.copyOf(domains), true, now); + return makeSearchResults(ImmutableList.copyOf(domains), true /* isTruncated */, now); } domains.add(domain); } } } - return makeSearchResults(ImmutableList.copyOf(domains), false, now); + return makeSearchResults(ImmutableList.copyOf(domains), false /* isTruncated */, now); } /** @@ -296,7 +373,7 @@ public class RdapDomainSearchAction extends RdapActionBase { * list, meaning that the truncation notice should be added. */ private RdapSearchResults makeSearchResults( - ImmutableList domains, boolean isTruncated, DateTime now) { + List domains, boolean isTruncated, DateTime now) { OutputDataType outputDataType = (domains.size() > 1) ? OutputDataType.SUMMARY : OutputDataType.FULL; ImmutableList.Builder> jsonBuilder = new ImmutableList.Builder<>(); diff --git a/java/google/registry/rdap/RdapEntitySearchAction.java b/java/google/registry/rdap/RdapEntitySearchAction.java index 483eb7dcf..d3a928280 100644 --- a/java/google/registry/rdap/RdapEntitySearchAction.java +++ b/java/google/registry/rdap/RdapEntitySearchAction.java @@ -136,9 +136,17 @@ public class RdapEntitySearchAction extends RdapActionBase { * assume that entity names are regular unicode. */ private RdapSearchResults searchByName(final RdapSearchPattern partialStringQuery, DateTime now) { - // Don't allow suffixes in entity name search queries. - if (!partialStringQuery.getHasWildcard() && (partialStringQuery.getSuffix() != null)) { - throw new UnprocessableEntityException("Suffixes not allowed in entity name searches"); + // For wildcard searches, make sure the initial string is long enough, and don't allow suffixes. + if (partialStringQuery.getHasWildcard()) { + if (partialStringQuery.getSuffix() != null) { + throw new UnprocessableEntityException( + "Suffixes not allowed in wildcard entity name searches"); + } + if (partialStringQuery.getInitialString().length() + < RdapSearchPattern.MIN_INITIAL_STRING_LENGTH) { + throw new UnprocessableEntityException( + "Initial search string required in wildcard entity name searches"); + } } // Get the registrar matches, depending on whether there's a wildcard. ImmutableList registrarMatches = @@ -183,6 +191,11 @@ public class RdapEntitySearchAction extends RdapActionBase { // wildcard searches for registrars, by simply not searching for registrars if a wildcard is // present. Fetch an extra contact to detect result set truncation. } else if (partialStringQuery.getSuffix() == null) { + if (partialStringQuery.getInitialString().length() + < RdapSearchPattern.MIN_INITIAL_STRING_LENGTH) { + throw new UnprocessableEntityException( + "Initial search string required in wildcard entity handle searches"); + } return makeSearchResults( ofy().load() .type(ContactResource.class) diff --git a/java/google/registry/rdap/RdapNameserverSearchAction.java b/java/google/registry/rdap/RdapNameserverSearchAction.java index c8689732a..50a34f805 100644 --- a/java/google/registry/rdap/RdapNameserverSearchAction.java +++ b/java/google/registry/rdap/RdapNameserverSearchAction.java @@ -80,7 +80,11 @@ public class RdapNameserverSearchAction extends RdapActionBase { return PATH; } - /** Parses the parameters and calls the appropriate search function. */ + /** + * Parses the parameters and calls the appropriate search function. + * + *

The RDAP spec allows nameserver search by either name or IP address. + */ @Override public ImmutableMap getJsonObjectForResource( String pathSearchString, boolean isHeadRequest, String linkBase) { @@ -135,39 +139,51 @@ public class RdapNameserverSearchAction extends RdapActionBase { ImmutableList.of( rdapJsonFormatter.makeRdapJsonForHost( hostResource, false, rdapLinkBase, rdapWhoisServer, now, OutputDataType.FULL))); - // Handle queries with a wildcard, but no suffix. There are no pending deletes for hosts, so we - // can call queryUndeleted. - } else if (partialStringQuery.getSuffix() == null) { - return makeSearchResults( - // Add 1 so we can detect truncation. - queryUndeleted( - HostResource.class, - "fullyQualifiedHostName", - partialStringQuery, - rdapResultSetMaxSize + 1) - .list(), - now); - // Handle queries with a wildcard and a suffix. In this case, it is more efficient to do things - // differently. We use the suffix to look up the domain, then loop through the subordinate hosts - // looking for matches. + // Handle queries with a wildcard. } else { - DomainResource domainResource = - loadByForeignKey(DomainResource.class, partialStringQuery.getSuffix(), now); - if (domainResource == null) { - throw new NotFoundException("No domain found for specified nameserver suffix"); - } - ImmutableList.Builder hostListBuilder = new ImmutableList.Builder<>(); - for (String fqhn : ImmutableSortedSet.copyOf(domainResource.getSubordinateHosts())) { - // We can't just check that the host name starts with the initial query string, because then - // the query ns.exam*.example.com would match against nameserver ns.example.com. - if (partialStringQuery.matches(fqhn)) { - HostResource hostResource = loadByForeignKey(HostResource.class, fqhn, now); - if (hostResource != null) { - hostListBuilder.add(hostResource); + // If there is a suffix, it should be a domain. If it happens to be a domain that we manage, + // we can look up the domain and look through the subordinate hosts. This is more efficient, + // and lets us permit wildcard searches with no initial string. + if (partialStringQuery.getSuffix() != null) { + DomainResource domainResource = + loadByForeignKey(DomainResource.class, partialStringQuery.getSuffix(), now); + ImmutableList.Builder hostListBuilder = new ImmutableList.Builder<>(); + if (domainResource != null) { + for (String fqhn : ImmutableSortedSet.copyOf(domainResource.getSubordinateHosts())) { + // We can't just check that the host name starts with the initial query string, because + // then the query ns.exam*.example.com would match against nameserver ns.example.com. + if (partialStringQuery.matches(fqhn)) { + HostResource hostResource = loadByForeignKey(HostResource.class, fqhn, now); + if (hostResource != null) { + hostListBuilder.add(hostResource); + } + } + } + } else { + // If we don't recognize the domain, call queryUndeleted and filter. + // TODO(mountford): figure out how to size this correctly + for (HostResource hostResource : + queryUndeleted( + HostResource.class, "fullyQualifiedHostName", partialStringQuery, 1000)) { + if (partialStringQuery.matches(hostResource.getFullyQualifiedHostName())) { + hostListBuilder.add(hostResource); + } } } + return makeSearchResults(hostListBuilder.build(), now); + // Handle queries with a wildcard, but no suffix. There are no pending deletes for hosts, so + // we can call queryUndeleted. + } else { + return makeSearchResults( + // Add 1 so we can detect truncation. + queryUndeleted( + HostResource.class, + "fullyQualifiedHostName", + partialStringQuery, + rdapResultSetMaxSize + 1) + .list(), + now); } - return makeSearchResults(hostListBuilder.build(), now); } } diff --git a/java/google/registry/rdap/RdapSearchPattern.java b/java/google/registry/rdap/RdapSearchPattern.java index acd8174f2..cc4211bf0 100644 --- a/java/google/registry/rdap/RdapSearchPattern.java +++ b/java/google/registry/rdap/RdapSearchPattern.java @@ -30,6 +30,8 @@ import javax.annotation.Nullable; */ public final class RdapSearchPattern { + static final int MIN_INITIAL_STRING_LENGTH = 2; + /** String before the wildcard character. */ private final String initialString; @@ -113,9 +115,6 @@ public final class RdapSearchPattern { suffix = null; } initialString = pattern.substring(0, wildcardPos); - if (initialString.length() < 2) { - throw new UnprocessableEntityException("At least two characters must be specified"); - } if (initialString.startsWith(ACE_PREFIX) && (initialString.length() < 7)) { throw new UnprocessableEntityException( "At least seven characters must be specified for punycode domain searches"); diff --git a/javatests/google/registry/rdap/RdapDomainSearchActionTest.java b/javatests/google/registry/rdap/RdapDomainSearchActionTest.java index 7447635b9..913ee7c78 100644 --- a/javatests/google/registry/rdap/RdapDomainSearchActionTest.java +++ b/javatests/google/registry/rdap/RdapDomainSearchActionTest.java @@ -380,11 +380,29 @@ public class RdapDomainSearchActionTest { assertThat(response.getStatus()).isEqualTo(422); } + @Test + public void testNoCharactersToMatch_rejected() throws Exception { + assertThat(generateActualJson(RequestType.NAME, "*")) + .isEqualTo( + generateExpectedJson( + "Initial search string is required for wildcard domain searches without a TLD" + + " suffix", + null, + null, + "rdap_error_422.json")); + assertThat(response.getStatus()).isEqualTo(422); + } + @Test public void testFewerThanTwoCharactersToMatch_rejected() throws Exception { assertThat(generateActualJson(RequestType.NAME, "a*")) - .isEqualTo(generateExpectedJson( - "At least two characters must be specified", null, null, "rdap_error_422.json")); + .isEqualTo( + generateExpectedJson( + "Initial search string must be at least 2 characters for wildcard domain searches" + + " without a TLD suffix", + null, + null, + "rdap_error_422.json")); assertThat(response.getStatus()).isEqualTo(422); } @@ -454,6 +472,18 @@ public class RdapDomainSearchActionTest { assertThat(response.getStatus()).isEqualTo(200); } + @Test + public void testDomainMatch_cstar_lol_found() throws Exception { + generateActualJson(RequestType.NAME, "c*.lol"); + assertThat(response.getStatus()).isEqualTo(200); + } + + @Test + public void testDomainMatch_star_lol_found() throws Exception { + generateActualJson(RequestType.NAME, "*.lol"); + assertThat(response.getStatus()).isEqualTo(200); + } + @Test public void testDomainMatch_cat_star_found() throws Exception { generateActualJson(RequestType.NAME, "cat.*"); @@ -511,7 +541,7 @@ public class RdapDomainSearchActionTest { assertThat(response.getStatus()).isEqualTo(404); } - // todo (b/27378695): reenable or delete this test + // TODO(b/27378695): reenable or delete this test @Ignore @Test public void testDomainMatchDomainInTestTld_notFound() throws Exception { @@ -714,7 +744,23 @@ public class RdapDomainSearchActionTest { } @Test - public void testNameserverMatchWithWildcardAndDomainSuffix_found() throws Exception { + public void testNameserverMatchWithNoPrefixWildcardAndDomainSuffix_found() throws Exception { + assertThat(generateActualJson(RequestType.NS_LDH_NAME, "*.cat.lol")) + .isEqualTo(generateExpectedJson("rdap_multiple_domains.json")); + assertThat(response.getStatus()).isEqualTo(200); + } + + @Test + public void testNameserverMatchWithOneCharacterPrefixWildcardAndDomainSuffix_found() + throws Exception { + assertThat(generateActualJson(RequestType.NS_LDH_NAME, "n*.cat.lol")) + .isEqualTo(generateExpectedJson("rdap_multiple_domains.json")); + assertThat(response.getStatus()).isEqualTo(200); + } + + @Test + public void testNameserverMatchWithTwoCharacterPrefixWildcardAndDomainSuffix_found() + throws Exception { assertThat(generateActualJson(RequestType.NS_LDH_NAME, "ns*.cat.lol")) .isEqualTo(generateExpectedJson("rdap_multiple_domains.json")); assertThat(response.getStatus()).isEqualTo(200); @@ -828,7 +874,7 @@ public class RdapDomainSearchActionTest { hostNs1CatLol.asBuilder().setDeletionTime(clock.nowUtc().minusDays(1)).build()); assertThat(generateActualJson(RequestType.NS_LDH_NAME, "ns1.cat*.lol")) .isEqualTo(generateExpectedJson( - "No domain found for specified nameserver suffix", null, null, "rdap_error_404.json")); + "No matching nameservers found", null, null, "rdap_error_404.json")); assertThat(response.getStatus()).isEqualTo(404); } diff --git a/javatests/google/registry/rdap/RdapEntitySearchActionTest.java b/javatests/google/registry/rdap/RdapEntitySearchActionTest.java index 3b948dd4c..fac699dd7 100644 --- a/javatests/google/registry/rdap/RdapEntitySearchActionTest.java +++ b/javatests/google/registry/rdap/RdapEntitySearchActionTest.java @@ -254,12 +254,23 @@ public class RdapEntitySearchActionTest { assertThat(response.getStatus()).isEqualTo(422); } + @Test + public void testNoCharactersToMatch_rejected() throws Exception { + assertThat(generateActualJsonWithHandle("*")) + .isEqualTo( + generateExpectedJson( + "Initial search string required in wildcard entity handle searches", + "rdap_error_422.json")); + assertThat(response.getStatus()).isEqualTo(422); + } + @Test public void testFewerThanTwoCharactersToMatch_rejected() throws Exception { assertThat(generateActualJsonWithHandle("a*")) .isEqualTo( generateExpectedJson( - "At least two characters must be specified", "rdap_error_422.json")); + "Initial search string required in wildcard entity handle searches", + "rdap_error_422.json")); assertThat(response.getStatus()).isEqualTo(422); } diff --git a/javatests/google/registry/rdap/RdapNameserverSearchActionTest.java b/javatests/google/registry/rdap/RdapNameserverSearchActionTest.java index 8a611962a..0d8301dc8 100644 --- a/javatests/google/registry/rdap/RdapNameserverSearchActionTest.java +++ b/javatests/google/registry/rdap/RdapNameserverSearchActionTest.java @@ -240,9 +240,7 @@ public class RdapNameserverSearchActionTest { @Test public void testNonexistentDomainSuffix_notFound() throws Exception { assertThat(generateActualJsonWithName("exam*.foo.bar")) - .isEqualTo( - generateExpectedJson( - "No domain found for specified nameserver suffix", "rdap_error_404.json")); + .isEqualTo(generateExpectedJson("No nameservers found", "rdap_error_404.json")); assertThat(response.getStatus()).isEqualTo(404); } @@ -253,12 +251,21 @@ public class RdapNameserverSearchActionTest { assertThat(response.getStatus()).isEqualTo(422); } + @Test + public void testNoCharactersToMatch_rejected() throws Exception { + assertThat(generateActualJsonWithName("*")) + .isEqualTo( + generateExpectedJson( + "Initial search string must be at least 2 characters", "rdap_error_422.json")); + assertThat(response.getStatus()).isEqualTo(422); + } + @Test public void testFewerThanTwoCharactersToMatch_rejected() throws Exception { assertThat(generateActualJsonWithName("a*")) .isEqualTo( generateExpectedJson( - "At least two characters must be specified", "rdap_error_422.json")); + "Initial search string must be at least 2 characters", "rdap_error_422.json")); assertThat(response.getStatus()).isEqualTo(422); } @@ -336,6 +343,18 @@ public class RdapNameserverSearchActionTest { assertThat(response.getStatus()).isEqualTo(200); } + @Test + public void testNameMatch_nstar_cat_lol_found() throws Exception { + generateActualJsonWithName("n*.cat.lol"); + assertThat(response.getStatus()).isEqualTo(200); + } + + @Test + public void testNameMatch_star_cat_lol_found() throws Exception { + generateActualJsonWithName("*.cat.lol"); + assertThat(response.getStatus()).isEqualTo(200); + } + @Test public void testNameMatch_nsstar_found() throws Exception { generateActualJsonWithName("ns*"); diff --git a/javatests/google/registry/rdap/RdapSearchPatternTest.java b/javatests/google/registry/rdap/RdapSearchPatternTest.java index e4bca0155..bdf708fad 100644 --- a/javatests/google/registry/rdap/RdapSearchPatternTest.java +++ b/javatests/google/registry/rdap/RdapSearchPatternTest.java @@ -80,12 +80,6 @@ public class RdapSearchPatternTest { assertThat(rdapSearchPattern.getSuffix()).isNull(); } - @Test - public void testPrefixTooShort_unprocessable() throws Exception { - thrown.expect(UnprocessableEntityException.class); - RdapSearchPattern.create("e*", true); - } - @Test public void testZeroLengthSuffix_unprocessable() throws Exception { thrown.expect(UnprocessableEntityException.class);