diff --git a/core/src/main/java/google/registry/rdap/AbstractJsonableObject.java b/core/src/main/java/google/registry/rdap/AbstractJsonableObject.java index 983d9bc98d7..95994b2683e 100644 --- a/core/src/main/java/google/registry/rdap/AbstractJsonableObject.java +++ b/core/src/main/java/google/registry/rdap/AbstractJsonableObject.java @@ -336,7 +336,7 @@ private static JsonElement toJsonElement(String name, Member member, Object obje // According to RFC 9083 section 3, the syntax of dates and times is defined in RFC3339. // // According to RFC3339, we should use ISO8601, which is what DateTime.toString does! - return new JsonPrimitive(((DateTime) object).toString()); + return new JsonPrimitive(object.toString()); } if (object == null) { return JsonNull.INSTANCE; diff --git a/core/src/main/java/google/registry/rdap/RdapActionBase.java b/core/src/main/java/google/registry/rdap/RdapActionBase.java index 66389eb18a7..4ccc734e461 100644 --- a/core/src/main/java/google/registry/rdap/RdapActionBase.java +++ b/core/src/main/java/google/registry/rdap/RdapActionBase.java @@ -24,10 +24,14 @@ import static jakarta.servlet.http.HttpServletResponse.SC_OK; import static java.nio.charset.StandardCharsets.UTF_8; +import com.google.common.collect.Streams; import com.google.common.flogger.FluentLogger; import com.google.common.net.MediaType; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import google.registry.config.RegistryConfig.Config; import google.registry.model.EppResource; import google.registry.model.registrar.Registrar; @@ -41,6 +45,7 @@ import google.registry.request.Parameter; import google.registry.request.RequestMethod; import google.registry.request.RequestPath; +import google.registry.request.RequestUrl; import google.registry.request.Response; import google.registry.util.Clock; import jakarta.inject.Inject; @@ -75,6 +80,7 @@ protected enum DeletedItemHandling { @Inject Response response; @Inject @RequestMethod Action.Method requestMethod; @Inject @RequestPath String requestPath; + @Inject @RequestUrl String requestUrl; @Inject RdapAuthorization rdapAuthorization; @Inject RdapJsonFormatter rdapJsonFormatter; @Inject @Parameter("includeDeleted") Optional includeDeletedParam; @@ -198,7 +204,9 @@ void setPayload(ReplyPayloadBase replyObject) { TopLevelReplyObject topLevelObject = TopLevelReplyObject.create(replyObject, rdapJsonFormatter.createTosNotice()); Gson gson = formatOutputParam.orElse(false) ? FORMATTED_OUTPUT_GSON : GSON; - response.setPayload(gson.toJson(topLevelObject.toJson())); + JsonObject jsonResult = topLevelObject.toJson(); + addLinkValuesRecursively(jsonResult); + response.setPayload(gson.toJson(jsonResult)); } /** @@ -264,4 +272,34 @@ DateTime getRequestTime() { return rdapJsonFormatter.getRequestTime(); } + /** + * Adds a request-referencing "value" to each link object. + * + *

This is the "context URI" as described in RFC 8288. Basically, this contains a reference to + * the request URL that generated this RDAP response. + * + *

This is required per the RDAP February 2024 response profile sections 2.6.3 and 2.10, and + * the technical implementation guide sections 3.2 and 3.3.2. + * + *

We must do this here (instead of where the links are generated) because many of the links + * (e.g. terms of service) are static constants, and thus cannot by default know what the request + * URL was. + */ + private void addLinkValuesRecursively(JsonElement jsonElement) { + if (jsonElement instanceof JsonArray jsonArray) { + jsonArray.forEach(this::addLinkValuesRecursively); + } else if (jsonElement instanceof JsonObject jsonObject) { + if (jsonObject.get("links") instanceof JsonArray linksArray) { + addLinkValues(linksArray); + } + jsonObject.entrySet().forEach(entry -> addLinkValuesRecursively(entry.getValue())); + } + } + + private void addLinkValues(JsonArray linksArray) { + Streams.stream(linksArray) + .map(JsonElement::getAsJsonObject) + .filter(o -> !o.has("value")) + .forEach(o -> o.addProperty("value", requestUrl)); + } } diff --git a/core/src/main/java/google/registry/rdap/RdapIcannStandardInformation.java b/core/src/main/java/google/registry/rdap/RdapIcannStandardInformation.java index 3b8e4a70065..c1b3479b348 100644 --- a/core/src/main/java/google/registry/rdap/RdapIcannStandardInformation.java +++ b/core/src/main/java/google/registry/rdap/RdapIcannStandardInformation.java @@ -44,7 +44,7 @@ public class RdapIcannStandardInformation { + " https://icann.org/epp") .addLink( Link.builder() - .setRel("alternate") + .setRel("glossary") .setHref("https://icann.org/epp") .setType("text/html") .build()) @@ -57,7 +57,7 @@ public class RdapIcannStandardInformation { .setDescription("URL of the ICANN RDDS Inaccuracy Complaint Form: https://icann.org/wicf") .addLink( Link.builder() - .setRel("alternate") + .setRel("help") .setHref("https://icann.org/wicf") .setType("text/html") .build()) diff --git a/core/src/main/java/google/registry/rdap/RdapJsonFormatter.java b/core/src/main/java/google/registry/rdap/RdapJsonFormatter.java index d79056b4137..61208f40a42 100644 --- a/core/src/main/java/google/registry/rdap/RdapJsonFormatter.java +++ b/core/src/main/java/google/registry/rdap/RdapJsonFormatter.java @@ -271,7 +271,7 @@ Notice createTosNotice() { URI htmlUri = htmlBaseURI.resolve(rdapTosStaticUrl); noticeBuilder.addLink( Link.builder() - .setRel("alternate") + .setRel("terms-of-service") .setHref(htmlUri.toString()) .setType("text/html") .build()); diff --git a/core/src/main/java/google/registry/rdap/RdapSearchActionBase.java b/core/src/main/java/google/registry/rdap/RdapSearchActionBase.java index 8b4d65b0b60..2daa836c869 100644 --- a/core/src/main/java/google/registry/rdap/RdapSearchActionBase.java +++ b/core/src/main/java/google/registry/rdap/RdapSearchActionBase.java @@ -31,7 +31,6 @@ import google.registry.request.HttpException.UnprocessableEntityException; import google.registry.request.Parameter; import google.registry.request.ParameterMap; -import google.registry.request.RequestUrl; import jakarta.inject.Inject; import jakarta.persistence.criteria.CriteriaBuilder; import java.io.UnsupportedEncodingException; @@ -54,7 +53,6 @@ public abstract class RdapSearchActionBase extends RdapActionBase { private static final int RESULT_SET_SIZE_SCALING_FACTOR = 30; - @Inject @RequestUrl String requestUrl; @Inject @ParameterMap ImmutableListMultimap parameterMap; @Inject @Parameter("cursor") Optional cursorTokenParam; @Inject @Parameter("registrar") Optional registrarParam; diff --git a/core/src/test/java/google/registry/rdap/RdapActionBaseTest.java b/core/src/test/java/google/registry/rdap/RdapActionBaseTest.java index 588dafdd688..d5dd9c90b74 100644 --- a/core/src/test/java/google/registry/rdap/RdapActionBaseTest.java +++ b/core/src/test/java/google/registry/rdap/RdapActionBaseTest.java @@ -90,12 +90,6 @@ void testRuntimeException_returns500Error() { assertThat(response.getStatus()).isEqualTo(500); } - @Test - void testValidName_works() { - assertThat(generateActualJson("no.thing")).isEqualTo(loadJsonFile("rdapjson_toplevel.json")); - assertThat(response.getStatus()).isEqualTo(200); - } - @Test void testContentType_rdapjson_utf8() { generateActualJson("no.thing"); diff --git a/core/src/test/java/google/registry/rdap/RdapActionBaseTestCase.java b/core/src/test/java/google/registry/rdap/RdapActionBaseTestCase.java index b278046be11..a7232101bf1 100644 --- a/core/src/test/java/google/registry/rdap/RdapActionBaseTestCase.java +++ b/core/src/test/java/google/registry/rdap/RdapActionBaseTestCase.java @@ -22,7 +22,12 @@ import static google.registry.request.Action.Method.HEAD; import static org.mockito.Mockito.mock; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import google.registry.model.console.User; import google.registry.model.console.UserRoles; import google.registry.persistence.transaction.JpaTestExtensions; @@ -35,6 +40,7 @@ import google.registry.util.TypeUtils; import java.util.HashMap; import java.util.Optional; +import javax.annotation.Nullable; import org.joda.time.DateTime; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.RegisterExtension; @@ -43,6 +49,7 @@ abstract class RdapActionBaseTestCase { protected final FakeClock clock = new FakeClock(DateTime.parse("2000-01-01TZ")); + static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(); @RegisterExtension final JpaIntegrationTestExtension jpa = @@ -107,18 +114,13 @@ void loginAsAdmin() { metricRole = ADMINISTRATOR; } - JsonObject generateActualJson(String domainName) { - action.requestPath = actionPath + domainName; - action.requestMethod = GET; - action.run(); - return RdapTestHelper.parseJsonObject(response.getPayload()); + JsonObject generateActualJson(String name) { + return RdapTestHelper.parseJsonObject(runAction(name)); } - String generateHeadPayload(String domainName) { - action.requestPath = actionPath + domainName; + String generateHeadPayload(String name) { action.requestMethod = HEAD; - action.run(); - return response.getPayload(); + return runAction(name); } JsonObject generateExpectedJsonError(String description, int code) { @@ -138,16 +140,135 @@ JsonObject generateExpectedJsonError(String description, int code) { "TITLE", title, "CODE", - String.valueOf(code)); + String.valueOf(code), + "REQUEST_URL", + action.requestUrl); + } + + JsonFileBuilder jsonFileBuilder() { + return new JsonFileBuilder(action.requestUrl); + } + + private String runAction(String name) { + action.requestPath = actionPath + name; + action.requestUrl = "https://example.tld" + actionPath + name; + action.run(); + return response.getPayload(); } - static JsonFileBuilder jsonFileBuilder() { - return new JsonFileBuilder(); + JsonElement createTosNotice() { + return JsonParser.parseString( +""" +{ + "title": "RDAP Terms of Service", + "description": [ + "By querying our Domain Database, you are agreeing to comply with these terms so please read \ +them carefully.", + "Any information provided is 'as is' without any guarantee of accuracy.", + "Please do not misuse the Domain Database. It is intended solely for query-based access.", + "Don't use the Domain Database to allow, enable, or otherwise support the transmission of mass \ +unsolicited, commercial advertising or solicitations.", + "Don't access our Domain Database through the use of high volume, automated electronic \ +processes that send queries or data to the systems of any ICANN-accredited registrar.", + "You may only use the information contained in the Domain Database for lawful purposes.", + "Do not compile, repackage, disseminate, or otherwise use the information contained in the \ +Domain Database in its entirety, or in any substantial portion, without our prior written \ +permission.", + "We may retain certain details about queries to our Domain Database for the purposes of \ +detecting and preventing misuse.", + "We reserve the right to restrict or deny your access to the database if we suspect that you \ +have failed to comply with these terms.", + "We reserve the right to modify this agreement at any time." + ], + "links": [ + { + "rel": "self", + "href": "https://example.tld/rdap/help/tos", + "type": "application/rdap+json", + "value": "%REQUEST_URL%" + }, + { + "rel": "terms-of-service", + "href": "https://www.example.tld/about/rdap/tos.html", + "type": "text/html", + "value": "%REQUEST_URL%" + } + ] +} +""" + .replaceAll("%REQUEST_URL%", action.requestUrl)); + } + + JsonObject addPermanentBoilerplateNotices(JsonObject jsonObject) { + if (!jsonObject.has("notices")) { + jsonObject.add("notices", new JsonArray()); + } + JsonArray notices = jsonObject.getAsJsonArray("notices"); + notices.add(createTosNotice()); + notices.add( + JsonParser.parseString( +""" +{ + "description": [ + "This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars \ +version 1.0" + ] +} +""")); + return jsonObject; + } + + JsonObject addDomainBoilerplateNotices(JsonObject jsonObject) { + addPermanentBoilerplateNotices(jsonObject); + JsonArray notices = jsonObject.getAsJsonArray("notices"); + notices.add( + JsonParser.parseString( +""" +{ + "title": "Status Codes", + "description": [ + "For more information on domain status codes, please visit https://icann.org/epp" + ], + "links": [ + { + "rel": "glossary", + "href": "https://icann.org/epp", + "type": "text/html", + "value": "%REQUEST_URL%" + } + ] +} +""" + .replaceAll("%REQUEST_URL%", action.requestUrl))); + notices.add( + JsonParser.parseString( +""" +{ + "title": "RDDS Inaccuracy Complaint Form", + "description": [ + "URL of the ICANN RDDS Inaccuracy Complaint Form: https://icann.org/wicf" + ], + "links": [ + { + "rel": "help", + "href": "https://icann.org/wicf", + "type": "text/html", + "value": "%REQUEST_URL%" + } + ] +} +""" + .replaceAll("%REQUEST_URL%", action.requestUrl))); + return jsonObject; } protected static final class JsonFileBuilder { private final HashMap substitutions = new HashMap<>(); + private JsonFileBuilder(String requestUrl) { + substitutions.put("REQUEST_URL", requestUrl); + } + public JsonObject load(String filename) { return RdapTestHelper.loadJsonFile(filename, substitutions); } @@ -158,6 +279,14 @@ public JsonFileBuilder put(String key, String value) { return this; } + public JsonFileBuilder putAll(String... keysAndValues) { + checkArgument(keysAndValues.length % 2 == 0); + for (int i = 0; i < keysAndValues.length; i += 2) { + put(keysAndValues[i], keysAndValues[i + 1]); + } + return this; + } + public JsonFileBuilder put(String key, int index, String value) { return put(String.format("%s%d", key, index), value); } @@ -189,10 +318,38 @@ JsonFileBuilder addRegistrar(String fullName) { return putNext("REGISTRAR_FULL_NAME_", fullName); } + JsonFileBuilder addFullRegistrar( + String handle, @Nullable String fullName, String status, @Nullable String address) { + if (fullName != null) { + putNext("REGISTRAR_FULLNAME_", fullName); + } + if (address != null) { + putNext("REGISTRAR_ADDRESS_", address); + } + return putNext("REGISTRAR_HANDLE_", handle, "STATUS_", status); + } + JsonFileBuilder addContact(String handle) { return putNext("CONTACT_HANDLE_", handle); } + JsonFileBuilder addFullContact( + String handle, + @Nullable String status, + @Nullable String fullName, + @Nullable String address) { + if (fullName != null) { + putNext("CONTACT_FULLNAME_", fullName); + } + if (address != null) { + putNext("CONTACT_ADDRESS_", address); + } + if (status != null) { + putNext("STATUS_", status); + } + return putNext("CONTACT_HANDLE_", handle); + } + JsonFileBuilder setNextQuery(String nextQuery) { return put("NEXT_QUERY", nextQuery); } diff --git a/core/src/test/java/google/registry/rdap/RdapDataStructuresTest.java b/core/src/test/java/google/registry/rdap/RdapDataStructuresTest.java index 33b069f0e65..6d742dcb2e3 100644 --- a/core/src/test/java/google/registry/rdap/RdapDataStructuresTest.java +++ b/core/src/test/java/google/registry/rdap/RdapDataStructuresTest.java @@ -59,9 +59,12 @@ void testLink() { .setRel("myRel") .setTitle("myTitle") .setType("myType") + .setValue("myValue") .build(); assertThat(link.toJson()) - .isEqualTo(createJson("{'href':'myHref','rel':'myRel','title':'myTitle','type':'myType'}")); + .isEqualTo( + createJson( + "{'href':'myHref','rel':'myRel','title':'myTitle','type':'myType','value':'myValue'}")); assertRestrictedNames(link, "links[]"); } diff --git a/core/src/test/java/google/registry/rdap/RdapDomainActionTest.java b/core/src/test/java/google/registry/rdap/RdapDomainActionTest.java index 4ea3d648ab2..81f86946b96 100644 --- a/core/src/test/java/google/registry/rdap/RdapDomainActionTest.java +++ b/core/src/test/java/google/registry/rdap/RdapDomainActionTest.java @@ -230,16 +230,11 @@ void beforeEach() { clock.nowUtc().minusMonths(6))); } - private JsonObject addBoilerplate(JsonObject obj) { - RdapTestHelper.addDomainBoilerplateNotices(obj, "https://example.tld/rdap/"); - return obj; - } - private void assertProperResponseForCatLol(String queryString, String expectedOutputFile) { assertAboutJson() .that(generateActualJson(queryString)) .isEqualTo( - addBoilerplate( + addDomainBoilerplateNotices( jsonFileBuilder() .addDomain("cat.lol", "C-LOL") .addContact("4-ROID") @@ -357,7 +352,7 @@ void testIdnDomain_works() { assertAboutJson() .that(generateActualJson("cat.みんな")) .isEqualTo( - addBoilerplate( + addDomainBoilerplateNotices( jsonFileBuilder() .addDomain("cat.みんな", "1D-Q9JYB4C") .addContact("19-ROID") @@ -376,7 +371,7 @@ void testIdnDomainWithPercentEncoding_works() { assertAboutJson() .that(generateActualJson("cat.%E3%81%BF%E3%82%93%E3%81%AA")) .isEqualTo( - addBoilerplate( + addDomainBoilerplateNotices( jsonFileBuilder() .addDomain("cat.みんな", "1D-Q9JYB4C") .addContact("19-ROID") @@ -395,7 +390,7 @@ void testPunycodeDomain_works() { assertAboutJson() .that(generateActualJson("cat.xn--q9jyb4c")) .isEqualTo( - addBoilerplate( + addDomainBoilerplateNotices( jsonFileBuilder() .addDomain("cat.みんな", "1D-Q9JYB4C") .addContact("19-ROID") @@ -414,7 +409,7 @@ void testMultilevelDomain_works() { assertAboutJson() .that(generateActualJson("cat.1.tld")) .isEqualTo( - addBoilerplate( + addDomainBoilerplateNotices( jsonFileBuilder() .addDomain("cat.1.tld", "25-1_TLD") .addContact("21-ROID") @@ -473,7 +468,7 @@ void testDeletedDomain_works_loggedInAsCorrectRegistrar() { assertAboutJson() .that(generateActualJson("dodo.lol")) .isEqualTo( - addBoilerplate( + addDomainBoilerplateNotices( jsonFileBuilder() .addDomain("dodo.lol", "15-LOL") .addContact("11-ROID") @@ -493,7 +488,7 @@ void testDeletedDomain_works_loggedInAsAdmin() { assertAboutJson() .that(generateActualJson("dodo.lol")) .isEqualTo( - addBoilerplate( + addDomainBoilerplateNotices( jsonFileBuilder() .addDomain("dodo.lol", "15-LOL") .addContact("11-ROID") @@ -512,7 +507,9 @@ void testAddGracePeriod() { "addgraceperiod", "lol", clock.nowUtc(), clock.nowUtc().plusYears(1)); assertAboutJson() .that(generateActualJson("addgraceperiod.lol")) - .isEqualTo(addBoilerplate(jsonFileBuilder().load("rdap_domain_add_grace_period.json"))); + .isEqualTo( + addDomainBoilerplateNotices( + jsonFileBuilder().load("rdap_domain_add_grace_period.json"))); } @Test @@ -522,7 +519,8 @@ void testAutoRenewGracePeriod() { assertAboutJson() .that(generateActualJson("autorenew.lol")) .isEqualTo( - addBoilerplate(jsonFileBuilder().load("rdap_domain_auto_renew_grace_period.json"))); + addDomainBoilerplateNotices( + jsonFileBuilder().load("rdap_domain_auto_renew_grace_period.json"))); } @Test @@ -545,7 +543,7 @@ void testRedemptionGracePeriod() { assertAboutJson() .that(generateActualJson("redemption.lol")) .isEqualTo( - addBoilerplate( + addDomainBoilerplateNotices( jsonFileBuilder().load("rdap_domain_pending_delete_redemption_grace_period.json"))); } @@ -568,7 +566,8 @@ void testRenewGracePeriod() { assertAboutJson() .that(generateActualJson("renew.lol")) .isEqualTo( - addBoilerplate(jsonFileBuilder().load("rdap_domain_explicit_renew_grace_period.json"))); + addDomainBoilerplateNotices( + jsonFileBuilder().load("rdap_domain_explicit_renew_grace_period.json"))); } @Test @@ -590,7 +589,8 @@ void testTransferGracePeriod() { assertAboutJson() .that(generateActualJson("transfer.lol")) .isEqualTo( - addBoilerplate(jsonFileBuilder().load("rdap_domain_transfer_grace_period.json"))); + addDomainBoilerplateNotices( + jsonFileBuilder().load("rdap_domain_transfer_grace_period.json"))); } @Test @@ -631,12 +631,15 @@ void testBlockedByBsa() { "rel", "alternate", "type", - "text/html"))); + "text/html", + "value", + "https://example.tld/rdap/domain/example.lol"))); + JsonObject actuaResponse = generateActualJson("example.lol"); JsonObject expectedErrorResponse = generateExpectedJsonError("example.lol blocked by BSA", 404); expectedErrorResponse .getAsJsonArray("notices") .add(RdapTestHelper.GSON.toJsonTree(expectedBsaNotice)); - assertAboutJson().that(generateActualJson("example.lol")).isEqualTo(expectedErrorResponse); + assertAboutJson().that(actuaResponse).isEqualTo(expectedErrorResponse); assertThat(response.getStatus()).isEqualTo(404); } diff --git a/core/src/test/java/google/registry/rdap/RdapDomainSearchActionTest.java b/core/src/test/java/google/registry/rdap/RdapDomainSearchActionTest.java index 9ae1e4b78fe..44a059c3e9e 100644 --- a/core/src/test/java/google/registry/rdap/RdapDomainSearchActionTest.java +++ b/core/src/test/java/google/registry/rdap/RdapDomainSearchActionTest.java @@ -473,8 +473,7 @@ private void runSuccessfulTestWithCat2Lol( private JsonObject wrapInSearchReply(JsonObject obj) { obj = RdapTestHelper.wrapInSearchReply("domainSearchResults", obj); - RdapTestHelper.addDomainBoilerplateNotices(obj, "https://example.tld/rdap/"); - return obj; + return addDomainBoilerplateNotices(obj); } private void runSuccessfulTest(RequestType requestType, String queryString, JsonObject expected) { diff --git a/core/src/test/java/google/registry/rdap/RdapEntityActionTest.java b/core/src/test/java/google/registry/rdap/RdapEntityActionTest.java index e28fe9298c0..ec66e681810 100644 --- a/core/src/test/java/google/registry/rdap/RdapEntityActionTest.java +++ b/core/src/test/java/google/registry/rdap/RdapEntityActionTest.java @@ -15,7 +15,6 @@ package google.registry.rdap; import static com.google.common.truth.Truth.assertThat; -import static google.registry.rdap.RdapTestHelper.loadJsonFile; import static google.registry.testing.DatabaseHelper.createTld; import static google.registry.testing.DatabaseHelper.persistResource; import static google.registry.testing.DatabaseHelper.persistSimpleResources; @@ -28,7 +27,6 @@ import static org.mockito.Mockito.verify; import com.google.common.collect.ImmutableList; -import com.google.gson.JsonObject; import google.registry.model.contact.Contact; import google.registry.model.host.Host; import google.registry.model.registrar.Registrar; @@ -39,13 +37,15 @@ import google.registry.request.Action; import google.registry.testing.FullFieldsTestEntityHelper; import java.util.Optional; -import javax.annotation.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; /** Unit tests for {@link RdapEntityAction}. */ class RdapEntityActionTest extends RdapActionBaseTestCase { + private static final String CONTACT_NAME = "(◕‿◕)"; + private static final String CONTACT_ADDRESS = "\"1 Smiley Row\", \"Suite みんな\""; + RdapEntityActionTest() { super(RdapEntityAction.class); } @@ -67,7 +67,7 @@ void beforeEach() { registrant = FullFieldsTestEntityHelper.makeAndPersistContact( "8372808-REG", - "(◕‿◕)", + CONTACT_NAME, "lol@cat.みんな", ImmutableList.of("1 Smiley Row", "Suite みんな"), clock.nowUtc(), @@ -75,7 +75,7 @@ void beforeEach() { adminContact = FullFieldsTestEntityHelper.makeAndPersistContact( "8372808-ADM", - "(◕‿◕)", + CONTACT_NAME, "lol@cat.みんな", ImmutableList.of("1 Smiley Row", "Suite みんな"), clock.nowUtc(), @@ -83,7 +83,7 @@ void beforeEach() { techContact = FullFieldsTestEntityHelper.makeAndPersistContact( "8372808-TEC", - "(◕‿◕)", + CONTACT_NAME, "lol@cat.みんな", ImmutableList.of("1 Smiley Row", "Suite みんな"), clock.nowUtc(), @@ -110,7 +110,7 @@ void beforeEach() { disconnectedContact = FullFieldsTestEntityHelper.makeAndPersistContact( "8372808-DIS", - "(◕‿◕)", + CONTACT_NAME, "lol@cat.みんな", ImmutableList.of("1 Smiley Row", "Suite みんな"), clock.nowUtc(), @@ -123,186 +123,191 @@ void beforeEach() { clock.nowUtc().minusMonths(6)); } - private JsonObject generateExpectedJson( - String handle, - String fullName, - String status, - @Nullable String address, - String expectedOutputFile) { - return loadJsonFile( - expectedOutputFile, - "NAME", handle, - "FULLNAME", fullName, - "ADDRESS", (address == null) ? "\"1 Smiley Row\", \"Suite みんな\"" : address, - "TYPE", "entity", - "STATUS", status); - } - - private JsonObject generateExpectedJsonWithTopLevelEntries( - String handle, - String expectedOutputFile) { - return generateExpectedJsonWithTopLevelEntries( - handle, "(◕‿◕)", "active", null, expectedOutputFile); - } - - private JsonObject generateExpectedJsonWithTopLevelEntries( - String handle, - String fullName, - String status, - String address, - String expectedOutputFile) { - JsonObject obj = generateExpectedJson(handle, fullName, status, address, expectedOutputFile); - RdapTestHelper.addNonDomainBoilerplateNotices(obj, "https://example.tld/rdap/"); - return obj; - } - - private void runSuccessfulHandleTest(String handleQuery, String fileName) { - runSuccessfulHandleTest(handleQuery, "(◕‿◕)", "active", null, fileName); - } - - private void runSuccessfulHandleTest(String handleQuery, String fullName, String fileName) { - runSuccessfulHandleTest(handleQuery, fullName, "active", null, fileName); - } - - private void runSuccessfulHandleTest( - String handleQuery, - String fullName, - String rdapStatus, - String address, - String fileName) { - assertAboutJson() - .that(generateActualJson(handleQuery)) - .isEqualTo( - generateExpectedJsonWithTopLevelEntries( - handleQuery, fullName, rdapStatus, address, fileName)); - assertThat(response.getStatus()).isEqualTo(200); - } - - private void runNotFoundTest(String handleQuery) { - assertAboutJson() - .that(generateActualJson(handleQuery)) - .isEqualTo(generateExpectedJsonError(handleQuery + " not found", 404)); - assertThat(response.getStatus()).isEqualTo(404); - } - @Test void testUnknownEntity_RoidPattern_notFound() { - runNotFoundTest("_MISSING-ENTITY_"); + assertAboutJson() + .that(generateActualJson("_MISSING-ENTITY_")) + .isEqualTo(generateExpectedJsonError("_MISSING-ENTITY_ not found", 404)); + assertThat(response.getStatus()).isEqualTo(404); } @Test void testUnknownEntity_IanaPattern_notFound() { - runNotFoundTest("123"); + assertAboutJson() + .that(generateActualJson("123")) + .isEqualTo(generateExpectedJsonError("123 not found", 404)); + assertThat(response.getStatus()).isEqualTo(404); } @Test void testUnknownEntity_notRoidNotIana_notFound() { // Since we allow search by registrar name, every string is a possible name - runNotFoundTest("some,random,string"); + assertAboutJson() + .that(generateActualJson("some,random,string")) + .isEqualTo(generateExpectedJsonError("some,random,string not found", 404)); + assertThat(response.getStatus()).isEqualTo(404); } @Test void testValidRegistrantContact_works() { login("evilregistrar"); - runSuccessfulHandleTest(registrant.getRepoId(), "rdap_associated_contact.json"); + assertAboutJson() + .that(generateActualJson(registrant.getRepoId())) + .isEqualTo( + addPermanentBoilerplateNotices( + jsonFileBuilder() + .addFullContact(registrant.getRepoId(), null, CONTACT_NAME, CONTACT_ADDRESS) + .load("rdap_associated_contact.json"))); } @Test void testValidRegistrantContact_found_asAdministrator() { loginAsAdmin(); - runSuccessfulHandleTest(registrant.getRepoId(), "rdap_associated_contact.json"); + assertAboutJson() + .that(generateActualJson(registrant.getRepoId())) + .isEqualTo( + addPermanentBoilerplateNotices( + jsonFileBuilder() + .addFullContact(registrant.getRepoId(), null, CONTACT_NAME, CONTACT_ADDRESS) + .load("rdap_associated_contact.json"))); } @Test void testValidRegistrantContact_found_notLoggedIn() { - runSuccessfulHandleTest( - registrant.getRepoId(), - "(◕‿◕)", - "active", - null, - "rdap_associated_contact_no_personal_data.json"); + assertAboutJson() + .that(generateActualJson(registrant.getRepoId())) + .isEqualTo( + addPermanentBoilerplateNotices( + jsonFileBuilder() + .addFullContact(registrant.getRepoId(), "active", CONTACT_NAME, CONTACT_ADDRESS) + .load("rdap_associated_contact_no_personal_data.json"))); } @Test void testValidRegistrantContact_found_loggedInAsOtherRegistrar() { login("otherregistrar"); - runSuccessfulHandleTest( - registrant.getRepoId(), - "(◕‿◕)", - "active", - null, - "rdap_associated_contact_no_personal_data.json"); + assertAboutJson() + .that(generateActualJson(registrant.getRepoId())) + .isEqualTo( + addPermanentBoilerplateNotices( + jsonFileBuilder() + .addFullContact(registrant.getRepoId(), "active", CONTACT_NAME, CONTACT_ADDRESS) + .load("rdap_associated_contact_no_personal_data.json"))); } @Test void testValidAdminContact_works() { login("evilregistrar"); - runSuccessfulHandleTest(adminContact.getRepoId(), "rdap_associated_contact.json"); + assertAboutJson() + .that(generateActualJson(adminContact.getRepoId())) + .isEqualTo( + addPermanentBoilerplateNotices( + jsonFileBuilder() + .addFullContact(adminContact.getRepoId(), null, CONTACT_NAME, CONTACT_ADDRESS) + .load("rdap_associated_contact.json"))); } @Test void testValidTechContact_works() { login("evilregistrar"); - runSuccessfulHandleTest(techContact.getRepoId(), "rdap_associated_contact.json"); + assertAboutJson() + .that(generateActualJson(techContact.getRepoId())) + .isEqualTo( + addPermanentBoilerplateNotices( + jsonFileBuilder() + .addFullContact(techContact.getRepoId(), null, CONTACT_NAME, CONTACT_ADDRESS) + .load("rdap_associated_contact.json"))); } @Test void testValidDisconnectedContact_works() { login("evilregistrar"); - runSuccessfulHandleTest(disconnectedContact.getRepoId(), "rdap_contact.json"); + assertAboutJson() + .that(generateActualJson(disconnectedContact.getRepoId())) + .isEqualTo( + addPermanentBoilerplateNotices( + jsonFileBuilder() + .addFullContact( + disconnectedContact.getRepoId(), "active", CONTACT_NAME, CONTACT_ADDRESS) + .load("rdap_contact.json"))); } @Test void testDeletedContact_notFound() { - runNotFoundTest(deletedContact.getRepoId()); + String repoId = deletedContact.getRepoId(); + assertAboutJson() + .that(generateActualJson(repoId)) + .isEqualTo(generateExpectedJsonError(repoId + " not found", 404)); + assertThat(response.getStatus()).isEqualTo(404); } @Test void testDeletedContact_notFound_includeDeletedSetFalse() { action.includeDeletedParam = Optional.of(false); - runNotFoundTest(deletedContact.getRepoId()); + String repoId = deletedContact.getRepoId(); + assertAboutJson() + .that(generateActualJson(repoId)) + .isEqualTo(generateExpectedJsonError(repoId + " not found", 404)); + assertThat(response.getStatus()).isEqualTo(404); } @Test void testDeletedContact_notFound_notLoggedIn() { action.includeDeletedParam = Optional.of(true); - runNotFoundTest(deletedContact.getRepoId()); + String repoId = deletedContact.getRepoId(); + assertAboutJson() + .that(generateActualJson(repoId)) + .isEqualTo(generateExpectedJsonError(repoId + " not found", 404)); + assertThat(response.getStatus()).isEqualTo(404); } @Test void testDeletedContact_notFound_loggedInAsDifferentRegistrar() { login("idnregistrar"); action.includeDeletedParam = Optional.of(true); - runNotFoundTest(deletedContact.getRepoId()); + String repoId = deletedContact.getRepoId(); + assertAboutJson() + .that(generateActualJson(repoId)) + .isEqualTo(generateExpectedJsonError(repoId + " not found", 404)); + assertThat(response.getStatus()).isEqualTo(404); } @Test void testDeletedContact_found_loggedInAsCorrectRegistrar() { login("evilregistrar"); action.includeDeletedParam = Optional.of(true); - runSuccessfulHandleTest( - deletedContact.getRepoId(), - "", - "inactive", - "", - "rdap_contact_deleted.json"); + assertAboutJson() + .that(generateActualJson(deletedContact.getRepoId())) + .isEqualTo( + addPermanentBoilerplateNotices( + jsonFileBuilder() + .addContact(deletedContact.getRepoId()) + .load("rdap_contact_deleted.json"))); } @Test void testDeletedContact_found_loggedInAsAdmin() { loginAsAdmin(); action.includeDeletedParam = Optional.of(true); - runSuccessfulHandleTest( - deletedContact.getRepoId(), - "", - "inactive", - "", - "rdap_contact_deleted.json"); + assertAboutJson() + .that(generateActualJson(deletedContact.getRepoId())) + .isEqualTo( + addPermanentBoilerplateNotices( + jsonFileBuilder() + .addContact(deletedContact.getRepoId()) + .load("rdap_contact_deleted.json"))); } @Test void testRegistrar_found() { - runSuccessfulHandleTest("101", "Yes Virginia