From d0c5c80ff991f6614a507d55ff025ff760d188f9 Mon Sep 17 00:00:00 2001 From: Weimin Yu Date: Tue, 30 Dec 2025 03:19:40 +0000 Subject: [PATCH 1/3] Add RST support in Sandbox Added RST test label files as resources. Added a RstTmchUtils class that loads appropriate labels according to TLD pattern. Temporarily changed label fetching in production to include the TLD string, so that the new class may know which set of labels to use. --- .../flows/domain/DomainClaimsCheckFlow.java | 3 +- .../flows/domain/DomainCreateFlow.java | 5 +- .../flows/domain/DomainFlowTmchUtils.java | 11 +- .../model/smd/SignedMarkRevocationList.java | 6 + .../registry/model/tmch/ClaimsListDao.java | 6 + .../google/registry/tmch/RstTmchUtils.java | 119 ++++++++++++++++++ .../google/registry/tmch/ote.rst.dnl.csv | 10 ++ .../google/registry/tmch/ote.rst.smdrl.csv | 7 ++ .../google/registry/tmch/prod.rst.dnl.csv | 10 ++ .../google/registry/tmch/prod.rst.smdrl.csv | 7 ++ .../registry/tmch/RstTmchUtilsTest.java | 117 +++++++++++++++++ .../tmch/TmchTestDataExpirationTest.java | 2 +- 12 files changed, 294 insertions(+), 9 deletions(-) create mode 100644 core/src/main/java/google/registry/tmch/RstTmchUtils.java create mode 100644 core/src/main/resources/google/registry/tmch/ote.rst.dnl.csv create mode 100644 core/src/main/resources/google/registry/tmch/ote.rst.smdrl.csv create mode 100644 core/src/main/resources/google/registry/tmch/prod.rst.dnl.csv create mode 100644 core/src/main/resources/google/registry/tmch/prod.rst.smdrl.csv create mode 100644 core/src/test/java/google/registry/tmch/RstTmchUtilsTest.java diff --git a/core/src/main/java/google/registry/flows/domain/DomainClaimsCheckFlow.java b/core/src/main/java/google/registry/flows/domain/DomainClaimsCheckFlow.java index 749158c03cb..34b893cb9c9 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainClaimsCheckFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainClaimsCheckFlow.java @@ -108,7 +108,8 @@ public EppResponse run() throws EppException { verifyClaimsPeriodNotEnded(tld, now); } } - Optional claimKey = ClaimsListDao.get().getClaimKey(parsedDomain.parts().get(0)); + Optional claimKey = + ClaimsListDao.get(tldStr).getClaimKey(parsedDomain.parts().get(0)); launchChecksBuilder.add( LaunchCheck.create( LaunchCheckName.create(claimKey.isPresent(), domainName), claimKey.orElse(null))); diff --git a/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java b/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java index 18184347bcf..bc47760546f 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java @@ -280,7 +280,7 @@ public EppResponse run() throws EppException { checkAllowedAccessToTld(registrarId, tld.getTldStr()); checkHasBillingAccount(registrarId, tld.getTldStr()); boolean isValidReservedCreate = isValidReservedCreate(domainName, allocationToken); - ClaimsList claimsList = ClaimsListDao.get(); + ClaimsList claimsList = ClaimsListDao.get(tld.getTldStr()); verifyIsGaOrSpecialCase( tld, claimsList, @@ -312,7 +312,8 @@ public EppResponse run() throws EppException { // at this point so that we can verify it before the "after validation" extension point. signedMarkId = tmchUtils - .verifySignedMarks(launchCreate.get().getSignedMarks(), domainLabel, now) + .verifySignedMarks( + tld.getTldStr(), launchCreate.get().getSignedMarks(), domainLabel, now) .getId(); } verifyNotBlockedByBsa(domainName, tld, now, allocationToken); diff --git a/core/src/main/java/google/registry/flows/domain/DomainFlowTmchUtils.java b/core/src/main/java/google/registry/flows/domain/DomainFlowTmchUtils.java index ec1a144b00a..3dcf8994dc7 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainFlowTmchUtils.java +++ b/core/src/main/java/google/registry/flows/domain/DomainFlowTmchUtils.java @@ -55,7 +55,7 @@ public DomainFlowTmchUtils(TmchXmlSignature tmchXmlSignature) { } public SignedMark verifySignedMarks( - ImmutableList signedMarks, String domainLabel, DateTime now) + String tld, ImmutableList signedMarks, String domainLabel, DateTime now) throws EppException { if (signedMarks.size() > 1) { throw new TooManySignedMarksException(); @@ -64,7 +64,7 @@ public SignedMark verifySignedMarks( throw new SignedMarksMustBeEncodedException(); } SignedMark signedMark = - verifyEncodedSignedMark((EncodedSignedMark) signedMarks.get(0), now); + verifyEncodedSignedMark(tld, (EncodedSignedMark) signedMarks.get(0), now); return verifySignedMarkValidForDomainLabel(signedMark, domainLabel); } @@ -76,8 +76,9 @@ public SignedMark verifySignedMarkValidForDomainLabel(SignedMark signedMark, Str return signedMark; } - public SignedMark verifyEncodedSignedMark(EncodedSignedMark encodedSignedMark, DateTime now) - throws EppException { + // TODO(b/412715713): remove the tld parameter when RST completes. + public SignedMark verifyEncodedSignedMark( + String tld, EncodedSignedMark encodedSignedMark, DateTime now) throws EppException { if (!encodedSignedMark.getEncoding().equals("base64")) { throw new Base64RequiredForEncodedSignedMarksException(); } @@ -95,7 +96,7 @@ public SignedMark verifyEncodedSignedMark(EncodedSignedMark encodedSignedMark, D throw new SignedMarkParsingErrorException(); } - if (SignedMarkRevocationList.get().isSmdRevoked(signedMark.getId(), now)) { + if (SignedMarkRevocationList.get(tld).isSmdRevoked(signedMark.getId(), now)) { throw new SignedMarkRevokedErrorException(); } diff --git a/core/src/main/java/google/registry/model/smd/SignedMarkRevocationList.java b/core/src/main/java/google/registry/model/smd/SignedMarkRevocationList.java index 083882f343c..11a2d73739b 100644 --- a/core/src/main/java/google/registry/model/smd/SignedMarkRevocationList.java +++ b/core/src/main/java/google/registry/model/smd/SignedMarkRevocationList.java @@ -21,6 +21,7 @@ import com.google.common.base.Supplier; import com.google.common.collect.ImmutableMap; import google.registry.model.ImmutableObject; +import google.registry.tmch.RstTmchUtils; import jakarta.persistence.CollectionTable; import jakarta.persistence.Column; import jakarta.persistence.ElementCollection; @@ -71,6 +72,11 @@ public static SignedMarkRevocationList get() { return CACHE.get(); } + // TODO(b/412715713): remove the tld parameter when RST completes. + public static SignedMarkRevocationList get(String tld) { + return RstTmchUtils.getSmdrList(tld).orElseGet(SignedMarkRevocationList::get); + } + /** Create a new {@link SignedMarkRevocationList} without saving it. */ public static SignedMarkRevocationList create( DateTime creationTime, ImmutableMap revokes) { diff --git a/core/src/main/java/google/registry/model/tmch/ClaimsListDao.java b/core/src/main/java/google/registry/model/tmch/ClaimsListDao.java index 2e041794ed1..c00ac5deb7d 100644 --- a/core/src/main/java/google/registry/model/tmch/ClaimsListDao.java +++ b/core/src/main/java/google/registry/model/tmch/ClaimsListDao.java @@ -22,6 +22,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import google.registry.model.CacheUtils; +import google.registry.tmch.RstTmchUtils; import java.time.Duration; import java.util.Optional; @@ -72,6 +73,11 @@ public static ClaimsList get() { return CACHE.get(ClaimsListDao.class); } + // TODO(b/412715713): remove the tld parameter when RST completes. + public static ClaimsList get(String tld) { + return RstTmchUtils.getClaimsList(tld).orElseGet(ClaimsListDao::get); + } + /** * Returns the most recent revision of the {@link ClaimsList} in SQL or an empty list if it * doesn't exist. diff --git a/core/src/main/java/google/registry/tmch/RstTmchUtils.java b/core/src/main/java/google/registry/tmch/RstTmchUtils.java new file mode 100644 index 00000000000..a3aaf0c609c --- /dev/null +++ b/core/src/main/java/google/registry/tmch/RstTmchUtils.java @@ -0,0 +1,119 @@ +// Copyright 2025 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.tmch; + +import static com.google.common.base.Suppliers.memoize; +import static com.google.common.io.Resources.getResource; +import static com.google.common.io.Resources.readLines; +import static google.registry.tmch.RstTmchUtils.RstType.OTE; +import static google.registry.tmch.RstTmchUtils.RstType.PROD; +import static google.registry.util.RegistryEnvironment.SANDBOX; +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableMap; +import com.google.common.flogger.FluentLogger; +import google.registry.model.smd.SignedMarkRevocationList; +import google.registry.model.tmch.ClaimsList; +import google.registry.util.RegistryEnvironment; +import java.io.IOException; +import java.net.URL; +import java.util.Locale; +import java.util.Optional; + +/** + * Utilities supporting TMCH-related RST testing in the Sandbox environment. + * + *

For logistic reasons we must conduct RST testing in the Sandbox environments. RST tests + * require the use of special labels hosted on their website. To isolate these labels from regular + * customers conducting onboarding tests, we manually download the test files as resources, and + * serve them up only to RST TLDs. + */ +public class RstTmchUtils { + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + + enum RstType { + OTE, + PROD + } + + private static final ImmutableMap>> CLAIMS_CACHE = + ImmutableMap.of( + OTE, memoize(() -> getClaimList(OTE)), PROD, memoize(() -> getClaimList(PROD))); + + private static final ImmutableMap>> + SMDRL_CACHE = + ImmutableMap.of( + OTE, memoize(() -> getSmdrList(OTE)), PROD, memoize(() -> getSmdrList(PROD))); + + /** + * Returns appropriate test labels if {@code tld} is for RST testing; otherwise returns {@code + * defaultList}. + */ + public static Optional getClaimsList(String tld) { + return getRstType(tld).map(CLAIMS_CACHE::get).flatMap(Supplier::get); + } + + /** + * Returns appropriate test labels if {@code tld} is for RST testing; otherwise returns {@code + * defaultList}. + */ + public static Optional getSmdrList(String tld) { + return getRstType(tld).map(SMDRL_CACHE::get).flatMap(Supplier::get); + } + + static Optional getRstType(String tld) { + if (!RegistryEnvironment.get().equals(SANDBOX)) { + return Optional.empty(); + } + if (tld.startsWith("cc-rst-test-")) { + return Optional.of(OTE); + } + if (tld.startsWith("zz--")) { + return Optional.of(PROD); + } + return Optional.empty(); + } + + private static Optional getClaimList(RstType rstType) { + if (!RegistryEnvironment.get().equals(SANDBOX)) { + return Optional.empty(); + } + String resourceName = rstType.name().toLowerCase(Locale.ROOT) + ".rst.dnl.csv"; + URL resource = getResource(RstTmchUtils.class, resourceName); + try { + return Optional.of(ClaimsListParser.parse(readLines(resource, UTF_8))); + } catch (IOException fnfe) { + // Do not throw. + logger.atSevere().log("Could not load Claims list for %s in Sandbox.", rstType); + return Optional.empty(); + } + } + + private static Optional getSmdrList(RstType rstType) { + if (!RegistryEnvironment.get().equals(SANDBOX)) { + return Optional.empty(); + } + String resourceName = rstType.name().toLowerCase(Locale.ROOT) + ".rst.smdrl.csv"; + URL resource = getResource(RstTmchUtils.class, resourceName); + try { + return Optional.of(SmdrlCsvParser.parse(readLines(resource, UTF_8))); + } catch (IOException fnfe) { + // Do not throw. + logger.atSevere().log("Could not load Claims list for %s in Sandbox.", rstType); + return Optional.empty(); + } + } +} diff --git a/core/src/main/resources/google/registry/tmch/ote.rst.dnl.csv b/core/src/main/resources/google/registry/tmch/ote.rst.dnl.csv new file mode 100644 index 00000000000..237e8016b02 --- /dev/null +++ b/core/src/main/resources/google/registry/tmch/ote.rst.dnl.csv @@ -0,0 +1,10 @@ +1,2024-09-13T02:21:12.0Z +DNL,lookup-key,insertion-datetime +test---validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z +test--validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z +test-and-validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z +test-andvalidate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z +test-validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z +testand-validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z +testandvalidate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z +testvalidate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z diff --git a/core/src/main/resources/google/registry/tmch/ote.rst.smdrl.csv b/core/src/main/resources/google/registry/tmch/ote.rst.smdrl.csv new file mode 100644 index 00000000000..734a2a904b1 --- /dev/null +++ b/core/src/main/resources/google/registry/tmch/ote.rst.smdrl.csv @@ -0,0 +1,7 @@ +1,2022-11-22T01:49:36.9Z +smd-id,insertion-datetime +0000001761385117375880-65535,2013-07-15T00:00:00.0Z +0000001751501056761969-65535,2017-07-26T10:12:41.9Z +000000541526299609231-65535,2018-05-14T17:52:23.7Z +000000541602140609520-65535,2020-10-08T07:07:25.0Z +000000541669081776937-65535,2022-11-22T01:49:36.9Z diff --git a/core/src/main/resources/google/registry/tmch/prod.rst.dnl.csv b/core/src/main/resources/google/registry/tmch/prod.rst.dnl.csv new file mode 100644 index 00000000000..237e8016b02 --- /dev/null +++ b/core/src/main/resources/google/registry/tmch/prod.rst.dnl.csv @@ -0,0 +1,10 @@ +1,2024-09-13T02:21:12.0Z +DNL,lookup-key,insertion-datetime +test---validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z +test--validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z +test-and-validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z +test-andvalidate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z +test-validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z +testand-validate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z +testandvalidate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z +testvalidate,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z diff --git a/core/src/main/resources/google/registry/tmch/prod.rst.smdrl.csv b/core/src/main/resources/google/registry/tmch/prod.rst.smdrl.csv new file mode 100644 index 00000000000..734a2a904b1 --- /dev/null +++ b/core/src/main/resources/google/registry/tmch/prod.rst.smdrl.csv @@ -0,0 +1,7 @@ +1,2022-11-22T01:49:36.9Z +smd-id,insertion-datetime +0000001761385117375880-65535,2013-07-15T00:00:00.0Z +0000001751501056761969-65535,2017-07-26T10:12:41.9Z +000000541526299609231-65535,2018-05-14T17:52:23.7Z +000000541602140609520-65535,2020-10-08T07:07:25.0Z +000000541669081776937-65535,2022-11-22T01:49:36.9Z diff --git a/core/src/test/java/google/registry/tmch/RstTmchUtilsTest.java b/core/src/test/java/google/registry/tmch/RstTmchUtilsTest.java new file mode 100644 index 00000000000..bbbef41c4cd --- /dev/null +++ b/core/src/test/java/google/registry/tmch/RstTmchUtilsTest.java @@ -0,0 +1,117 @@ +// Copyright 2025 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.tmch; + +import static com.google.common.truth.Truth.assertThat; +import static google.registry.tmch.RstTmchUtils.getClaimsList; +import static google.registry.tmch.RstTmchUtils.getSmdrList; +import static google.registry.util.RegistryEnvironment.PRODUCTION; +import static google.registry.util.RegistryEnvironment.SANDBOX; + +import google.registry.util.RegistryEnvironment; +import java.util.stream.Stream; +import org.joda.time.DateTime; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class RstTmchUtilsTest { + + @ParameterizedTest + @MethodSource("provideTestCases") + @SuppressWarnings("unused") // testCaseName + void getClaimsList_production(String testCaseName, String tld) { + var currEnv = RegistryEnvironment.get(); + try { + PRODUCTION.setup(); + assertThat(getClaimsList(tld)).isEmpty(); + } finally { + currEnv.setup(); + } + } + + @ParameterizedTest + @MethodSource("provideTestCases") + @SuppressWarnings("unused") // testCaseName + void getSmdrList_production(String testCaseName, String tld) { + var currEnv = RegistryEnvironment.get(); + try { + PRODUCTION.setup(); + assertThat(getSmdrList(tld)).isEmpty(); + } finally { + currEnv.setup(); + } + } + + @ParameterizedTest + @MethodSource("provideTestCases") + @SuppressWarnings("unused") // testCaseName + void getClaimsList_sandbox(String testCaseName, String tld) { + var currEnv = RegistryEnvironment.get(); + try { + SANDBOX.setup(); + var claimsListOptional = getClaimsList(tld); + if (tld.equals("app")) { + assertThat(claimsListOptional).isEmpty(); + } else { + // Currently ote and prod have the same data. + var claimsList = claimsListOptional.get(); + assertThat(claimsList.getClaimKey("test-and-validate")).isPresent(); + var labelsToKeys = claimsList.getLabelsToKeys(); + assertThat(labelsToKeys).hasSize(8); + assertThat(labelsToKeys) + .containsEntry( + "test---validate", "2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001"); + } + } finally { + currEnv.setup(); + } + } + + @ParameterizedTest + @MethodSource("provideTestCases") + @SuppressWarnings("unused") // testCaseName + void getSmdrList_sandbox(String testCaseName, String tld) { + var currEnv = RegistryEnvironment.get(); + try { + SANDBOX.setup(); + var smdrListOptional = getSmdrList(tld); + if (tld.equals("app")) { + assertThat(smdrListOptional).isEmpty(); + } else { + // Currently ote and prod have the same data. + var smdrList = smdrListOptional.get(); + assertThat(smdrList.size()).isEqualTo(5); + assertThat( + smdrList.isSmdRevoked( + "000000541526299609231-65535", DateTime.parse("2018-05-14T17:52:23.6Z"))) + .isFalse(); + assertThat( + smdrList.isSmdRevoked( + "000000541526299609231-65535", DateTime.parse("2018-05-14T17:52:23.7Z"))) + .isTrue(); + } + } finally { + currEnv.setup(); + } + } + + private static Stream provideTestCases() { + return Stream.of( + Arguments.of("NotRST", "app"), + Arguments.of("OTE", "cc-rst-test-tld-1"), + Arguments.of("PROD", "zz--idn-123")); + } +} diff --git a/core/src/test/java/google/registry/tmch/TmchTestDataExpirationTest.java b/core/src/test/java/google/registry/tmch/TmchTestDataExpirationTest.java index a5448eb0900..797671aabca 100644 --- a/core/src/test/java/google/registry/tmch/TmchTestDataExpirationTest.java +++ b/core/src/test/java/google/registry/tmch/TmchTestDataExpirationTest.java @@ -57,7 +57,7 @@ void testActiveSignedMarkFiles_areValidAndNotExpired() throws Exception { String tmchData = loadFile(TmchTestDataExpirationTest.class, filePath); EncodedSignedMark smd = TmchData.readEncodedSignedMark(tmchData); try { - tmchUtils.verifyEncodedSignedMark(smd, DateTime.now(UTC)); + tmchUtils.verifyEncodedSignedMark("", smd, DateTime.now(UTC)); } catch (EppException e) { throw new AssertionError("Error verifying signed mark " + filePath, e); } From 077257639a78998d5b5ab9f1be21603b5279fa5e Mon Sep 17 00:00:00 2001 From: Weimin Yu Date: Tue, 30 Dec 2025 17:33:14 +0000 Subject: [PATCH 2/3] Addressing comments --- .../google/registry/tmch/RstTmchUtils.java | 51 ++++++++++--------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/core/src/main/java/google/registry/tmch/RstTmchUtils.java b/core/src/main/java/google/registry/tmch/RstTmchUtils.java index a3aaf0c609c..78cb223318d 100644 --- a/core/src/main/java/google/registry/tmch/RstTmchUtils.java +++ b/core/src/main/java/google/registry/tmch/RstTmchUtils.java @@ -17,8 +17,8 @@ import static com.google.common.base.Suppliers.memoize; import static com.google.common.io.Resources.getResource; import static com.google.common.io.Resources.readLines; -import static google.registry.tmch.RstTmchUtils.RstType.OTE; -import static google.registry.tmch.RstTmchUtils.RstType.PROD; +import static google.registry.tmch.RstTmchUtils.RstEnvironment.OTE; +import static google.registry.tmch.RstTmchUtils.RstEnvironment.PROD; import static google.registry.util.RegistryEnvironment.SANDBOX; import static java.nio.charset.StandardCharsets.UTF_8; @@ -44,37 +44,36 @@ public class RstTmchUtils { private static final FluentLogger logger = FluentLogger.forEnclosingClass(); - enum RstType { + /** + * The RST environments. + * + *

We conduct both OTE and PROD RST tests in Sandbox. + */ + enum RstEnvironment { OTE, PROD } - private static final ImmutableMap>> CLAIMS_CACHE = + private static final ImmutableMap>> CLAIMS_CACHE = ImmutableMap.of( - OTE, memoize(() -> getClaimList(OTE)), PROD, memoize(() -> getClaimList(PROD))); + OTE, memoize(() -> getClaimsList(OTE)), PROD, memoize(() -> getClaimsList(PROD))); - private static final ImmutableMap>> + private static final ImmutableMap>> SMDRL_CACHE = ImmutableMap.of( OTE, memoize(() -> getSmdrList(OTE)), PROD, memoize(() -> getSmdrList(PROD))); - /** - * Returns appropriate test labels if {@code tld} is for RST testing; otherwise returns {@code - * defaultList}. - */ + /** Returns appropriate test labels if {@code tld} is for RST testing; otherwise returns empty. */ public static Optional getClaimsList(String tld) { - return getRstType(tld).map(CLAIMS_CACHE::get).flatMap(Supplier::get); + return getRstEnvironment(tld).map(CLAIMS_CACHE::get).flatMap(Supplier::get); } - /** - * Returns appropriate test labels if {@code tld} is for RST testing; otherwise returns {@code - * defaultList}. - */ + /** Returns appropriate test labels if {@code tld} is for RST testing; otherwise returns empty. */ public static Optional getSmdrList(String tld) { - return getRstType(tld).map(SMDRL_CACHE::get).flatMap(Supplier::get); + return getRstEnvironment(tld).map(SMDRL_CACHE::get).flatMap(Supplier::get); } - static Optional getRstType(String tld) { + static Optional getRstEnvironment(String tld) { if (!RegistryEnvironment.get().equals(SANDBOX)) { return Optional.empty(); } @@ -87,32 +86,34 @@ static Optional getRstType(String tld) { return Optional.empty(); } - private static Optional getClaimList(RstType rstType) { + private static Optional getClaimsList(RstEnvironment rstEnvironment) { if (!RegistryEnvironment.get().equals(SANDBOX)) { return Optional.empty(); } - String resourceName = rstType.name().toLowerCase(Locale.ROOT) + ".rst.dnl.csv"; + String resourceName = rstEnvironment.name().toLowerCase(Locale.ROOT) + ".rst.dnl.csv"; URL resource = getResource(RstTmchUtils.class, resourceName); try { return Optional.of(ClaimsListParser.parse(readLines(resource, UTF_8))); - } catch (IOException fnfe) { + } catch (IOException e) { // Do not throw. - logger.atSevere().log("Could not load Claims list for %s in Sandbox.", rstType); + logger.atSevere().withCause(e).log( + "Could not load Claims list for %s in Sandbox.", rstEnvironment); return Optional.empty(); } } - private static Optional getSmdrList(RstType rstType) { + private static Optional getSmdrList(RstEnvironment rstEnvironment) { if (!RegistryEnvironment.get().equals(SANDBOX)) { return Optional.empty(); } - String resourceName = rstType.name().toLowerCase(Locale.ROOT) + ".rst.smdrl.csv"; + String resourceName = rstEnvironment.name().toLowerCase(Locale.ROOT) + ".rst.smdrl.csv"; URL resource = getResource(RstTmchUtils.class, resourceName); try { return Optional.of(SmdrlCsvParser.parse(readLines(resource, UTF_8))); - } catch (IOException fnfe) { + } catch (IOException e) { // Do not throw. - logger.atSevere().log("Could not load Claims list for %s in Sandbox.", rstType); + logger.atSevere().withCause(e).log( + "Could not load Claims list for %s in Sandbox.", rstEnvironment); return Optional.empty(); } } From aa16ea90701012ffd414d93ac04c0d64b8f51b42 Mon Sep 17 00:00:00 2001 From: Weimin Yu Date: Tue, 30 Dec 2025 19:38:16 +0000 Subject: [PATCH 3/3] Addressing comments --- .../google/registry/tmch/RstTmchUtils.java | 4 +- .../registry/tmch/RstTmchUtilsIntTest.java | 161 ++++++++++++++++++ 2 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 core/src/test/java/google/registry/tmch/RstTmchUtilsIntTest.java diff --git a/core/src/main/java/google/registry/tmch/RstTmchUtils.java b/core/src/main/java/google/registry/tmch/RstTmchUtils.java index 78cb223318d..e2e581b4ef7 100644 --- a/core/src/main/java/google/registry/tmch/RstTmchUtils.java +++ b/core/src/main/java/google/registry/tmch/RstTmchUtils.java @@ -97,7 +97,7 @@ private static Optional getClaimsList(RstEnvironment rstEnvironment) } catch (IOException e) { // Do not throw. logger.atSevere().withCause(e).log( - "Could not load Claims list for %s in Sandbox.", rstEnvironment); + "Could not load Claims list %s for %s in Sandbox.", resourceName, rstEnvironment); return Optional.empty(); } } @@ -113,7 +113,7 @@ private static Optional getSmdrList(RstEnvironment rst } catch (IOException e) { // Do not throw. logger.atSevere().withCause(e).log( - "Could not load Claims list for %s in Sandbox.", rstEnvironment); + "Could not load SMDR list %s for %s in Sandbox.", resourceName, rstEnvironment); return Optional.empty(); } } diff --git a/core/src/test/java/google/registry/tmch/RstTmchUtilsIntTest.java b/core/src/test/java/google/registry/tmch/RstTmchUtilsIntTest.java new file mode 100644 index 00000000000..ae809780038 --- /dev/null +++ b/core/src/test/java/google/registry/tmch/RstTmchUtilsIntTest.java @@ -0,0 +1,161 @@ +// Copyright 2025 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.tmch; + +import static com.google.common.truth.Truth.assertThat; +import static google.registry.persistence.transaction.TransactionManagerFactory.tm; +import static google.registry.util.RegistryEnvironment.PRODUCTION; +import static google.registry.util.RegistryEnvironment.SANDBOX; +import static org.joda.time.DateTime.now; +import static org.joda.time.DateTimeZone.UTC; + +import com.google.common.base.Splitter; +import google.registry.model.smd.SignedMarkRevocationList; +import google.registry.model.smd.SignedMarkRevocationListDao; +import google.registry.model.tmch.ClaimsListDao; +import google.registry.persistence.transaction.JpaTestExtensions; +import google.registry.testing.FakeClock; +import google.registry.util.RegistryEnvironment; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class RstTmchUtilsIntTest { + private final FakeClock clock = new FakeClock(); + + @RegisterExtension + final JpaTestExtensions.JpaIntegrationTestExtension jpa = + new JpaTestExtensions.Builder().withClock(clock).buildIntegrationTestExtension(); + + private static final String TMCH_CLAIM_LABEL = "tmch"; + // RST label found in *.rst.dnl.csv resources. Currently both files are identical + private static final String RST_CLAIM_LABEL = "test--validate"; + + private static final String TMCH_SMD_ID = "tmch"; + // RST label found in *.rst.smdrl.csv resources. Currently both files are identical + private static final String RST_SMD_ID = "0000001761385117375880-65535"; + + private static final String TMCH_DNL = + """ + 1,2024-09-13T02:21:12.0Z + DNL,lookup-key,insertion-datetime + LABEL,2024091300/6/a/b/arJyPPf2CK7f21bVGne0qMgW0000000001,2024-09-13T02:21:12.0Z + """ + .replace("LABEL", TMCH_CLAIM_LABEL); + + private static final String TMCH_SMDRL = + """ + 1,2022-11-22T01:49:36.9Z + smd-id,insertion-datetime + ID,2013-07-15T00:00:00.0Z + """ + .replace("ID", TMCH_SMD_ID); + + @BeforeEach + void setup() throws Exception { + Splitter lineSplitter = Splitter.on("\n").omitEmptyStrings().trimResults(); + tm().transact( + () -> ClaimsListDao.save(ClaimsListParser.parse(lineSplitter.splitToList(TMCH_DNL)))); + tm().transact( + () -> + SignedMarkRevocationListDao.save( + SmdrlCsvParser.parse(lineSplitter.splitToList(TMCH_SMDRL)))); + } + + @ParameterizedTest + @MethodSource("provideTestCases") + @SuppressWarnings("unused") // testCaseName + void getClaimsList_production(String testCaseName, String tld) { + var currEnv = RegistryEnvironment.get(); + try { + PRODUCTION.setup(); + var claimsList = ClaimsListDao.get(tld); + assertThat(claimsList.getClaimKey(TMCH_CLAIM_LABEL)).isPresent(); + assertThat(claimsList.getClaimKey(RST_CLAIM_LABEL)).isEmpty(); + } finally { + currEnv.setup(); + } + } + + @ParameterizedTest + @MethodSource("provideTestCases") + @SuppressWarnings("unused") // testCaseName + void getSmdrList_production(String testCaseName, String tld) { + var currEnv = RegistryEnvironment.get(); + try { + PRODUCTION.setup(); + var smdrl = SignedMarkRevocationList.get(tld); + assertThat(smdrl.isSmdRevoked(TMCH_SMD_ID, now(UTC))).isTrue(); + assertThat(smdrl.isSmdRevoked(RST_SMD_ID, now(UTC))).isFalse(); + assertThat(smdrl.size()).isEqualTo(1); + } finally { + currEnv.setup(); + } + } + + @ParameterizedTest + @MethodSource("provideTestCases") + @SuppressWarnings("unused") // testCaseName + void getClaimsList_sandbox(String testCaseName, String tld) { + var currEnv = RegistryEnvironment.get(); + try { + SANDBOX.setup(); + var claimsList = ClaimsListDao.get(tld); + if (tld.equals("app")) { + assertThat(claimsList.getClaimKey(TMCH_CLAIM_LABEL)).isPresent(); + assertThat(claimsList.getClaimKey(RST_CLAIM_LABEL)).isEmpty(); + } else { + assertThat(claimsList.getClaimKey(TMCH_CLAIM_LABEL)).isEmpty(); + // Currently ote and prod have the same data. + assertThat(claimsList.getClaimKey(RST_CLAIM_LABEL)).isPresent(); + } + } finally { + currEnv.setup(); + } + } + + @ParameterizedTest + @MethodSource("provideTestCases") + @SuppressWarnings("unused") // testCaseName + void getSmdrList_sandbox(String testCaseName, String tld) { + var currEnv = RegistryEnvironment.get(); + try { + SANDBOX.setup(); + var smdrList = SignedMarkRevocationList.get(tld); + if (tld.equals("app")) { + assertThat(smdrList.size()).isEqualTo(1); + assertThat(smdrList.isSmdRevoked(TMCH_SMD_ID, now(UTC))).isTrue(); + assertThat(smdrList.isSmdRevoked(RST_SMD_ID, now(UTC))).isFalse(); + } else { + // Currently ote and prod have the same data. + assertThat(smdrList.size()).isEqualTo(5); + assertThat(smdrList.isSmdRevoked(TMCH_SMD_ID, now())).isFalse(); + assertThat(smdrList.isSmdRevoked(RST_SMD_ID, now())).isTrue(); + } + } finally { + currEnv.setup(); + } + } + + private static Stream provideTestCases() { + return Stream.of( + Arguments.of("NotRST", "app"), + Arguments.of("OTE", "cc-rst-test-tld-1"), + Arguments.of("PROD", "zz--idn-123")); + } +}