Add more absent clTrid unit tests

In RFC 5730, clTrid is specified as optional. We ran into an error earlier this
year in which a registrar was not passing a client transaction id and we didn't
handle it correctly. So, this CL adds some tests of common EPP operations verify
that they work correctly when the clTrid is not specified.

This also slightly improves some flow logic to make it more obvious at first
glance that clTrid is indeed optional.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=202000845
This commit is contained in:
mcilwain 2018-06-25 12:39:41 -07:00 committed by Ben McIlwain
parent 6706b99828
commit ed910455b0
21 changed files with 252 additions and 33 deletions

View file

@ -156,7 +156,8 @@ public class FlowModule {
@FlowScope @FlowScope
Trid provideTrid(EppInput eppInput, ServerTridProvider serverTridProvider) { Trid provideTrid(EppInput eppInput, ServerTridProvider serverTridProvider) {
return Trid.create( return Trid.create(
eppInput.getCommandWrapper().getClTrid(), serverTridProvider.createServerTrid()); eppInput.getCommandWrapper().getClTrid().orElse(null),
serverTridProvider.createServerTrid());
} }
@Provides @Provides

View file

@ -124,9 +124,8 @@ public final class AsyncFlowEnqueuer {
.param(PARAM_SERVER_TRANSACTION_ID, trid.getServerTransactionId()) .param(PARAM_SERVER_TRANSACTION_ID, trid.getServerTransactionId())
.param(PARAM_IS_SUPERUSER, Boolean.toString(isSuperuser)) .param(PARAM_IS_SUPERUSER, Boolean.toString(isSuperuser))
.param(PARAM_REQUESTED_TIME, now.toString()); .param(PARAM_REQUESTED_TIME, now.toString());
if (trid.getClientTransactionId().isPresent()) { trid.getClientTransactionId()
task.param(PARAM_CLIENT_TRANSACTION_ID, trid.getClientTransactionId().get()); .ifPresent(clTrid -> task.param(PARAM_CLIENT_TRANSACTION_ID, clTrid));
}
addTaskToQueueWithRetry(asyncDeletePullQueue, task); addTaskToQueueWithRetry(asyncDeletePullQueue, task);
} }

View file

@ -16,7 +16,6 @@ package google.registry.model.eppcommon;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.google.common.annotations.VisibleForTesting;
import com.googlecode.objectify.annotation.Embed; import com.googlecode.objectify.annotation.Embed;
import google.registry.model.ImmutableObject; import google.registry.model.ImmutableObject;
import java.util.Optional; import java.util.Optional;
@ -40,6 +39,7 @@ public class Trid extends ImmutableObject {
/** The client transaction id, if provided by the client, otherwise null. */ /** The client transaction id, if provided by the client, otherwise null. */
@XmlElement(name = "clTRID", namespace = "urn:ietf:params:xml:ns:epp-1.0") @XmlElement(name = "clTRID", namespace = "urn:ietf:params:xml:ns:epp-1.0")
@Nullable
String clientTransactionId; String clientTransactionId;
public String getServerTransactionId() { public String getServerTransactionId() {
@ -50,7 +50,6 @@ public class Trid extends ImmutableObject {
return Optional.ofNullable(clientTransactionId); return Optional.ofNullable(clientTransactionId);
} }
@VisibleForTesting
public static Trid create(@Nullable String clientTransactionId, String serverTransactionId) { public static Trid create(@Nullable String clientTransactionId, String serverTransactionId) {
checkArgumentNotNull(serverTransactionId, "serverTransactionId cannot be null"); checkArgumentNotNull(serverTransactionId, "serverTransactionId cannot be null");
Trid instance = new Trid(); Trid instance = new Trid();

View file

@ -366,10 +366,15 @@ public class EppInput extends ImmutableObject {
@XmlElementWrapper @XmlElementWrapper
List<CommandExtension> extension; List<CommandExtension> extension;
String clTRID; @Nullable String clTRID;
public String getClTrid() { /**
return clTRID; * Returns the client transaction ID.
*
* <p>This is optional (i.e. it may not be specified) per RFC 5730.
*/
public Optional<String> getClTrid() {
return Optional.ofNullable(clTRID);
} }
public InnerCommand getCommand() { public InnerCommand getCommand() {

View file

@ -63,6 +63,7 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import javax.annotation.Nullable;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
@ -141,8 +142,9 @@ public abstract class FlowTestCase<F extends Flow> extends ShardableTestCase {
return TestDataHelper.loadFile(getClass(), filename, substitutions); return TestDataHelper.loadFile(getClass(), filename, substitutions);
} }
@Nullable
protected String getClientTrid() throws Exception { protected String getClientTrid() throws Exception {
return eppLoader.getEpp().getCommandWrapper().getClTrid(); return eppLoader.getEpp().getCommandWrapper().getClTrid().orElse(null);
} }
/** Gets the client ID that the flow will run as. */ /** Gets the client ID that the flow will run as. */

View file

@ -170,9 +170,8 @@ public abstract class ResourceFlowTestCase<F extends Flow, R extends EppResource
.param("serverTransactionId", trid.getServerTransactionId()) .param("serverTransactionId", trid.getServerTransactionId())
.param("isSuperuser", Boolean.toString(isSuperuser)) .param("isSuperuser", Boolean.toString(isSuperuser))
.param("requestedTime", clock.nowUtc().toString()); .param("requestedTime", clock.nowUtc().toString());
if (trid.getClientTransactionId().isPresent()) { trid.getClientTransactionId()
expected.param("clientTransactionId", trid.getClientTransactionId().get()); .ifPresent(clTrid -> expected.param("clientTransactionId", clTrid));
}
assertTasksEnqueued("async-delete-pull", expected); assertTasksEnqueued("async-delete-pull", expected);
} }

View file

@ -114,6 +114,16 @@ public class DomainCheckFlowTest
create(true, "example3.tld", null)); create(true, "example3.tld", null));
} }
@Test
public void testSuccess_clTridNotSpecified() throws Exception {
setEppInput("domain_check_no_cltrid.xml");
persistActiveDomain("example1.tld");
doCheckTest(
create(false, "example1.tld", "In use"),
create(true, "example2.tld", null),
create(true, "example3.tld", null));
}
@Test @Test
public void testSuccess_oneExists_allocationTokenIsInvalid() throws Exception { public void testSuccess_oneExists_allocationTokenIsInvalid() throws Exception {
setEppInput("domain_check_allocationtoken.xml"); setEppInput("domain_check_allocationtoken.xml");

View file

@ -393,6 +393,13 @@ public class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow,
doSuccessfulTest(); doSuccessfulTest();
} }
@Test
public void testSuccess_clTridNotSpecified() throws Exception {
setEppInput("domain_create_no_cltrid.xml");
persistContactsAndHosts();
doSuccessfulTest("tld", "domain_create_response_no_cltrid.xml");
}
@Test @Test
public void testFailure_invalidAllocationToken() { public void testFailure_invalidAllocationToken() {
setEppInput("domain_create_allocationtoken.xml"); setEppInput("domain_create_allocationtoken.xml");

View file

@ -155,6 +155,12 @@ public class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, Dom
doSuccessfulTest("domain_info_response.xml"); doSuccessfulTest("domain_info_response.xml");
} }
@Test
public void testSuccess_clTridNotSpecified() throws Exception {
setEppInput("domain_info_no_cltrid.xml");
doSuccessfulTest("domain_info_response_no_cltrid.xml");
}
@Test @Test
public void testSuccess_allHosts_noDelegatedHosts() throws Exception { public void testSuccess_allHosts_noDelegatedHosts() throws Exception {
// There aren't any delegated hosts. // There aren't any delegated hosts.

View file

@ -107,14 +107,11 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow,
ContactResource unusedContact; ContactResource unusedContact;
HistoryEntry historyEntryDomainCreate; HistoryEntry historyEntryDomainCreate;
public DomainUpdateFlowTest() {
// Note that "domain_update.xml" tests adding and removing the same contact type.
setEppInput("domain_update.xml");
}
@Before @Before
public void initDomainTest() { public void initDomainTest() {
createTld("tld"); createTld("tld");
// Note that "domain_update.xml" tests adding and removing the same contact type.
setEppInput("domain_update.xml");
} }
private void persistReferencedEntities() { private void persistReferencedEntities() {
@ -149,8 +146,12 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow,
} }
private void doSuccessfulTest() throws Exception { private void doSuccessfulTest() throws Exception {
doSuccessfulTest("domain_update_response.xml");
}
private void doSuccessfulTest(String expectedXmlFilename) throws Exception {
assertTransactionalFlow(true); assertTransactionalFlow(true);
runFlowAssertResponse(loadFile("domain_update_response.xml")); runFlowAssertResponse(loadFile(expectedXmlFilename));
// Check that the domain was updated. These values came from the xml. // Check that the domain was updated. These values came from the xml.
assertAboutDomains() assertAboutDomains()
.that(reloadResourceByForeignKey()) .that(reloadResourceByForeignKey())
@ -178,6 +179,14 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow,
doSuccessfulTest(); doSuccessfulTest();
} }
@Test
public void testSuccess_clTridNotSpecified() throws Exception {
setEppInput("domain_update_no_cltrid.xml");
persistReferencedEntities();
persistDomain();
doSuccessfulTest("generic_success_response_no_cltrid.xml");
}
@Test @Test
public void testSuccess_cachingDisabled() throws Exception { public void testSuccess_cachingDisabled() throws Exception {
RegistryConfig.overrideIsEppResourceCachingEnabledForTesting(false); RegistryConfig.overrideIsEppResourceCachingEnabledForTesting(false);

View file

@ -0,0 +1,11 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<check>
<domain:check xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>example1.tld</domain:name>
<domain:name>example2.tld</domain:name>
<domain:name>example3.tld</domain:name>
</domain:check>
</check>
</command>
</epp>

View file

@ -0,0 +1,21 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<create>
<domain:create
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>example.tld</domain:name>
<domain:period unit="y">2</domain:period>
<domain:ns>
<domain:hostObj>ns1.example.net</domain:hostObj>
<domain:hostObj>ns2.example.net</domain:hostObj>
</domain:ns>
<domain:registrant>jd1234</domain:registrant>
<domain:contact type="admin">sh8013</domain:contact>
<domain:contact type="tech">sh8013</domain:contact>
<domain:authInfo>
<domain:pw>2fooBAR</domain:pw>
</domain:authInfo>
</domain:create>
</create>
</command>
</epp>

View file

@ -0,0 +1,18 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<response>
<result code="1000">
<msg>Command completed successfully</msg>
</result>
<resData>
<domain:creData
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>example.tld</domain:name>
<domain:crDate>1999-04-03T22:00:00.0Z</domain:crDate>
<domain:exDate>2001-04-03T22:00:00.0Z</domain:exDate>
</domain:creData>
</resData>
<trID>
<svTRID>server-trid</svTRID>
</trID>
</response>
</epp>

View file

@ -0,0 +1,10 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<info>
<domain:info
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name hosts="all">example.tld</domain:name>
</domain:info>
</info>
</command>
</epp>

View file

@ -0,0 +1,37 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<response>
<result code="1000">
<msg>Command completed successfully</msg>
</result>
<resData>
<domain:infData
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>example.tld</domain:name>
<domain:roid>%ROID%</domain:roid>
<domain:status s="ok"/>
<domain:registrant>jd1234</domain:registrant>
<domain:contact type="admin">sh8013</domain:contact>
<domain:contact type="tech">sh8013</domain:contact>
<domain:ns>
<domain:hostObj>ns1.example.tld</domain:hostObj>
<domain:hostObj>ns1.example.net</domain:hostObj>
</domain:ns>
<domain:host>ns1.example.tld</domain:host>
<domain:host>ns2.example.tld</domain:host>
<domain:clID>NewRegistrar</domain:clID>
<domain:crID>TheRegistrar</domain:crID>
<domain:crDate>1999-04-03T22:00:00.0Z</domain:crDate>
<domain:upID>NewRegistrar</domain:upID>
<domain:upDate>1999-12-03T09:00:00.0Z</domain:upDate>
<domain:exDate>2005-04-03T22:00:00.0Z</domain:exDate>
<domain:trDate>2000-04-08T09:00:00.0Z</domain:trDate>
<domain:authInfo>
<domain:pw>2fooBAR</domain:pw>
</domain:authInfo>
</domain:infData>
</resData>
<trID>
<svTRID>server-trid</svTRID>
</trID>
</response>
</epp>

View file

@ -0,0 +1,31 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<update>
<domain:update
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>example.tld</domain:name>
<domain:add>
<domain:ns>
<domain:hostObj>ns2.example.foo</domain:hostObj>
</domain:ns>
<domain:contact type="tech">mak21</domain:contact>
<domain:status s="clientHold"
lang="en">Payment overdue.</domain:status>
</domain:add>
<domain:rem>
<domain:ns>
<domain:hostObj>ns1.example.foo</domain:hostObj>
</domain:ns>
<domain:contact type="tech">sh8013</domain:contact>
<domain:status s="clientUpdateProhibited"/>
</domain:rem>
<domain:chg>
<domain:registrant>sh8013</domain:registrant>
<domain:authInfo>
<domain:pw>2BARfoo</domain:pw>
</domain:authInfo>
</domain:chg>
</domain:update>
</update>
</command>
</epp>

View file

@ -0,0 +1,10 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<response>
<result code="1000">
<msg>Command completed successfully</msg>
</result>
<trID>
<svTRID>server-trid</svTRID>
</trID>
</response>
</epp>

View file

@ -59,28 +59,42 @@ public class PollRequestFlowTest extends FlowTestCase<PollRequestFlow> {
host = persistActiveHost("ns1.test.example"); host = persistActiveHost("ns1.test.example");
} }
@Test private void persistPendingTransferPollMessage() {
public void testSuccess_domainTransferApproved() throws Exception {
persistResource( persistResource(
new PollMessage.OneTime.Builder() new PollMessage.OneTime.Builder()
.setClientId(getClientIdForFlow()) .setClientId(getClientIdForFlow())
.setEventTime(clock.nowUtc().minusDays(1)) .setEventTime(clock.nowUtc().minusDays(1))
.setMsg("Transfer approved.") .setMsg("Transfer approved.")
.setResponseData(ImmutableList.of(new DomainTransferResponse.Builder() .setResponseData(
.setFullyQualifiedDomainName("test.example") ImmutableList.of(
.setTransferStatus(TransferStatus.SERVER_APPROVED) new DomainTransferResponse.Builder()
.setGainingClientId(getClientIdForFlow()) .setFullyQualifiedDomainName("test.example")
.setTransferRequestTime(clock.nowUtc().minusDays(5)) .setTransferStatus(TransferStatus.SERVER_APPROVED)
.setLosingClientId("TheRegistrar") .setGainingClientId(getClientIdForFlow())
.setPendingTransferExpirationTime(clock.nowUtc().minusDays(1)) .setTransferRequestTime(clock.nowUtc().minusDays(5))
.setExtendedRegistrationExpirationTime(clock.nowUtc().plusYears(1)) .setLosingClientId("TheRegistrar")
.build())) .setPendingTransferExpirationTime(clock.nowUtc().minusDays(1))
.setExtendedRegistrationExpirationTime(clock.nowUtc().plusYears(1))
.build()))
.setParent(createHistoryEntryForEppResource(domain)) .setParent(createHistoryEntryForEppResource(domain))
.build()); .build());
}
@Test
public void testSuccess_domainTransferApproved() throws Exception {
persistPendingTransferPollMessage();
assertTransactionalFlow(false); assertTransactionalFlow(false);
runFlowAssertResponse(loadFile("poll_response_domain_transfer.xml")); runFlowAssertResponse(loadFile("poll_response_domain_transfer.xml"));
} }
@Test
public void testSuccess_clTridNotSpecified() throws Exception {
setEppInput("poll_no_cltrid.xml");
persistPendingTransferPollMessage();
assertTransactionalFlow(false);
runFlowAssertResponse(loadFile("poll_response_domain_transfer_no_cltrid.xml"));
}
@Test @Test
public void testSuccess_contactTransferPending() throws Exception { public void testSuccess_contactTransferPending() throws Exception {
clock.setTo(DateTime.parse("2000-06-13T22:00:00.0Z")); clock.setTo(DateTime.parse("2000-06-13T22:00:00.0Z"));

View file

@ -0,0 +1,5 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<poll op="req"/>
</command>
</epp>

View file

@ -0,0 +1,25 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<response>
<result code="1301">
<msg>Command completed successfully; ack to dequeue</msg>
</result>
<msgQ count="1" id="1-3-EXAMPLE-5-6-2011">
<qDate>2011-01-01T01:01:01Z</qDate>
<msg>Transfer approved.</msg>
</msgQ>
<resData>
<domain:trnData xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>test.example</domain:name>
<domain:trStatus>serverApproved</domain:trStatus>
<domain:reID>NewRegistrar</domain:reID>
<domain:reDate>2010-12-28T01:01:01Z</domain:reDate>
<domain:acID>TheRegistrar</domain:acID>
<domain:acDate>2011-01-01T01:01:01Z</domain:acDate>
<domain:exDate>2012-01-02T01:01:01Z</domain:exDate>
</domain:trnData>
</resData>
<trID>
<svTRID>server-trid</svTRID>
</trID>
</response>
</epp>

View file

@ -35,7 +35,7 @@ public class EppInputTest {
public void testUnmarshalling_contactInfo() throws Exception { public void testUnmarshalling_contactInfo() throws Exception {
EppInput input = EppInput input =
unmarshal(EppInput.class, loadBytes(ContactResourceTest.class, "contact_info.xml").read()); unmarshal(EppInput.class, loadBytes(ContactResourceTest.class, "contact_info.xml").read());
assertThat(input.getCommandWrapper().getClTrid()).isEqualTo("ABC-12345"); assertThat(input.getCommandWrapper().getClTrid()).hasValue("ABC-12345");
assertThat(input.getCommandType()).isEqualTo("info"); assertThat(input.getCommandType()).isEqualTo("info");
assertThat(input.getResourceType()).hasValue("contact"); assertThat(input.getResourceType()).hasValue("contact");
assertThat(input.getSingleTargetId()).hasValue("sh8013"); assertThat(input.getSingleTargetId()).hasValue("sh8013");
@ -46,7 +46,7 @@ public class EppInputTest {
public void testUnmarshalling_domainCheck() throws Exception { public void testUnmarshalling_domainCheck() throws Exception {
EppInput input = EppInput input =
unmarshal(EppInput.class, loadBytes(DomainResourceTest.class, "domain_check.xml").read()); unmarshal(EppInput.class, loadBytes(DomainResourceTest.class, "domain_check.xml").read());
assertThat(input.getCommandWrapper().getClTrid()).isEqualTo("ABC-12345"); assertThat(input.getCommandWrapper().getClTrid()).hasValue("ABC-12345");
assertThat(input.getCommandType()).isEqualTo("check"); assertThat(input.getCommandType()).isEqualTo("check");
assertThat(input.getResourceType()).hasValue("domain"); assertThat(input.getResourceType()).hasValue("domain");
assertThat(input.getSingleTargetId()).isEmpty(); assertThat(input.getSingleTargetId()).isEmpty();
@ -56,7 +56,7 @@ public class EppInputTest {
@Test @Test
public void testUnmarshalling_login() throws Exception { public void testUnmarshalling_login() throws Exception {
EppInput input = unmarshal(EppInput.class, loadBytes(getClass(), "login_valid.xml").read()); EppInput input = unmarshal(EppInput.class, loadBytes(getClass(), "login_valid.xml").read());
assertThat(input.getCommandWrapper().getClTrid()).isEqualTo("ABC-12345"); assertThat(input.getCommandWrapper().getClTrid()).hasValue("ABC-12345");
assertThat(input.getCommandType()).isEqualTo("login"); assertThat(input.getCommandType()).isEqualTo("login");
assertThat(input.getResourceType()).isEmpty(); assertThat(input.getResourceType()).isEmpty();
assertThat(input.getSingleTargetId()).isEmpty(); assertThat(input.getSingleTargetId()).isEmpty();