// 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.ui.forms; import static com.google.common.collect.Range.atLeast; import static com.google.common.collect.Range.atMost; import static com.google.common.collect.Range.closed; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth8.assertThat; import static google.registry.testing.JUnitBackports.assertThrows; import static google.registry.testing.JUnitBackports.expectThrows; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import com.google.common.base.CharMatcher; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.testing.NullPointerTester; import com.google.re2j.Pattern; import java.util.List; import java.util.Set; import java.util.function.Function; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** Unit tests for {@link FormField}. */ @RunWith(JUnit4.class) public class FormFieldTest { private enum ICanHazEnum { LOL, CAT } @Test public void testConvert_nullString_notPresent() { assertThat(FormField.named("lol").build().convert(null)).isEmpty(); } @Test public void testConvert_emptyString_returnsEmpty() { assertThat(FormField.named("lol").build().convert("").get()).isEmpty(); } @Test public void testWithDefault_hasValue_returnsValue() { assertThat(FormField.named("lol").withDefault("default").build().convert("return me!")) .hasValue("return me!"); } @Test public void testWithDefault_nullValue_returnsDefault() { assertThat(FormField.named("lol").withDefault("default").build().convert(null)) .hasValue("default"); } @Test public void testEmptyToNull_emptyString_notPresent() { assertThat(FormField.named("lol").emptyToNull().build().convert("")).isEmpty(); } @Test public void testEmptyToNullRequired_emptyString_throwsFfe() { FormFieldException thrown = expectThrows( FormFieldException.class, () -> FormField.named("lol").emptyToNull().required().build().convert("")); assertThat(thrown, equalTo(new FormFieldException("This field is required.").propagate("lol"))); } @Test public void testEmptyToNull_typeMismatch() { assertThrows( IllegalStateException.class, () -> FormField.named("lol", Object.class).emptyToNull()); } @Test public void testNamedLong() { assertThat(FormField.named("lol", Long.class).build().convert(666L)).hasValue(666L); } @Test public void testUppercased() { FormField field = FormField.named("lol").uppercased().build(); assertThat(field.convert(null)).isEmpty(); assertThat(field.convert("foo")).hasValue("FOO"); assertThat(field.convert("BAR")).hasValue("BAR"); } @Test public void testLowercased() { FormField field = FormField.named("lol").lowercased().build(); assertThat(field.convert(null)).isEmpty(); assertThat(field.convert("foo")).hasValue("foo"); assertThat(field.convert("BAR")).hasValue("bar"); } @Test public void testIn_passesThroughNull() { FormField field = FormField.named("lol") .in(ImmutableSet.of("foo", "bar")) .build(); assertThat(field.convert(null)).isEmpty(); } @Test public void testIn_valueIsContainedInSet() { FormField field = FormField.named("lol") .in(ImmutableSet.of("foo", "bar")) .build(); assertThat(field.convert("foo")).hasValue("foo"); assertThat(field.convert("bar")).hasValue("bar"); } @Test public void testIn_valueMissingFromSet() { FormField field = FormField.named("lol") .in(ImmutableSet.of("foo", "bar")) .build(); FormFieldException thrown = expectThrows(FormFieldException.class, () -> field.convert("omfg")); assertThat(thrown, equalTo(new FormFieldException("Unrecognized value.").propagate("lol"))); } @Test public void testRange_hasLowerBound_nullValue_passesThrough() { assertThat(FormField.named("lol").range(atLeast(5)).build().convert(null)).isEmpty(); } @Test public void testRange_minimum_stringLengthEqualToMinimum_doesNothing() { assertThat(FormField.named("lol").range(atLeast(5)).build().convert("hello")).hasValue("hello"); } @Test public void testRange_minimum_stringLengthShorterThanMinimum_throwsFfe() { FormFieldException thrown = expectThrows( FormFieldException.class, () -> FormField.named("lol").range(atLeast(4)).build().convert("lol")); assertThat(thrown).hasMessageThat().contains("Number of characters (3) not in range [4"); } @Test public void testRange_noLowerBound_nullValue_passThrough() { assertThat(FormField.named("lol").range(atMost(5)).build().convert(null)).isEmpty(); } @Test public void testRange_maximum_stringLengthEqualToMaximum_doesNothing() { assertThat(FormField.named("lol").range(atMost(5)).build().convert("hello")).hasValue("hello"); } @Test public void testRange_maximum_stringLengthShorterThanMaximum_throwsFfe() { FormFieldException thrown = expectThrows( FormFieldException.class, () -> FormField.named("lol").range(atMost(5)).build().convert("omgomg")); assertThat(thrown).hasMessageThat().contains("Number of characters (6) not in range"); } @Test public void testRange_numericTypes() { FormField.named("lol", Byte.class).range(closed(5, 10)).build().convert((byte) 7); FormField.named("lol", Short.class).range(closed(5, 10)).build().convert((short) 7); FormField.named("lol", Integer.class).range(closed(5, 10)).build().convert(7); FormField.named("lol", Long.class).range(closed(5, 10)).build().convert(7L); FormField.named("lol", Float.class).range(closed(5, 10)).build().convert(7F); FormField.named("lol", Double.class).range(closed(5, 10)).build().convert(7D); } @Test public void testRange_typeMismatch() { assertThrows( IllegalStateException.class, () -> FormField.named("lol", Object.class).range(atMost(5))); } @Test public void testMatches_matches_doesNothing() { assertThat(FormField.named("lol").matches(Pattern.compile("[a-z]+")).build().convert("abc")) .hasValue("abc"); } @Test public void testMatches_mismatch_throwsFfeAndShowsDefaultErrorMessageWithPattern() { FormFieldException thrown = expectThrows( FormFieldException.class, () -> FormField.named("lol") .matches(Pattern.compile("[a-z]+")) .build() .convert("123abc456")); assertThat( thrown, equalTo(new FormFieldException("Must match pattern: [a-z]+").propagate("lol"))); } @Test public void testMatches_typeMismatch() { assertThrows( IllegalStateException.class, () -> FormField.named("lol", Object.class).matches(Pattern.compile("."))); } @Test public void testRetains() { assertThat( FormField.named("lol") .retains(CharMatcher.anyOf("0123456789")) .build() .convert(" 123 1593-43 453 45 4 4 \t")) .hasValue("1231593434534544"); } @Test public void testCast() { assertThat( FormField.named("lol") .transform(Integer.class, Integer::parseInt) .build() .convert("123")) .hasValue(123); } @Test public void testCast_twice() { assertThat( FormField.named("lol") .transform(Object.class, Integer::parseInt) .transform(String.class, Object::toString) .build() .convert("123")) .hasValue("123"); } @Test public void testAsList_null_notPresent() { assertThat(FormField.named("lol").asList().build().convert(null)).isEmpty(); } @Test public void testAsList_empty_returnsEmpty() { assertThat(FormField.named("lol").asList().build().convert(ImmutableList.of()).get()) .isEmpty(); } @Test public void testAsListEmptyToNullRequired_empty_throwsFfe() { FormFieldException thrown = expectThrows( FormFieldException.class, () -> FormField.named("lol") .asList() .emptyToNull() .required() .build() .convert(ImmutableList.of())); assertThat(thrown, equalTo(new FormFieldException("This field is required.").propagate("lol"))); } @Test public void testListEmptyToNull_empty_notPresent() { assertThat(FormField.named("lol") .asList() .emptyToNull() .build() .convert(ImmutableList.of())) .isEmpty(); } @Test public void testAsEnum() { FormField omgField = FormField.named("omg") .asEnum(ICanHazEnum.class) .build(); assertThat(omgField.convert("LOL").get()).isEqualTo(ICanHazEnum.LOL); assertThat(omgField.convert("CAT").get()).isEqualTo(ICanHazEnum.CAT); } @Test public void testAsEnum_lowercase_works() { FormField omgField = FormField.named("omg") .asEnum(ICanHazEnum.class) .build(); assertThat(omgField.convert("lol").get()).isEqualTo(ICanHazEnum.LOL); assertThat(omgField.convert("cat").get()).isEqualTo(ICanHazEnum.CAT); } @Test public void testAsEnum_badInput_throwsFfe() { FormField omgField = FormField.named("omg") .asEnum(ICanHazEnum.class) .build(); FormFieldException thrown = expectThrows(FormFieldException.class, () -> omgField.convert("helo")); assertThat( thrown, equalTo( new FormFieldException("Enum ICanHazEnum does not contain 'helo'").propagate("omg"))); } @Test public void testSplitList() { FormField> field = FormField.named("lol") .asList(Splitter.on(',').omitEmptyStrings()) .build(); assertThat(field.convert("oh,my,goth").get()) .containsExactly("oh", "my", "goth") .inOrder(); assertThat(field.convert("").get()).isEmpty(); assertThat(field.convert(null)).isEmpty(); } @Test public void testSplitSet() { FormField> field = FormField.named("lol") .uppercased() .asSet(Splitter.on(',').omitEmptyStrings()) .build(); assertThat(field.convert("oh,my,goth").get()) .containsExactly("OH", "MY", "GOTH") .inOrder(); assertThat(field.convert("").get()).isEmpty(); assertThat(field.convert(null)).isEmpty(); } @Test public void testAsList() { assertThat( FormField.named("lol").asList().build().convert(ImmutableList.of("lol", "cat", "")).get()) .containsExactly("lol", "cat", "").inOrder(); } @Test public void testAsList_trimmedEmptyToNullOnItems() { assertThat(FormField.named("lol") .trimmed() .emptyToNull() .matches(Pattern.compile("[a-z]+")) .asList() .range(closed(1, 2)) .build() .convert(ImmutableList.of("lol\n", "\tcat ")) .get()) .containsExactly("lol", "cat").inOrder(); } @Test public void testAsList_nullElements_getIgnored() { assertThat(FormField.named("lol") .emptyToNull() .asList() .build() .convert(ImmutableList.of("omg", "")) .get()) .containsExactly("omg"); } @Test public void testAsListRequiredElements_nullElement_throwsFfeWithIndex() { FormFieldException thrown = expectThrows( FormFieldException.class, () -> FormField.named("lol") .emptyToNull() .required() .asList() .build() .convert(ImmutableList.of("omg", ""))); assertThat( thrown, equalTo(new FormFieldException("This field is required.").propagate(1).propagate("lol"))); } @Test public void testMapAsListRequiredElements_nullElement_throwsFfeWithIndexAndKey() { FormFieldException thrown = expectThrows( FormFieldException.class, () -> FormField.mapNamed("lol") .transform( String.class, input -> FormField.named("cat") .emptyToNull() .required() .build() .extractUntyped(input) .get()) .asList() .build() .convert(ImmutableList.of(ImmutableMap.of("cat", "")))); assertThat( thrown, equalTo( new FormFieldException("This field is required.") .propagate("cat") .propagate(0) .propagate("lol"))); } @Test public void testAsListTrimmed_typeMismatch() { FormField.named("lol").trimmed().asList(); assertThrows(IllegalStateException.class, () -> FormField.named("lol").asList().trimmed()); } @Test public void testAsMatrix() { assertThat( FormField.named("lol", Integer.class) .transform(input -> input * 2) .asList() .asList() .build() .convert( Lists.cartesianProduct( ImmutableList.of(ImmutableList.of(1, 2), ImmutableList.of(3, 4)))) .get()) .containsExactly( ImmutableList.of(2, 6), ImmutableList.of(2, 8), ImmutableList.of(4, 6), ImmutableList.of(4, 8)) .inOrder(); } @Test public void testAsSet() { assertThat(FormField.named("lol") .asSet() .build() .convert(ImmutableList.of("lol", "cat", "cat")) .get()) .containsExactly("lol", "cat"); } @Test public void testTrimmed() { assertThat(FormField.named("lol").trimmed().build().convert(" \thello \t\n")).hasValue("hello"); } @Test public void testTrimmed_typeMismatch() { assertThrows(IllegalStateException.class, () -> FormField.named("lol", Object.class).trimmed()); } @Test public void testAsBuilder() { FormField field = FormField.named("omg").uppercased().build(); assertThat(field.name()).isEqualTo("omg"); assertThat(field.convert("hello")).hasValue("HELLO"); field = field.asBuilder().build(); assertThat(field.name()).isEqualTo("omg"); assertThat(field.convert("hello")).hasValue("HELLO"); } @Test public void testAsBuilderNamed() { FormField field = FormField.named("omg").uppercased().build(); assertThat(field.name()).isEqualTo("omg"); assertThat(field.convert("hello")).hasValue("HELLO"); field = field.asBuilderNamed("bog").build(); assertThat(field.name()).isEqualTo("bog"); assertThat(field.convert("hello")).hasValue("HELLO"); } @Test public void testNullness() { NullPointerTester tester = new NullPointerTester() .setDefault(Class.class, Object.class) .setDefault(Function.class, x -> x) .setDefault(Pattern.class, Pattern.compile(".")) .setDefault(String.class, "hello.com"); tester.testAllPublicStaticMethods(FormField.class); tester.testAllPublicInstanceMethods(FormField.named("lol")); tester.testAllPublicInstanceMethods(FormField.named("lol").build()); } }