diff --git a/java/google/registry/model/OteAccountBuilder.java b/java/google/registry/model/OteAccountBuilder.java index 03506889b..e3c795022 100644 --- a/java/google/registry/model/OteAccountBuilder.java +++ b/java/google/registry/model/OteAccountBuilder.java @@ -361,7 +361,7 @@ public final class OteAccountBuilder { } /** Returns the ClientIds of the OT&E, with the TLDs each has access to. */ - private static ImmutableMap createClientIdToTldMap(String baseClientId) { + static ImmutableMap createClientIdToTldMap(String baseClientId) { checkArgument( REGISTRAR_PATTERN.matcher(baseClientId).matches(), "Invalid registrar name: %s", diff --git a/java/google/registry/model/OteStats.java b/java/google/registry/model/OteStats.java new file mode 100644 index 000000000..393d45bf0 --- /dev/null +++ b/java/google/registry/model/OteStats.java @@ -0,0 +1,277 @@ +// Copyright 2018 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.model; + +import static com.google.common.base.Predicates.equalTo; +import static com.google.common.collect.ImmutableList.toImmutableList; +import static google.registry.model.eppcommon.EppXmlTransformer.unmarshal; +import static google.registry.model.ofy.ObjectifyService.ofy; +import static google.registry.util.CollectionUtils.isNullOrEmpty; +import static google.registry.util.DomainNameUtils.ACE_PREFIX; + +import com.google.common.base.Ascii; +import com.google.common.base.Predicates; +import com.google.common.collect.HashMultiset; +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Multiset; +import com.googlecode.objectify.Key; +import com.googlecode.objectify.cmd.Query; +import google.registry.model.domain.DomainCommand; +import google.registry.model.domain.fee.FeeCreateCommandExtension; +import google.registry.model.domain.launch.LaunchCreateExtension; +import google.registry.model.domain.secdns.SecDnsCreateExtension; +import google.registry.model.domain.secdns.SecDnsUpdateExtension; +import google.registry.model.eppinput.EppInput; +import google.registry.model.eppinput.EppInput.ResourceCommandWrapper; +import google.registry.model.host.HostCommand; +import google.registry.model.reporting.HistoryEntry; +import google.registry.model.reporting.HistoryEntry.Type; +import google.registry.xml.XmlException; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** Represents stats derived from HistoryEntry objects on actions taken by registrars. */ +public class OteStats { + + /** + * Returns the statistics about the OT&E actions that have been taken by a particular registrar. + */ + public static OteStats getFromRegistrar(String registrarName) { + return new OteStats().recordRegistrarHistory(registrarName); + } + + private OteStats() {} + + private static final Predicate HAS_CLAIMS_NOTICE = + eppInput -> { + Optional launchCreate = + eppInput.getSingleExtension(LaunchCreateExtension.class); + return launchCreate.isPresent() && launchCreate.get().getNotice() != null; + }; + + private static final Predicate HAS_SEC_DNS = + eppInput -> + eppInput.getSingleExtension(SecDnsCreateExtension.class).isPresent() + || eppInput.getSingleExtension(SecDnsUpdateExtension.class).isPresent(); + + private static final Predicate IS_SUNRISE = + eppInput -> { + Optional launchCreate = + eppInput.getSingleExtension(LaunchCreateExtension.class); + return launchCreate.isPresent() && !isNullOrEmpty(launchCreate.get().getSignedMarks()); + }; + + private static final Predicate IS_IDN = + eppInput -> + ((DomainCommand.Create) + ((ResourceCommandWrapper) eppInput.getCommandWrapper().getCommand()) + .getResourceCommand()) + .getFullyQualifiedDomainName() + .startsWith(ACE_PREFIX); + + private static final Predicate IS_SUBORDINATE = + eppInput -> + !isNullOrEmpty( + ((HostCommand.Create) + ((ResourceCommandWrapper) eppInput.getCommandWrapper().getCommand()) + .getResourceCommand()) + .getInetAddresses()); + + /** Enum defining the distinct statistics (types of registrar actions) to record. */ + public enum StatType { + CONTACT_CREATES(0, equalTo(Type.CONTACT_CREATE)), + CONTACT_DELETES(0, equalTo(Type.CONTACT_DELETE)), + CONTACT_TRANSFER_APPROVES(0, equalTo(Type.CONTACT_TRANSFER_APPROVE)), + CONTACT_TRANSFER_CANCELS(0, equalTo(Type.CONTACT_TRANSFER_CANCEL)), + CONTACT_TRANSFER_REJECTS(0, equalTo(Type.CONTACT_TRANSFER_REJECT)), + CONTACT_TRANSFER_REQUESTS(0, equalTo(Type.CONTACT_TRANSFER_REQUEST)), + CONTACT_UPDATES(0, equalTo(Type.CONTACT_UPDATE)), + DOMAIN_APPLICATION_CREATES(0, equalTo(Type.DOMAIN_APPLICATION_CREATE)), + DOMAIN_APPLICATION_CREATES_LANDRUSH( + 0, equalTo(Type.DOMAIN_APPLICATION_CREATE), IS_SUNRISE.negate()), + DOMAIN_APPLICATION_CREATES_SUNRISE(0, equalTo(Type.DOMAIN_APPLICATION_CREATE), IS_SUNRISE), + DOMAIN_APPLICATION_DELETES(0, equalTo(Type.DOMAIN_APPLICATION_DELETE)), + DOMAIN_APPLICATION_UPDATES(0, equalTo(Type.DOMAIN_APPLICATION_UPDATE)), + DOMAIN_AUTORENEWS(0, equalTo(Type.DOMAIN_AUTORENEW)), + DOMAIN_CREATES(0, equalTo(Type.DOMAIN_CREATE)), + DOMAIN_CREATES_ASCII(1, equalTo(Type.DOMAIN_CREATE), IS_IDN.negate()), + DOMAIN_CREATES_IDN(1, equalTo(Type.DOMAIN_CREATE), IS_IDN), + DOMAIN_CREATES_START_DATE_SUNRISE(1, equalTo(Type.DOMAIN_CREATE), IS_SUNRISE), + DOMAIN_CREATES_WITH_CLAIMS_NOTICE(1, equalTo(Type.DOMAIN_CREATE), HAS_CLAIMS_NOTICE), + DOMAIN_CREATES_WITH_FEE( + 1, + equalTo(Type.DOMAIN_CREATE), + eppInput -> eppInput.getSingleExtension(FeeCreateCommandExtension.class).isPresent()), + DOMAIN_CREATES_WITH_SEC_DNS(1, equalTo(Type.DOMAIN_CREATE), HAS_SEC_DNS), + DOMAIN_CREATES_WITHOUT_SEC_DNS(0, equalTo(Type.DOMAIN_CREATE), HAS_SEC_DNS.negate()), + DOMAIN_DELETES(2, equalTo(Type.DOMAIN_DELETE)), + DOMAIN_RENEWS(0, equalTo(Type.DOMAIN_RENEW)), + DOMAIN_RESTORES(1, equalTo(Type.DOMAIN_RESTORE)), + DOMAIN_TRANSFER_APPROVES(1, equalTo(Type.DOMAIN_TRANSFER_APPROVE)), + DOMAIN_TRANSFER_CANCELS(1, equalTo(Type.DOMAIN_TRANSFER_CANCEL)), + DOMAIN_TRANSFER_REJECTS(1, equalTo(Type.DOMAIN_TRANSFER_REJECT)), + DOMAIN_TRANSFER_REQUESTS(1, equalTo(Type.DOMAIN_TRANSFER_REQUEST)), + DOMAIN_UPDATES(0, equalTo(Type.DOMAIN_UPDATE)), + DOMAIN_UPDATES_WITH_SEC_DNS(1, equalTo(Type.DOMAIN_UPDATE), HAS_SEC_DNS), + DOMAIN_UPDATES_WITHOUT_SEC_DNS(0, equalTo(Type.DOMAIN_UPDATE), HAS_SEC_DNS.negate()), + HOST_CREATES(0, equalTo(Type.HOST_CREATE)), + HOST_CREATES_EXTERNAL(0, equalTo(Type.HOST_CREATE), IS_SUBORDINATE.negate()), + HOST_CREATES_SUBORDINATE(1, equalTo(Type.HOST_CREATE), IS_SUBORDINATE), + HOST_DELETES(1, equalTo(Type.HOST_DELETE)), + HOST_UPDATES(1, equalTo(Type.HOST_UPDATE)), + UNCLASSIFIED_FLOWS(0, Predicates.alwaysFalse()); + + /** StatTypes with a non-zero requirement */ + public static final ImmutableList REQUIRED_STAT_TYPES = + Arrays.stream(values()) + .filter(statType -> statType.requirement > 0) + .collect(toImmutableList()); + + /** Required number of times registrars must complete this action. */ + private final int requirement; + + /** Filter to check the HistoryEntry Type */ + @SuppressWarnings("ImmutableEnumChecker") // Predicates are immutable. + private final Predicate typeFilter; + + /** Optional filter on the EppInput. */ + @SuppressWarnings("ImmutableEnumChecker") // Predicates are immutable. + private final Optional> eppInputFilter; + + StatType(int requirement, Predicate typeFilter) { + this(requirement, typeFilter, null); + } + + StatType( + int requirement, + Predicate typeFilter, + Predicate eppInputFilter) { + this.requirement = requirement; + this.typeFilter = typeFilter; + if (eppInputFilter == null) { + this.eppInputFilter = Optional.empty(); + } else { + this.eppInputFilter = Optional.of(eppInputFilter); + } + } + + /** Returns the number of times this StatType must be performed. */ + public int getRequirement() { + return requirement; + } + + /** Returns a more human-readable translation of the enum constant. */ + private String description() { + return Ascii.toLowerCase(this.name().replace('_', ' ')); + } + + /** + * Check if the {@link HistoryEntry} type matches as well as the {@link EppInput} if supplied. + */ + private boolean matches(HistoryEntry.Type historyType, Optional eppInput) { + if (eppInputFilter.isPresent() && eppInput.isPresent()) { + return typeFilter.test(historyType) && eppInputFilter.get().test(eppInput.get()); + } else { + return typeFilter.test(historyType); + } + } + } + + /** Stores counts of how many times each action type was performed. */ + private final Multiset statCounts = HashMultiset.create(); + + /** + * Records data on what actions have been performed by the four numbered OT&E variants of the + * registrar name. + * + *

Stops when it notices that all tests have passed. + */ + private OteStats recordRegistrarHistory(String registrarName) { + ImmutableCollection clientIds = + OteAccountBuilder.createClientIdToTldMap(registrarName).keySet(); + + Query query = + ofy() + .load() + .type(HistoryEntry.class) + .filter("clientId in", clientIds) + .order("modificationTime"); + for (HistoryEntry historyEntry : query) { + try { + record(historyEntry); + } catch (XmlException e) { + throw new RuntimeException("Couldn't parse history entry " + Key.create(historyEntry), e); + } + // Break out early if all tests were passed. + if (wereAllTestsPassed()) { + break; + } + } + return this; + } + + /** Interprets the data in the provided HistoryEntry and increments counters. */ + private void record(final HistoryEntry historyEntry) throws XmlException { + byte[] xmlBytes = historyEntry.getXmlBytes(); + // xmlBytes can be null on contact create and update for safe-harbor compliance. + final Optional eppInput = + (xmlBytes == null) ? Optional.empty() : Optional.of(unmarshal(EppInput.class, xmlBytes)); + if (!statCounts.addAll( + EnumSet.allOf(StatType.class).stream() + .filter(statType -> statType.matches(historyEntry.getType(), eppInput)) + .collect(toImmutableList()))) { + statCounts.add(StatType.UNCLASSIFIED_FLOWS); + } + } + + private boolean wereAllTestsPassed() { + return Arrays.stream(StatType.values()).allMatch(s -> statCounts.count(s) >= s.requirement); + } + + /** Returns the total number of actions taken */ + public int getSize() { + return statCounts.size(); + } + + /** Returns the number of times that a particular StatType was seen */ + public int getCount(StatType statType) { + return statCounts.count(statType); + } + + /** + * Returns a list of failures, any cases where the passed stats fail to meet the required + * thresholds, or the empty list if all requirements are met. + */ + public ImmutableList getFailures() { + return StatType.REQUIRED_STAT_TYPES.stream() + .filter(statType -> statCounts.count(statType) < statType.requirement) + .collect(toImmutableList()); + } + + /** Returns a string showing all possible actions and how many times each was performed. */ + @Override + public String toString() { + return String.format( + "%s\nTOTAL: %d", + EnumSet.allOf(StatType.class).stream() + .map(stat -> String.format("%s: %d", stat.description(), statCounts.count(stat))) + .collect(Collectors.joining("\n")), + statCounts.size()); + } +} diff --git a/java/google/registry/tools/server/VerifyOteAction.java b/java/google/registry/tools/server/VerifyOteAction.java index 814b1903b..94b48e413 100644 --- a/java/google/registry/tools/server/VerifyOteAction.java +++ b/java/google/registry/tools/server/VerifyOteAction.java @@ -14,47 +14,19 @@ package google.registry.tools.server; -import static com.google.common.base.Predicates.equalTo; -import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.Maps.toMap; -import static google.registry.model.eppcommon.EppXmlTransformer.unmarshal; -import static google.registry.model.ofy.ObjectifyService.ofy; -import static google.registry.util.CollectionUtils.isNullOrEmpty; -import static google.registry.util.DomainNameUtils.ACE_PREFIX; -import com.google.common.base.Ascii; import com.google.common.base.Joiner; -import com.google.common.base.Predicates; -import com.google.common.collect.HashMultiset; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Multiset; -import com.googlecode.objectify.Key; -import com.googlecode.objectify.cmd.Query; -import google.registry.model.domain.DomainCommand; -import google.registry.model.domain.fee.FeeCreateCommandExtension; -import google.registry.model.domain.launch.LaunchCreateExtension; -import google.registry.model.domain.secdns.SecDnsCreateExtension; -import google.registry.model.domain.secdns.SecDnsUpdateExtension; -import google.registry.model.eppinput.EppInput; -import google.registry.model.eppinput.EppInput.ResourceCommandWrapper; -import google.registry.model.host.HostCommand; -import google.registry.model.reporting.HistoryEntry; -import google.registry.model.reporting.HistoryEntry.Type; +import com.google.common.collect.Maps; +import google.registry.model.OteStats; +import google.registry.model.OteStats.StatType; import google.registry.request.Action; import google.registry.request.JsonActionRunner; import google.registry.request.JsonActionRunner.JsonAction; import google.registry.request.auth.Auth; -import google.registry.xml.XmlException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.EnumSet; import java.util.List; import java.util.Map; -import java.util.Optional; -import java.util.function.Predicate; import java.util.stream.Collectors; -import java.util.stream.IntStream; -import java.util.stream.Stream; import javax.inject.Inject; /** @@ -62,10 +34,9 @@ import javax.inject.Inject; * OT&E commands that have been run just previously to verification may not be picked up yet. */ @Action( - path = VerifyOteAction.PATH, - method = Action.Method.POST, - auth = Auth.AUTH_INTERNAL_OR_ADMIN -) + path = VerifyOteAction.PATH, + method = Action.Method.POST, + auth = Auth.AUTH_INTERNAL_OR_ADMIN) public class VerifyOteAction implements Runnable, JsonAction { public static final String PATH = "/_dr/admin/verifyOte"; @@ -84,252 +55,31 @@ public class VerifyOteAction implements Runnable, JsonAction { @SuppressWarnings("unchecked") public Map handleJsonRequest(Map json) { final boolean summarize = Boolean.parseBoolean((String) json.get("summarize")); - return toMap( - (List) json.get("registrars"), registrar -> checkRegistrar(registrar, summarize)); + + Map registrarResults = + toMap((List) json.get("registrars"), OteStats::getFromRegistrar); + return Maps.transformValues(registrarResults, stats -> transformOteStats(stats, summarize)); } - /** Checks whether the provided registrar has passed OT&E and returns relevant information. */ - private String checkRegistrar(String registrarName, boolean summarize) { - HistoryEntryStats historyEntryStats = - new HistoryEntryStats().recordRegistrarHistory(registrarName); - List failureMessages = historyEntryStats.findFailures(); - int testsPassed = StatType.NUM_REQUIREMENTS - failureMessages.size(); - String status = failureMessages.isEmpty() ? "PASS" : "FAIL"; + private String transformOteStats(OteStats stats, boolean summarize) { + List failures = stats.getFailures(); + int numRequiredTests = StatType.REQUIRED_STAT_TYPES.size(); + int testsPassed = numRequiredTests - failures.size(); + + String status = failures.isEmpty() ? "PASS" : "FAIL"; return summarize ? String.format( "# actions: %4d - Reqs: [%s] %2d/%2d - Overall: %s", - historyEntryStats.statCounts.size(), - historyEntryStats.toSummary(), - testsPassed, - StatType.NUM_REQUIREMENTS, - status) + stats.getSize(), getSummary(stats), testsPassed, numRequiredTests, status) : String.format( "%s\n%s\nRequirements passed: %2d/%2d\nOverall OT&E status: %s\n", - historyEntryStats, - Joiner.on('\n').join(failureMessages), - testsPassed, - StatType.NUM_REQUIREMENTS, - status); + stats, Joiner.on('\n').join(failures), testsPassed, numRequiredTests, status); } - private static final Predicate HAS_CLAIMS_NOTICE = - eppInput -> { - Optional launchCreate = - eppInput.getSingleExtension(LaunchCreateExtension.class); - return launchCreate.isPresent() && launchCreate.get().getNotice() != null; - }; - - private static final Predicate HAS_SEC_DNS = - eppInput -> - (eppInput.getSingleExtension(SecDnsCreateExtension.class).isPresent()) - || (eppInput.getSingleExtension(SecDnsUpdateExtension.class).isPresent()); - private static final Predicate IS_SUNRISE = - eppInput -> { - Optional launchCreate = - eppInput.getSingleExtension(LaunchCreateExtension.class); - return launchCreate.isPresent() && !isNullOrEmpty(launchCreate.get().getSignedMarks()); - }; - - private static final Predicate IS_IDN = - eppInput -> - ((DomainCommand.Create) - ((ResourceCommandWrapper) eppInput.getCommandWrapper().getCommand()) - .getResourceCommand()) - .getFullyQualifiedDomainName() - .startsWith(ACE_PREFIX); - private static final Predicate IS_SUBORDINATE = - eppInput -> - !isNullOrEmpty( - ((HostCommand.Create) - ((ResourceCommandWrapper) eppInput.getCommandWrapper().getCommand()) - .getResourceCommand()) - .getInetAddresses()); - /** Enum defining the distinct statistics (types of registrar actions) to record. */ - public enum StatType { - CONTACT_CREATES(0, equalTo(Type.CONTACT_CREATE)), - CONTACT_DELETES(0, equalTo(Type.CONTACT_DELETE)), - CONTACT_TRANSFER_APPROVES(0, equalTo(Type.CONTACT_TRANSFER_APPROVE)), - CONTACT_TRANSFER_CANCELS(0, equalTo(Type.CONTACT_TRANSFER_CANCEL)), - CONTACT_TRANSFER_REJECTS(0, equalTo(Type.CONTACT_TRANSFER_REJECT)), - CONTACT_TRANSFER_REQUESTS(0, equalTo(Type.CONTACT_TRANSFER_REQUEST)), - CONTACT_UPDATES(0, equalTo(Type.CONTACT_UPDATE)), - DOMAIN_APPLICATION_CREATES(0, equalTo(Type.DOMAIN_APPLICATION_CREATE)), - DOMAIN_APPLICATION_CREATES_LANDRUSH( - 0, equalTo(Type.DOMAIN_APPLICATION_CREATE), IS_SUNRISE.negate()), - DOMAIN_APPLICATION_CREATES_SUNRISE(0, equalTo(Type.DOMAIN_APPLICATION_CREATE), IS_SUNRISE), - DOMAIN_APPLICATION_DELETES(0, equalTo(Type.DOMAIN_APPLICATION_DELETE)), - DOMAIN_APPLICATION_UPDATES(0, equalTo(Type.DOMAIN_APPLICATION_UPDATE)), - DOMAIN_AUTORENEWS(0, equalTo(Type.DOMAIN_AUTORENEW)), - DOMAIN_CREATES(0, equalTo(Type.DOMAIN_CREATE)), - DOMAIN_CREATES_ASCII(1, equalTo(Type.DOMAIN_CREATE), IS_IDN.negate()), - DOMAIN_CREATES_IDN(1, equalTo(Type.DOMAIN_CREATE), IS_IDN), - DOMAIN_CREATES_START_DATE_SUNRISE(1, equalTo(Type.DOMAIN_CREATE), IS_SUNRISE), - DOMAIN_CREATES_WITH_CLAIMS_NOTICE(1, equalTo(Type.DOMAIN_CREATE), HAS_CLAIMS_NOTICE), - DOMAIN_CREATES_WITH_FEE( - 1, - equalTo(Type.DOMAIN_CREATE), - eppInput -> eppInput.getSingleExtension(FeeCreateCommandExtension.class).isPresent()), - DOMAIN_CREATES_WITH_SEC_DNS(1, equalTo(Type.DOMAIN_CREATE), HAS_SEC_DNS), - DOMAIN_CREATES_WITHOUT_SEC_DNS(0, equalTo(Type.DOMAIN_CREATE), HAS_SEC_DNS.negate()), - DOMAIN_DELETES(2, equalTo(Type.DOMAIN_DELETE)), - DOMAIN_RENEWS(0, equalTo(Type.DOMAIN_RENEW)), - DOMAIN_RESTORES(1, equalTo(Type.DOMAIN_RESTORE)), - DOMAIN_TRANSFER_APPROVES(1, equalTo(Type.DOMAIN_TRANSFER_APPROVE)), - DOMAIN_TRANSFER_CANCELS(1, equalTo(Type.DOMAIN_TRANSFER_CANCEL)), - DOMAIN_TRANSFER_REJECTS(1, equalTo(Type.DOMAIN_TRANSFER_REJECT)), - DOMAIN_TRANSFER_REQUESTS(1, equalTo(Type.DOMAIN_TRANSFER_REQUEST)), - DOMAIN_UPDATES(0, equalTo(Type.DOMAIN_UPDATE)), - DOMAIN_UPDATES_WITH_SEC_DNS(1, equalTo(Type.DOMAIN_UPDATE), HAS_SEC_DNS), - DOMAIN_UPDATES_WITHOUT_SEC_DNS(0, equalTo(Type.DOMAIN_UPDATE), HAS_SEC_DNS.negate()), - HOST_CREATES(0, equalTo(Type.HOST_CREATE)), - HOST_CREATES_EXTERNAL(0, equalTo(Type.HOST_CREATE), IS_SUBORDINATE.negate()), - HOST_CREATES_SUBORDINATE(1, equalTo(Type.HOST_CREATE), IS_SUBORDINATE), - HOST_DELETES(1, equalTo(Type.HOST_DELETE)), - HOST_UPDATES(1, equalTo(Type.HOST_UPDATE)), - UNCLASSIFIED_FLOWS(0, Predicates.alwaysFalse()); - - /** The number of StatTypes with a non-zero requirement. */ - private static final int NUM_REQUIREMENTS = - (int) Stream.of(values()).filter(statType -> statType.requirement > 0).count(); - - /** Required number of times registrars must complete this action. */ - final int requirement; - - /** Filter to check the HistoryEntry Type */ - @SuppressWarnings("ImmutableEnumChecker") // Predicates are immutable. - private final Predicate typeFilter; - - /** Optional filter on the EppInput. */ - @SuppressWarnings("ImmutableEnumChecker") // Predicates are immutable. - private final Optional> eppInputFilter; - - StatType(int requirement, Predicate typeFilter) { - this(requirement, typeFilter, null); - } - - StatType( - int requirement, - Predicate typeFilter, - Predicate eppInputFilter) { - this.requirement = requirement; - this.typeFilter = typeFilter; - if (eppInputFilter == null) { - this.eppInputFilter = Optional.empty(); - } else { - this.eppInputFilter = Optional.of(eppInputFilter); - } - } - - /** Returns a more human-readable translation of the enum constant. */ - String description() { - return Ascii.toLowerCase(this.name().replace('_', ' ')); - } - - /** - * Check if the {@link HistoryEntry} type matches as well as the {@link EppInput} if supplied. - */ - boolean matches(HistoryEntry.Type historyType, Optional eppInput) { - if (eppInputFilter.isPresent() && eppInput.isPresent()) { - return typeFilter.test(historyType) && eppInputFilter.get().test(eppInput.get()); - } else { - return typeFilter.test(historyType); - } - } - } - - /** Class to represent stats derived from HistoryEntry objects on actions taken by registrars. */ - static class HistoryEntryStats { - - /** Stores counts of how many times each action type was performed. */ - Multiset statCounts = HashMultiset.create(); - - /** - * Records data in the passed historyEntryStats object on what actions have been performed by - * the four numbered OT&E variants of the registrar name. - * - *

Stops when it notices that all tests have passed. - */ - HistoryEntryStats recordRegistrarHistory(String registrarName) { - ImmutableList clientIds = - IntStream.rangeClosed(1, 4) - .mapToObj(i -> String.format("%s-%d", registrarName, i)) - .collect(toImmutableList()); - - Query query = - ofy() - .load() - .type(HistoryEntry.class) - .filter("clientId in", clientIds) - .order("modificationTime"); - for (HistoryEntry historyEntry : query) { - try { - record(historyEntry); - } catch (XmlException e) { - throw new RuntimeException("Couldn't parse history entry " + Key.create(historyEntry), e); - } - // Break out early if all tests were passed. - if (wereAllTestsPassed()) { - break; - } - } - return this; - } - - /** Interprets the data in the provided HistoryEntry and increments counters. */ - void record(final HistoryEntry historyEntry) throws XmlException { - byte[] xmlBytes = historyEntry.getXmlBytes(); - // xmlBytes can be null on contact create and update for safe-harbor compliance. - final Optional eppInput = - (xmlBytes == null) ? Optional.empty() : Optional.of(unmarshal(EppInput.class, xmlBytes)); - if (!statCounts.addAll( - EnumSet.allOf(StatType.class).stream() - .filter(statType -> statType.matches(historyEntry.getType(), eppInput)) - .collect(toImmutableList()))) { - statCounts.add(StatType.UNCLASSIFIED_FLOWS); - } - } - - boolean wereAllTestsPassed() { - return Arrays.stream(StatType.values()).allMatch(s -> statCounts.count(s) >= s.requirement); - } - - /** - * Returns a list of failure messages describing any cases where the passed stats fail to meet - * the required thresholds, or the empty list if all requirements are met. - */ - List findFailures() { - List messages = new ArrayList<>(); - for (StatType statType : StatType.values()) { - if (statCounts.count(statType) < statType.requirement) { - messages.add( - String.format( - "Failure: %s %s found.", - (statType.requirement == 1 ? "No" : "Not enough"), statType.description())); - } - } - return messages; - } - - /** Returns a string showing all possible actions and how many times each was performed. */ - @Override - public String toString() { - return String.format( - "%s\nTOTAL: %d", - EnumSet.allOf(StatType.class) - .stream() - .map(stat -> String.format("%s: %d", stat.description(), statCounts.count(stat))) - .collect(Collectors.joining("\n")), - statCounts.size()); - } - - /** Returns a string showing the results of each test, one character per test. */ - String toSummary() { - return EnumSet.allOf(StatType.class) - .stream() - .filter(statType -> statType.requirement > 0) - .sorted() - .map(statType -> (statCounts.count(statType) < statType.requirement) ? "." : "-") - .collect(Collectors.joining("")); - } + private String getSummary(OteStats stats) { + return StatType.REQUIRED_STAT_TYPES.stream() + .sorted() + .map(statType -> (stats.getCount(statType) < statType.getRequirement()) ? "." : "-") + .collect(Collectors.joining("")); } } diff --git a/javatests/google/registry/model/OteStatsTest.java b/javatests/google/registry/model/OteStatsTest.java new file mode 100644 index 000000000..cf0a8724e --- /dev/null +++ b/javatests/google/registry/model/OteStatsTest.java @@ -0,0 +1,147 @@ +// Copyright 2018 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.model; + +import static com.google.common.truth.Truth.assertThat; + +import google.registry.model.OteStats.StatType; +import google.registry.testing.AppEngineRule; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class OteStatsTest { + + @Rule public final AppEngineRule appEngine = AppEngineRule.builder().withDatastore().build(); + + @Before + public void init() throws Exception { + OteStatsTestHelper.setupHistoryEntries(); + } + + @Test + public void testSuccess_allPass() { + OteStats stats = OteStats.getFromRegistrar("blobio"); + assertThat(stats.getFailures()).isEmpty(); + assertThat(stats.getSize()).isEqualTo(31); + } + + @Test + public void testSuccess_someFailures() { + OteStatsTestHelper.deleteHostDeleteHistoryEntry(); + OteStatsTestHelper.deleteDomainCreateHistoryEntry(); + OteStatsTestHelper.deleteDomainRestoreHistoryEntry(); + OteStats stats = OteStats.getFromRegistrar("blobio"); + assertThat(stats.getFailures()) + .containsExactly( + StatType.DOMAIN_CREATES_IDN, StatType.DOMAIN_RESTORES, StatType.HOST_DELETES) + .inOrder(); + assertThat(stats.getSize()).isEqualTo(35); + } + + @Test + public void testSuccess_toString() { + OteStats stats = OteStats.getFromRegistrar("blobio"); + String expected = + "contact creates: 0\n" + + "contact deletes: 0\n" + + "contact transfer approves: 0\n" + + "contact transfer cancels: 0\n" + + "contact transfer rejects: 0\n" + + "contact transfer requests: 0\n" + + "contact updates: 0\n" + + "domain application creates: 0\n" + + "domain application creates landrush: 0\n" + + "domain application creates sunrise: 0\n" + + "domain application deletes: 0\n" + + "domain application updates: 0\n" + + "domain autorenews: 0\n" + + "domain creates: 5\n" + + "domain creates ascii: 4\n" + + "domain creates idn: 1\n" + + "domain creates start date sunrise: 1\n" + + "domain creates with claims notice: 1\n" + + "domain creates with fee: 1\n" + + "domain creates with sec dns: 1\n" + + "domain creates without sec dns: 4\n" + + "domain deletes: 2\n" + + "domain renews: 0\n" + + "domain restores: 1\n" + + "domain transfer approves: 1\n" + + "domain transfer cancels: 1\n" + + "domain transfer rejects: 1\n" + + "domain transfer requests: 1\n" + + "domain updates: 1\n" + + "domain updates with sec dns: 1\n" + + "domain updates without sec dns: 0\n" + + "host creates: 1\n" + + "host creates external: 0\n" + + "host creates subordinate: 1\n" + + "host deletes: 1\n" + + "host updates: 1\n" + + "unclassified flows: 0\n" + + "TOTAL: 31"; + assertThat(stats.toString()).isEqualTo(expected); + } + + @Test + public void testMissingHostDeletes_toString() { + OteStatsTestHelper.deleteHostDeleteHistoryEntry(); + OteStats stats = OteStats.getFromRegistrar("blobio"); + String expected = + "contact creates: 0\n" + + "contact deletes: 0\n" + + "contact transfer approves: 0\n" + + "contact transfer cancels: 0\n" + + "contact transfer rejects: 0\n" + + "contact transfer requests: 0\n" + + "contact updates: 0\n" + + "domain application creates: 0\n" + + "domain application creates landrush: 0\n" + + "domain application creates sunrise: 0\n" + + "domain application deletes: 0\n" + + "domain application updates: 0\n" + + "domain autorenews: 0\n" + + "domain creates: 5\n" + + "domain creates ascii: 4\n" + + "domain creates idn: 1\n" + + "domain creates start date sunrise: 1\n" + + "domain creates with claims notice: 1\n" + + "domain creates with fee: 1\n" + + "domain creates with sec dns: 1\n" + + "domain creates without sec dns: 4\n" + + "domain deletes: 2\n" + + "domain renews: 0\n" + + "domain restores: 1\n" + + "domain transfer approves: 1\n" + + "domain transfer cancels: 1\n" + + "domain transfer rejects: 1\n" + + "domain transfer requests: 1\n" + + "domain updates: 1\n" + + "domain updates with sec dns: 1\n" + + "domain updates without sec dns: 0\n" + + "host creates: 1\n" + + "host creates external: 0\n" + + "host creates subordinate: 1\n" + + "host deletes: 0\n" + + "host updates: 10\n" + + "unclassified flows: 0\n" + + "TOTAL: 39"; + assertThat(stats.toString()).isEqualTo(expected); + } +} diff --git a/javatests/google/registry/model/OteStatsTestHelper.java b/javatests/google/registry/model/OteStatsTestHelper.java new file mode 100644 index 000000000..410e6c111 --- /dev/null +++ b/javatests/google/registry/model/OteStatsTestHelper.java @@ -0,0 +1,159 @@ +// Copyright 2017 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.model; + +import static google.registry.testing.DatastoreHelper.deleteResource; +import static google.registry.testing.DatastoreHelper.persistPremiumList; +import static google.registry.testing.DatastoreHelper.persistResource; +import static google.registry.testing.TestDataHelper.loadBytes; +import static google.registry.util.DateTimeUtils.END_OF_TIME; + +import google.registry.model.eppcommon.Trid; +import google.registry.model.reporting.HistoryEntry; +import google.registry.model.reporting.HistoryEntry.Type; +import java.io.IOException; + +public final class OteStatsTestHelper { + + private static HistoryEntry hostDeleteHistoryEntry; + private static HistoryEntry domainCreateHistoryEntry; + private static HistoryEntry domainRestoreHistoryEntry; + + public static void setupHistoryEntries() throws IOException { + persistPremiumList("default_sandbox_list", "sandbox,USD 1000"); + persistResource( + new HistoryEntry.Builder() + .setClientId("blobio-1") + .setType(Type.DOMAIN_CREATE) + .setXmlBytes(getBytes("domain_create_sunrise.xml")) + .build()); + domainCreateHistoryEntry = + persistResource( + new HistoryEntry.Builder() + .setClientId("blobio-1") + .setType(Type.DOMAIN_CREATE) + .setXmlBytes(getBytes("domain_create_idn.xml")) + .build()); + persistResource( + new HistoryEntry.Builder() + .setClientId("blobio-1") + .setType(Type.DOMAIN_CREATE) + .setXmlBytes(getBytes("domain_create_claim_notice.xml")) + .build()); + persistResource( + new HistoryEntry.Builder() + .setClientId("blobio-1") + .setType(Type.DOMAIN_CREATE) + .setXmlBytes(getBytes("domain_create_anchor_tenant_fee_standard.xml")) + .build()); + persistResource( + new HistoryEntry.Builder() + .setClientId("blobio-1") + .setType(Type.DOMAIN_CREATE) + .setXmlBytes(getBytes("domain_create_dsdata.xml")) + .build()); + persistResource( + new HistoryEntry.Builder() + .setClientId("blobio-1") + .setType(Type.DOMAIN_DELETE) + .setXmlBytes(getBytes("domain_delete.xml")) + .build()); + persistResource( + new HistoryEntry.Builder() + .setClientId("blobio-2") + .setType(Type.DOMAIN_DELETE) + .setXmlBytes(getBytes("domain_delete.xml")) + .build()); + domainRestoreHistoryEntry = + persistResource( + new HistoryEntry.Builder() + .setClientId("blobio-1") + .setType(Type.DOMAIN_RESTORE) + .setXmlBytes(getBytes("domain_restore.xml")) + .build()); + persistResource( + new HistoryEntry.Builder() + .setClientId("blobio-1") + .setType(Type.DOMAIN_TRANSFER_APPROVE) + .setXmlBytes(getBytes("domain_transfer_approve.xml")) + .build()); + persistResource( + new HistoryEntry.Builder() + .setClientId("blobio-1") + .setType(Type.DOMAIN_TRANSFER_CANCEL) + .setXmlBytes(getBytes("domain_transfer_cancel.xml")) + .build()); + persistResource( + new HistoryEntry.Builder() + .setClientId("blobio-1") + .setType(Type.DOMAIN_TRANSFER_REJECT) + .setXmlBytes(getBytes("domain_transfer_reject.xml")) + .build()); + persistResource( + new HistoryEntry.Builder() + .setClientId("blobio-1") + .setType(Type.DOMAIN_TRANSFER_REQUEST) + .setXmlBytes(getBytes("domain_transfer_request.xml")) + .build()); + persistResource( + new HistoryEntry.Builder() + .setClientId("blobio-1") + .setType(Type.DOMAIN_UPDATE) + .setXmlBytes(getBytes("domain_update_with_secdns.xml")) + .build()); + persistResource( + new HistoryEntry.Builder() + .setClientId("blobio-1") + .setType(Type.HOST_CREATE) + .setXmlBytes(getBytes("host_create_complete.xml")) + .build()); + hostDeleteHistoryEntry = + persistResource( + new HistoryEntry.Builder() + .setClientId("blobio-1") + .setType(Type.HOST_DELETE) + .setXmlBytes(getBytes("host_delete.xml")) + .build()); + // Persist 10 host updates for a total of 25 history entries. Since these also sort last by + // modification time, when these cause all tests to pass, only the first will be recorded and + // the rest will be skipped. + for (int i = 0; i < 10; i++) { + persistResource( + new HistoryEntry.Builder() + .setClientId("blobio-1") + .setType(Type.HOST_UPDATE) + .setXmlBytes(getBytes("host_update.xml")) + .setTrid(Trid.create(null, String.format("blahtrid-%d", i))) + .setModificationTime(END_OF_TIME) + .build()); + } + } + + public static void deleteHostDeleteHistoryEntry() { + deleteResource(hostDeleteHistoryEntry); + } + + public static void deleteDomainCreateHistoryEntry() { + deleteResource(domainCreateHistoryEntry); + } + + public static void deleteDomainRestoreHistoryEntry() { + deleteResource(domainRestoreHistoryEntry); + } + + private static byte[] getBytes(String filename) throws IOException { + return loadBytes(OteStatsTestHelper.class, filename).read(); + } +} diff --git a/javatests/google/registry/model/testdata/domain_create_anchor_tenant_fee_standard.xml b/javatests/google/registry/model/testdata/domain_create_anchor_tenant_fee_standard.xml new file mode 100644 index 000000000..08030ce7f --- /dev/null +++ b/javatests/google/registry/model/testdata/domain_create_anchor_tenant_fee_standard.xml @@ -0,0 +1,29 @@ + + + + + example.tld + 2 + jd1234 + jd1234 + jd1234 + + abcdefghijklmnop + + + + + + anchor-tenant-test + false + true + + + USD + 26.00 + + + RegistryTool + + diff --git a/javatests/google/registry/tools/server/testdata/domain_create_claim_notice.xml b/javatests/google/registry/model/testdata/domain_create_claim_notice.xml similarity index 100% rename from javatests/google/registry/tools/server/testdata/domain_create_claim_notice.xml rename to javatests/google/registry/model/testdata/domain_create_claim_notice.xml diff --git a/javatests/google/registry/tools/server/testdata/domain_create_dsdata.xml b/javatests/google/registry/model/testdata/domain_create_dsdata.xml similarity index 100% rename from javatests/google/registry/tools/server/testdata/domain_create_dsdata.xml rename to javatests/google/registry/model/testdata/domain_create_dsdata.xml diff --git a/javatests/google/registry/tools/server/testdata/domain_create_idn.xml b/javatests/google/registry/model/testdata/domain_create_idn.xml similarity index 100% rename from javatests/google/registry/tools/server/testdata/domain_create_idn.xml rename to javatests/google/registry/model/testdata/domain_create_idn.xml diff --git a/javatests/google/registry/tools/server/testdata/domain_create_sunrise.xml b/javatests/google/registry/model/testdata/domain_create_sunrise.xml similarity index 100% rename from javatests/google/registry/tools/server/testdata/domain_create_sunrise.xml rename to javatests/google/registry/model/testdata/domain_create_sunrise.xml diff --git a/javatests/google/registry/model/testdata/domain_delete.xml b/javatests/google/registry/model/testdata/domain_delete.xml new file mode 100644 index 000000000..b16cfec36 --- /dev/null +++ b/javatests/google/registry/model/testdata/domain_delete.xml @@ -0,0 +1,17 @@ + + + + + example.tld + + + + + Deleted by registry administrator: Test + false + + + RegistryTool + + diff --git a/javatests/google/registry/tools/server/testdata/domain_restore.xml b/javatests/google/registry/model/testdata/domain_restore.xml similarity index 100% rename from javatests/google/registry/tools/server/testdata/domain_restore.xml rename to javatests/google/registry/model/testdata/domain_restore.xml diff --git a/javatests/google/registry/tools/server/testdata/domain_transfer_approve.xml b/javatests/google/registry/model/testdata/domain_transfer_approve.xml similarity index 100% rename from javatests/google/registry/tools/server/testdata/domain_transfer_approve.xml rename to javatests/google/registry/model/testdata/domain_transfer_approve.xml diff --git a/javatests/google/registry/tools/server/testdata/domain_transfer_cancel.xml b/javatests/google/registry/model/testdata/domain_transfer_cancel.xml similarity index 100% rename from javatests/google/registry/tools/server/testdata/domain_transfer_cancel.xml rename to javatests/google/registry/model/testdata/domain_transfer_cancel.xml diff --git a/javatests/google/registry/tools/server/testdata/domain_transfer_reject.xml b/javatests/google/registry/model/testdata/domain_transfer_reject.xml similarity index 100% rename from javatests/google/registry/tools/server/testdata/domain_transfer_reject.xml rename to javatests/google/registry/model/testdata/domain_transfer_reject.xml diff --git a/javatests/google/registry/tools/server/testdata/domain_transfer_request.xml b/javatests/google/registry/model/testdata/domain_transfer_request.xml similarity index 100% rename from javatests/google/registry/tools/server/testdata/domain_transfer_request.xml rename to javatests/google/registry/model/testdata/domain_transfer_request.xml diff --git a/javatests/google/registry/tools/server/testdata/domain_update_with_secdns.xml b/javatests/google/registry/model/testdata/domain_update_with_secdns.xml similarity index 100% rename from javatests/google/registry/tools/server/testdata/domain_update_with_secdns.xml rename to javatests/google/registry/model/testdata/domain_update_with_secdns.xml diff --git a/javatests/google/registry/model/testdata/host_create_complete.xml b/javatests/google/registry/model/testdata/host_create_complete.xml new file mode 100644 index 000000000..b9bc7afed --- /dev/null +++ b/javatests/google/registry/model/testdata/host_create_complete.xml @@ -0,0 +1,13 @@ + + + + + example.tld + 162.100.102.99 + 4.5.6.7 + 2001:0db8:85a3:0000:0000:8a2e:0370:7334 + + + RegistryTool + + diff --git a/javatests/google/registry/model/testdata/host_delete.xml b/javatests/google/registry/model/testdata/host_delete.xml new file mode 100644 index 000000000..b56877485 --- /dev/null +++ b/javatests/google/registry/model/testdata/host_delete.xml @@ -0,0 +1,17 @@ + + + + + ns1.example.tld + + + + + Deleted by registry administrator: Test + false + + + RegistryTool + + diff --git a/javatests/google/registry/tools/server/testdata/host_update.xml b/javatests/google/registry/model/testdata/host_update.xml similarity index 100% rename from javatests/google/registry/tools/server/testdata/host_update.xml rename to javatests/google/registry/model/testdata/host_update.xml diff --git a/javatests/google/registry/tools/server/BUILD b/javatests/google/registry/tools/server/BUILD index 4141c7ebb..55f428ada 100644 --- a/javatests/google/registry/tools/server/BUILD +++ b/javatests/google/registry/tools/server/BUILD @@ -18,6 +18,7 @@ java_library( "//java/google/registry/request", "//java/google/registry/tools/server", "//java/google/registry/util", + "//javatests/google/registry/model", "//javatests/google/registry/testing", "//javatests/google/registry/testing/mapreduce", "//third_party/objectify:objectify-v4_1", diff --git a/javatests/google/registry/tools/server/VerifyOteActionTest.java b/javatests/google/registry/tools/server/VerifyOteActionTest.java index ab905bd39..450328967 100644 --- a/javatests/google/registry/tools/server/VerifyOteActionTest.java +++ b/javatests/google/registry/tools/server/VerifyOteActionTest.java @@ -15,18 +15,12 @@ package google.registry.tools.server; import static com.google.common.truth.Truth.assertThat; -import static google.registry.testing.DatastoreHelper.deleteResource; -import static google.registry.testing.DatastoreHelper.persistResource; -import static google.registry.util.DateTimeUtils.END_OF_TIME; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import google.registry.model.eppcommon.Trid; -import google.registry.model.reporting.HistoryEntry; -import google.registry.model.reporting.HistoryEntry.Type; +import google.registry.model.OteStatsTestHelper; import google.registry.testing.AppEngineRule; import java.util.Map; -import java.util.Map.Entry; import java.util.regex.Pattern; import org.junit.Before; import org.junit.Rule; @@ -42,214 +36,94 @@ public class VerifyOteActionTest { private final VerifyOteAction action = new VerifyOteAction(); - private HistoryEntry hostDeleteHistoryEntry; - private HistoryEntry domainCreateHistoryEntry; - private HistoryEntry domainRestoreHistoryEntry; - @Before public void init() throws Exception { - persistResource( - new HistoryEntry.Builder() - .setClientId("blobio-1") - .setType(Type.DOMAIN_CREATE) - .setXmlBytes(ToolsTestData.loadBytes("domain_create_sunrise.xml").read()) - .build()); - domainCreateHistoryEntry = persistResource( - new HistoryEntry.Builder() - .setClientId("blobio-1") - .setType(Type.DOMAIN_CREATE) - .setXmlBytes(ToolsTestData.loadBytes("domain_create_idn.xml").read()) - .build()); - persistResource( - new HistoryEntry.Builder() - .setClientId("blobio-1") - .setType(Type.DOMAIN_CREATE) - .setXmlBytes(ToolsTestData.loadBytes("domain_create_claim_notice.xml").read()) - .build()); - persistResource( - new HistoryEntry.Builder() - .setClientId("blobio-1") - .setType(Type.DOMAIN_CREATE) - .setXmlBytes(ToolsTestData.loadBytes("domain_create_anchor_tenant_fee_standard.xml").read()) - .build()); - persistResource( - new HistoryEntry.Builder() - .setClientId("blobio-1") - .setType(Type.DOMAIN_CREATE) - .setXmlBytes(ToolsTestData.loadBytes("domain_create_dsdata.xml").read()) - .build()); - persistResource( - new HistoryEntry.Builder() - .setClientId("blobio-1") - .setType(Type.DOMAIN_DELETE) - .setXmlBytes(ToolsTestData.loadBytes("domain_delete.xml").read()) - .build()); - persistResource( - new HistoryEntry.Builder() - .setClientId("blobio-2") - .setType(Type.DOMAIN_DELETE) - .setXmlBytes(ToolsTestData.loadBytes("domain_delete.xml").read()) - .build()); - domainRestoreHistoryEntry = persistResource( - new HistoryEntry.Builder() - .setClientId("blobio-1") - .setType(Type.DOMAIN_RESTORE) - .setXmlBytes(ToolsTestData.loadBytes("domain_restore.xml").read()) - .build()); - persistResource( - new HistoryEntry.Builder() - .setClientId("blobio-1") - .setType(Type.DOMAIN_TRANSFER_APPROVE) - .setXmlBytes(ToolsTestData.loadBytes("domain_transfer_approve.xml").read()) - .build()); - persistResource( - new HistoryEntry.Builder() - .setClientId("blobio-1") - .setType(Type.DOMAIN_TRANSFER_CANCEL) - .setXmlBytes(ToolsTestData.loadBytes("domain_transfer_cancel.xml").read()) - .build()); - persistResource( - new HistoryEntry.Builder() - .setClientId("blobio-1") - .setType(Type.DOMAIN_TRANSFER_REJECT) - .setXmlBytes(ToolsTestData.loadBytes("domain_transfer_reject.xml").read()) - .build()); - persistResource( - new HistoryEntry.Builder() - .setClientId("blobio-1") - .setType(Type.DOMAIN_TRANSFER_REQUEST) - .setXmlBytes(ToolsTestData.loadBytes("domain_transfer_request.xml").read()) - .build()); - persistResource( - new HistoryEntry.Builder() - .setClientId("blobio-1") - .setType(Type.DOMAIN_UPDATE) - .setXmlBytes(ToolsTestData.loadBytes("domain_update_with_secdns.xml").read()) - .build()); - persistResource( - new HistoryEntry.Builder() - .setClientId("blobio-1") - .setType(Type.HOST_CREATE) - .setXmlBytes(ToolsTestData.loadBytes("host_create_complete.xml").read()) - .build()); - hostDeleteHistoryEntry = - persistResource( - new HistoryEntry.Builder() - .setClientId("blobio-1") - .setType(Type.HOST_DELETE) - .setXmlBytes(ToolsTestData.loadBytes("host_delete.xml").read()) - .build()); - // Persist 10 host updates for a total of 25 history entries. Since these also sort last by - // modification time, when these cause all tests to pass, only the first will be recorded and - // the rest will be skipped. - for (int i = 0; i < 10; i++) { - persistResource( - new HistoryEntry.Builder() - .setClientId("blobio-1") - .setType(Type.HOST_UPDATE) - .setXmlBytes(ToolsTestData.loadBytes("host_update.xml").read()) - .setTrid(Trid.create(null, String.format("blahtrid-%d", i))) - .setModificationTime(END_OF_TIME) - .build()); - } + OteStatsTestHelper.setupHistoryEntries(); } @Test public void testSuccess_summarize_allPass() { - Map response = - action.handleJsonRequest( - ImmutableMap.of("summarize", "true", "registrars", ImmutableList.of("blobio"))); - assertThat(response) - .containsExactly( - "blobio", "# actions: 31 - Reqs: [----------------] 16/16 - Overall: PASS"); + assertThat(getResponse(true)) + .isEqualTo("# actions: 31 - Reqs: [----------------] 16/16 - Overall: PASS"); } @Test - public void testSuccess_summarize_someFailures() { - deleteResource(hostDeleteHistoryEntry); - deleteResource(domainCreateHistoryEntry); - deleteResource(domainRestoreHistoryEntry); - Map response = - action.handleJsonRequest( - ImmutableMap.of("summarize", "true", "registrars", ImmutableList.of("blobio"))); - assertThat(response) - .containsExactly( - "blobio", "# actions: 35 - Reqs: [-.-----.------.-] 13/16 - Overall: FAIL"); + public void testFailure_summarize_someFailures() { + OteStatsTestHelper.deleteDomainCreateHistoryEntry(); + OteStatsTestHelper.deleteDomainRestoreHistoryEntry(); + OteStatsTestHelper.deleteHostDeleteHistoryEntry(); + assertThat(getResponse(true)) + .isEqualTo("# actions: 35 - Reqs: [-.-----.------.-] 13/16 - Overall: FAIL"); } @Test public void testSuccess_passNotSummarized() { - Map response = - action.handleJsonRequest( - ImmutableMap.of("summarize", "false", "registrars", ImmutableList.of("blobio"))); - - for (Entry registrar : response.entrySet()) { - assertThat(registrar.getKey()).matches("blobio"); - String expectedOteStatus = - "domain creates idn: 1\n" - + "domain creates start date sunrise: 1\n" - + "domain creates with claims notice: 1\n" - + "domain creates with fee: 1\n" - + "domain creates with sec dns: 1\n" - + ".*" - + "domain deletes: 2\n" - + ".*" - + "domain restores: 1\n" - + "domain transfer approves: 1\n" - + "domain transfer cancels: 1\n" - + "domain transfer rejects: 1\n" - + "domain transfer requests: 1\n" - + ".*" - + "domain updates with sec dns: 1\n" - + ".*" - + "host creates subordinate: 1\n" - + "host deletes: 1\n" - + "host updates: 1\n" - + ".*" - + "Requirements passed: 16/16\n" - + "Overall OT&E status: PASS\n"; - Pattern expectedOteStatusPattern = Pattern.compile(expectedOteStatus, Pattern.DOTALL); - assertThat(registrar.getValue().toString()).containsMatch(expectedOteStatusPattern); - } + String expectedOteStatus = + "domain creates idn: 1\n" + + "domain creates start date sunrise: 1\n" + + "domain creates with claims notice: 1\n" + + "domain creates with fee: 1\n" + + "domain creates with sec dns: 1\n" + + ".*" + + "domain deletes: 2\n" + + ".*" + + "domain restores: 1\n" + + "domain transfer approves: 1\n" + + "domain transfer cancels: 1\n" + + "domain transfer rejects: 1\n" + + "domain transfer requests: 1\n" + + ".*" + + "domain updates with sec dns: 1\n" + + ".*" + + "host creates subordinate: 1\n" + + "host deletes: 1\n" + + "host updates: 1\n" + + ".*" + + "Requirements passed: 16/16\n" + + "Overall OT&E status: PASS\n"; + Pattern expectedOteStatusPattern = Pattern.compile(expectedOteStatus, Pattern.DOTALL); + assertThat(getResponse(false)).containsMatch(expectedOteStatusPattern); } @Test public void testFailure_missingHostDelete() { - deleteResource(hostDeleteHistoryEntry); + OteStatsTestHelper.deleteHostDeleteHistoryEntry(); + String expectedOteStatus = + "domain creates idn: 1\n" + + "domain creates start date sunrise: 1\n" + + "domain creates with claims notice: 1\n" + + "domain creates with fee: 1\n" + + "domain creates with sec dns: 1\n" + + ".*" + + "domain deletes: 2\n" + + ".*" + + "domain restores: 1\n" + + "domain transfer approves: 1\n" + + "domain transfer cancels: 1\n" + + "domain transfer rejects: 1\n" + + "domain transfer requests: 1\n" + + ".*" + + "domain updates with sec dns: 1\n" + + ".*" + + "host creates subordinate: 1\n" + + "host deletes: 0\n" + + "host updates: 10\n" + + ".*" + + "Requirements passed: 15/16\n" + + "Overall OT&E status: FAIL\n"; + Pattern expectedOteStatusPattern = Pattern.compile(expectedOteStatus, Pattern.DOTALL); + assertThat(getResponse(false)).containsMatch(expectedOteStatusPattern); + } + private String getResponse(boolean summarize) { Map response = action.handleJsonRequest( - ImmutableMap.of("summarize", "false", "registrars", ImmutableList.of("blobio"))); - - for (Entry registrar : response.entrySet()) { - assertThat(registrar.getKey()).matches("blobio"); - String oteStatus = registrar.getValue().toString(); - - String expectedOteStatus = - "domain creates idn: 1\n" - + "domain creates start date sunrise: 1\n" - + "domain creates with claims notice: 1\n" - + "domain creates with fee: 1\n" - + "domain creates with sec dns: 1\n" - + ".*" - + "domain deletes: 2\n" - + ".*" - + "domain restores: 1\n" - + "domain transfer approves: 1\n" - + "domain transfer cancels: 1\n" - + "domain transfer rejects: 1\n" - + "domain transfer requests: 1\n" - + ".*" - + "domain updates with sec dns: 1\n" - + ".*" - + "host creates subordinate: 1\n" - + "host deletes: 0\n" - + "host updates: 10\n" - + ".*" - + "Requirements passed: 15/16\n" - + "Overall OT&E status: FAIL\n"; - Pattern expectedOteStatusPattern = Pattern.compile(expectedOteStatus, Pattern.DOTALL); - assertThat(oteStatus).containsMatch(expectedOteStatusPattern); - } + ImmutableMap.of( + "summarize", + Boolean.toString(summarize), + "registrars", + ImmutableList.of("blobio"))); + assertThat(response).containsKey("blobio"); + return response.get("blobio").toString(); } }