diff --git a/core/src/main/java/google/registry/bsa/persistence/BsaTld.java b/core/src/main/java/google/registry/bsa/persistence/BsaTld.java new file mode 100644 index 00000000000..d3effee649f --- /dev/null +++ b/core/src/main/java/google/registry/bsa/persistence/BsaTld.java @@ -0,0 +1,55 @@ +// Copyright 2026 The Nomulus Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package google.registry.bsa.persistence; + +import google.registry.model.tld.Tld; +import google.registry.persistence.VKey; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import java.time.Instant; + +/** + * A {@link Tld} that is ready to be included in BSA processing. + * + *

A {@code Tld}'s enrollment in BSA is controlled by configuration (See {@link + * Tld#getBsaEnrollStartTime}). This table records the {@code Tld}s that have been initialized. + */ +@Entity +final class BsaTld { + + @Id String tld; + + /** The time when this tld was properly set up for BSA processing. */ + @SuppressWarnings("unused") // Field is for record and troubleshooting. + @Column(nullable = false) + Instant readyTime; + + @SuppressWarnings("unused") // For Hibernate + private BsaTld() {} + + BsaTld(String tld, Instant readyTime) { + this.tld = tld; + this.readyTime = readyTime; + } + + String getTld() { + return tld; + } + + static VKey vKey(String tld) { + return VKey.create(BsaTld.class, tld); + } +} diff --git a/core/src/main/java/google/registry/bsa/persistence/Queries.java b/core/src/main/java/google/registry/bsa/persistence/Queries.java index e22c13f7115..e6f73353d35 100644 --- a/core/src/main/java/google/registry/bsa/persistence/Queries.java +++ b/core/src/main/java/google/registry/bsa/persistence/Queries.java @@ -16,6 +16,7 @@ import static com.google.common.base.Verify.verify; import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableSet.toImmutableSet; import static google.registry.bsa.BsaStringUtils.DOMAIN_SPLITTER; import static google.registry.bsa.BsaTransactions.bsaQuery; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; @@ -44,6 +45,11 @@ private static Object detach(Object obj) { return obj; } + /** Returns the names of all TLDs enrolled in BSA. */ + public static ImmutableSet getEnrolledTlds() { + return tm().loadAllOf(BsaTld.class).stream().map(BsaTld::getTld).collect(toImmutableSet()); + } + public static ImmutableList batchReadBsaLabelText( Optional lastRead, int batchSize) { return ImmutableList.copyOf( diff --git a/core/src/main/resources/META-INF/persistence.xml b/core/src/main/resources/META-INF/persistence.xml index 322ce06f8ff..d793ce648f7 100644 --- a/core/src/main/resources/META-INF/persistence.xml +++ b/core/src/main/resources/META-INF/persistence.xml @@ -39,6 +39,7 @@ google.registry.bsa.persistence.BsaDomainRefresh google.registry.bsa.persistence.BsaDownload google.registry.bsa.persistence.BsaLabel + google.registry.bsa.persistence.BsaTld google.registry.bsa.persistence.BsaUnblockableDomain google.registry.model.billing.BillingCancellation google.registry.model.billing.BillingEvent diff --git a/core/src/test/java/google/registry/bsa/persistence/BsaTldTest.java b/core/src/test/java/google/registry/bsa/persistence/BsaTldTest.java new file mode 100644 index 00000000000..d11a077d7ed --- /dev/null +++ b/core/src/test/java/google/registry/bsa/persistence/BsaTldTest.java @@ -0,0 +1,43 @@ +// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package google.registry.bsa.persistence; + +import static com.google.common.truth.Truth.assertThat; +import static google.registry.persistence.transaction.TransactionManagerFactory.tm; + +import google.registry.persistence.transaction.JpaTestExtensions; +import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationWithCoverageExtension; +import google.registry.testing.FakeClock; +import java.time.Instant; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +/** Unit tests for {@link BsaLabel}. */ +public class BsaTldTest { + + FakeClock fakeClock = new FakeClock(Instant.parse("2024-01-01T00:00:00Z")); + + @RegisterExtension + final JpaIntegrationWithCoverageExtension jpa = + new JpaTestExtensions.Builder().withClock(fakeClock).buildIntegrationWithCoverageExtension(); + + @Test + void persist() { + tm().transact(() -> tm().put(new BsaTld("tld", fakeClock.now()))); + BsaTld persisted = tm().transact(() -> tm().loadByKey(BsaTld.vKey("tld"))); + assertThat(persisted.getTld()).isEqualTo("tld"); + assertThat(persisted.readyTime).isEqualTo(fakeClock.now()); + } +} diff --git a/core/src/test/java/google/registry/bsa/persistence/QueriesTest.java b/core/src/test/java/google/registry/bsa/persistence/QueriesTest.java index f185aeb7cde..7c9507b9a20 100644 --- a/core/src/test/java/google/registry/bsa/persistence/QueriesTest.java +++ b/core/src/test/java/google/registry/bsa/persistence/QueriesTest.java @@ -77,6 +77,21 @@ void setup() { }); } + @Test + void getEnrolledTlds_noneEnrolled() { + assertThat(tm().transact(Queries::getEnrolledTlds)).isEmpty(); + } + + @Test + void getEnrolledTlds_hasEnrolled() { + tm().transact( + () -> { + tm().put(new BsaTld("tld1", fakeClock.now())); + tm().put(new BsaTld("tld2", fakeClock.now())); + }); + assertThat(tm().transact(Queries::getEnrolledTlds)).containsExactly("tld1", "tld2"); + } + @Test void queryBsaUnblockableDomainByLabels_oneLabel() { assertThat( diff --git a/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java b/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java index 476f5d8e0e1..8616589387c 100644 --- a/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java +++ b/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java @@ -19,6 +19,7 @@ import google.registry.bsa.persistence.BsaDomainRefreshTest; import google.registry.bsa.persistence.BsaDownloadTest; import google.registry.bsa.persistence.BsaLabelTest; +import google.registry.bsa.persistence.BsaTldTest; import google.registry.bsa.persistence.BsaUnblockableDomainTest; import google.registry.model.billing.BillingBaseTest; import google.registry.model.common.CursorTest; @@ -77,6 +78,7 @@ BsaDomainRefreshTest.class, BsaDownloadTest.class, BsaLabelTest.class, + BsaTldTest.class, BsaUnblockableDomainTest.class, BulkPricingPackageTest.class, ClaimsListDaoTest.class, diff --git a/db/src/main/resources/sql/schema/db-schema.sql.generated b/db/src/main/resources/sql/schema/db-schema.sql.generated index c9d74594eb9..d10fbb5f3fd 100644 --- a/db/src/main/resources/sql/schema/db-schema.sql.generated +++ b/db/src/main/resources/sql/schema/db-schema.sql.generated @@ -112,6 +112,12 @@ primary key (label) ); + create table "BsaTld" ( + tld text not null, + ready_time timestamp(6) with time zone not null, + primary key (tld) + ); + create table "BsaUnblockableDomain" ( label text not null, tld text not null,