Sometimes include deletion times in domain-list exports (#2602)

We only include the deletion time if the domain is in the 5-day
PENDING_DELETE period after the 30 day REDEMPTION period. For all other
domains, we just have an empty string as that field.

This is behind a feature flag so that we can control when it is enabled
This commit is contained in:
gbrodman
2024-11-06 17:59:30 +00:00
committed by GitHub
parent cc20f7d76d
commit ae61cd443d
5 changed files with 250 additions and 53 deletions
@@ -16,10 +16,14 @@ package google.registry.export;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.export.ExportDomainListsAction.REGISTERED_DOMAINS_FILENAME;
import static google.registry.model.common.FeatureFlag.FeatureName.INCLUDE_PENDING_DELETE_DATE_FOR_DOMAINS;
import static google.registry.model.common.FeatureFlag.FeatureStatus.ACTIVE;
import static google.registry.model.common.FeatureFlag.FeatureStatus.INACTIVE;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
import static google.registry.testing.DatabaseHelper.persistDeletedDomain;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.eq;
@@ -31,8 +35,14 @@ import com.google.cloud.storage.BlobId;
import com.google.cloud.storage.StorageException;
import com.google.cloud.storage.contrib.nio.testing.LocalStorageHelper;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.net.MediaType;
import google.registry.gcs.GcsUtils;
import google.registry.model.common.FeatureFlag;
import google.registry.model.domain.Domain;
import google.registry.model.domain.GracePeriod;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.tld.Tld;
import google.registry.model.tld.Tld.TldType;
import google.registry.persistence.transaction.JpaTestExtensions;
@@ -56,7 +66,7 @@ class ExportDomainListsActionTest {
@RegisterExtension
final JpaIntegrationTestExtension jpa =
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
new JpaTestExtensions.Builder().withClock(clock).buildIntegrationTestExtension();
@BeforeEach
void beforeEach() {
@@ -70,6 +80,7 @@ class ExportDomainListsActionTest {
action.gcsUtils = gcsUtils;
action.clock = clock;
action.driveConnection = driveConnection;
persistFeatureFlag(INACTIVE);
}
private void verifyExportedToDrive(String folderId, String domains) throws Exception {
@@ -83,7 +94,7 @@ class ExportDomainListsActionTest {
}
@Test
void test_outputsOnlyActiveDomains() throws Exception {
void test_outputsOnlyActiveDomains_txt() throws Exception {
persistActiveDomain("onetwo.tld");
persistActiveDomain("rudnitzky.tld");
persistDeletedDomain("mortuary.tld", DateTime.parse("2001-03-14T10:11:12Z"));
@@ -97,7 +108,22 @@ class ExportDomainListsActionTest {
}
@Test
void test_outputsOnlyDomainsOnRealTlds() throws Exception {
void test_outputsOnlyActiveDomains_csv() throws Exception {
persistFeatureFlag(ACTIVE);
persistActiveDomain("onetwo.tld");
persistActiveDomain("rudnitzky.tld");
persistDeletedDomain("mortuary.tld", DateTime.parse("2001-03-14T10:11:12Z"));
action.run();
BlobId existingFile = BlobId.of("outputbucket", "tld.txt");
String tlds = new String(gcsUtils.readBytesFrom(existingFile), UTF_8);
// Check that it only contains the active domains, not the dead one.
assertThat(tlds).isEqualTo("onetwo.tld,\nrudnitzky.tld,");
verifyExportedToDrive("brouhaha", "onetwo.tld,\nrudnitzky.tld,");
verifyNoMoreInteractions(driveConnection);
}
@Test
void test_outputsOnlyDomainsOnRealTlds_txt() throws Exception {
persistActiveDomain("onetwo.tld");
persistActiveDomain("rudnitzky.tld");
persistActiveDomain("wontgo.testtld");
@@ -116,7 +142,58 @@ class ExportDomainListsActionTest {
}
@Test
void test_outputsDomainsFromDifferentTldsToMultipleFiles() throws Exception {
void test_outputsOnlyDomainsOnRealTlds_csv() throws Exception {
persistFeatureFlag(ACTIVE);
persistActiveDomain("onetwo.tld");
persistActiveDomain("rudnitzky.tld");
persistActiveDomain("wontgo.testtld");
action.run();
BlobId existingFile = BlobId.of("outputbucket", "tld.txt");
String tlds = new String(gcsUtils.readBytesFrom(existingFile), UTF_8).trim();
// Check that it only contains the domains on the real TLD, and not the test one.
assertThat(tlds).isEqualTo("onetwo.tld,\nrudnitzky.tld,");
// Make sure that the test TLD file wasn't written out.
BlobId nonexistentFile = BlobId.of("outputbucket", "testtld.txt");
assertThrows(StorageException.class, () -> gcsUtils.readBytesFrom(nonexistentFile));
ImmutableList<String> ls = gcsUtils.listFolderObjects("outputbucket", "");
assertThat(ls).containsExactly("tld.txt");
verifyExportedToDrive("brouhaha", "onetwo.tld,\nrudnitzky.tld,");
verifyNoMoreInteractions(driveConnection);
}
@Test
void test_outputIncludesDeletionTimes_forPendingDeletes_notRdemption() throws Exception {
persistFeatureFlag(ACTIVE);
// Domains pending delete (meaning the 5 day period, not counting the 30 day redemption period)
// should include their pending deletion date
persistActiveDomain("active.tld");
Domain redemption = persistActiveDomain("redemption.tld");
persistResource(
redemption
.asBuilder()
.addStatusValue(StatusValue.PENDING_DELETE)
.addGracePeriod(
GracePeriod.createWithoutBillingEvent(
GracePeriodStatus.REDEMPTION,
redemption.getRepoId(),
clock.nowUtc().plusDays(20),
redemption.getCurrentSponsorRegistrarId()))
.build());
persistResource(
persistActiveDomain("pendingdelete.tld")
.asBuilder()
.addStatusValue(StatusValue.PENDING_DELETE)
.setDeletionTime(clock.nowUtc().plusDays(3))
.build());
action.run();
verifyExportedToDrive(
"brouhaha", "active.tld,\npendingdelete.tld,2020-02-05T02:02:02.000Z\nredemption.tld,");
}
@Test
void test_outputsDomainsFromDifferentTldsToMultipleFiles_txt() throws Exception {
createTld("tldtwo");
persistResource(Tld.get("tldtwo").asBuilder().setDriveFolderId("hooray").build());
@@ -143,4 +220,43 @@ class ExportDomainListsActionTest {
// tldthree does not have a drive id, so no export to drive is performed.
verifyNoMoreInteractions(driveConnection);
}
@Test
void test_outputsDomainsFromDifferentTldsToMultipleFiles_csv() throws Exception {
persistFeatureFlag(ACTIVE);
createTld("tldtwo");
persistResource(Tld.get("tldtwo").asBuilder().setDriveFolderId("hooray").build());
createTld("tldthree");
// You'd think this test was written around Christmas, but it wasn't.
persistActiveDomain("dasher.tld");
persistActiveDomain("prancer.tld");
persistActiveDomain("rudolph.tldtwo");
persistActiveDomain("santa.tldtwo");
persistActiveDomain("buddy.tldtwo");
persistActiveDomain("cupid.tldthree");
action.run();
BlobId firstTldFile = BlobId.of("outputbucket", "tld.txt");
String tlds = new String(gcsUtils.readBytesFrom(firstTldFile), UTF_8).trim();
assertThat(tlds).isEqualTo("dasher.tld,\nprancer.tld,");
BlobId secondTldFile = BlobId.of("outputbucket", "tldtwo.txt");
String moreTlds = new String(gcsUtils.readBytesFrom(secondTldFile), UTF_8).trim();
assertThat(moreTlds).isEqualTo("buddy.tldtwo,\nrudolph.tldtwo,\nsanta.tldtwo,");
BlobId thirdTldFile = BlobId.of("outputbucket", "tldthree.txt");
String evenMoreTlds = new String(gcsUtils.readBytesFrom(thirdTldFile), UTF_8).trim();
assertThat(evenMoreTlds).isEqualTo("cupid.tldthree,");
verifyExportedToDrive("brouhaha", "dasher.tld,\nprancer.tld,");
verifyExportedToDrive("hooray", "buddy.tldtwo,\nrudolph.tldtwo,\nsanta.tldtwo,");
// tldthree does not have a drive id, so no export to drive is performed.
verifyNoMoreInteractions(driveConnection);
}
private void persistFeatureFlag(FeatureFlag.FeatureStatus status) {
persistResource(
new FeatureFlag()
.asBuilder()
.setFeatureName(INCLUDE_PENDING_DELETE_DATE_FOR_DOMAINS)
.setStatusMap(ImmutableSortedMap.of(START_OF_TIME, status))
.build());
}
}
@@ -24,7 +24,6 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
import static google.registry.testing.DatabaseHelper.loadByEntity;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.joda.time.DateTimeZone.UTC;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableSet;
@@ -33,6 +32,7 @@ import google.registry.model.EntityTestCase;
import google.registry.model.common.FeatureFlag.FeatureFlagNotFoundException;
import google.registry.model.common.FeatureFlag.FeatureStatus;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link FeatureFlag}. */
@@ -50,7 +50,7 @@ public class FeatureFlagTest extends EntityTestCase {
.setStatusMap(
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
.put(START_OF_TIME, INACTIVE)
.put(DateTime.now(UTC).plusWeeks(8), ACTIVE)
.put(fakeClock.nowUtc().plusWeeks(8), ACTIVE)
.build())
.build();
persistResource(featureFlag);
@@ -66,7 +66,7 @@ public class FeatureFlagTest extends EntityTestCase {
.setStatusMap(
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
.put(START_OF_TIME, INACTIVE)
.put(DateTime.now(UTC).plusWeeks(8), ACTIVE)
.put(fakeClock.nowUtc().plusWeeks(8), ACTIVE)
.build())
.build();
persistResource(featureFlag);
@@ -82,7 +82,7 @@ public class FeatureFlagTest extends EntityTestCase {
.setStatusMap(
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
.put(START_OF_TIME, INACTIVE)
.put(DateTime.now(UTC).plusWeeks(8), ACTIVE)
.put(fakeClock.nowUtc().plusWeeks(8), ACTIVE)
.build())
.build());
FeatureFlag featureFlag2 =
@@ -92,7 +92,7 @@ public class FeatureFlagTest extends EntityTestCase {
.setStatusMap(
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
.put(START_OF_TIME, INACTIVE)
.put(DateTime.now(UTC).plusWeeks(3), INACTIVE)
.put(fakeClock.nowUtc().plusWeeks(3), INACTIVE)
.build())
.build());
FeatureFlag featureFlag3 =
@@ -122,7 +122,7 @@ public class FeatureFlagTest extends EntityTestCase {
.setStatusMap(
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
.put(START_OF_TIME, INACTIVE)
.put(DateTime.now(UTC).plusWeeks(8), ACTIVE)
.put(fakeClock.nowUtc().plusWeeks(8), ACTIVE)
.build())
.build());
persistResource(
@@ -131,7 +131,7 @@ public class FeatureFlagTest extends EntityTestCase {
.setStatusMap(
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
.put(START_OF_TIME, INACTIVE)
.put(DateTime.now(UTC).plusWeeks(3), INACTIVE)
.put(fakeClock.nowUtc().plusWeeks(3), INACTIVE)
.build())
.build());
FeatureFlagNotFoundException thrown =
@@ -164,7 +164,7 @@ public class FeatureFlagTest extends EntityTestCase {
.setStatusMap(
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
.put(START_OF_TIME, INACTIVE)
.put(DateTime.now(UTC).plusWeeks(8), ACTIVE)
.put(fakeClock.nowUtc().plusWeeks(8), ACTIVE)
.build());
IllegalArgumentException thrown =
assertThrows(IllegalArgumentException.class, () -> featureFlagBuilder.build());
@@ -180,7 +180,7 @@ public class FeatureFlagTest extends EntityTestCase {
() ->
featureFlagBuilder.setStatusMap(
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
.put(DateTime.now(UTC).plusWeeks(8), ACTIVE)
.put(fakeClock.nowUtc().plusWeeks(8), ACTIVE)
.build()));
assertThat(thrown)
.hasMessageThat()
@@ -203,4 +203,37 @@ public class FeatureFlagTest extends EntityTestCase {
fakeClock.setTo(DateTime.parse("2011-10-17TZ"));
assertThat(tm().transact(() -> FeatureFlag.isActiveNow(TEST_FEATURE))).isTrue();
}
@Test
void testSuccess_default_exists() {
persistResource(
new FeatureFlag.Builder()
.setFeatureName(TEST_FEATURE)
.setStatusMap(
ImmutableSortedMap.<DateTime, FeatureStatus>naturalOrder()
.put(START_OF_TIME, INACTIVE)
.put(fakeClock.nowUtc().plusWeeks(8), ACTIVE)
.build())
.build());
tm().transact(
() -> {
assertThat(FeatureFlag.isActiveNowOrElse(TEST_FEATURE, false)).isFalse();
assertThat(FeatureFlag.isActiveNowOrElse(TEST_FEATURE, true)).isFalse();
});
fakeClock.advanceBy(Duration.standardDays(365));
tm().transact(
() -> {
assertThat(FeatureFlag.isActiveNowOrElse(TEST_FEATURE, false)).isTrue();
assertThat(FeatureFlag.isActiveNowOrElse(TEST_FEATURE, true)).isTrue();
});
}
@Test
void testSuccess_default_doesNotExist() {
tm().transact(
() -> {
assertThat(FeatureFlag.isActiveNowOrElse(TEST_FEATURE, false)).isFalse();
assertThat(FeatureFlag.isActiveNowOrElse(TEST_FEATURE, true)).isTrue();
});
}
}