From c7c904db1b146d24e25927b613155d130bbe40d1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= <yoann@hibernate.org>
Date: Tue, 25 Feb 2020 15:17:03 +0100
Subject: [PATCH] HV-1774 Add a few tests to demonstrate the behavior of
 TokenCollector

---
 .../AbstractTokenCollectorTest.java           | 186 ++++++++++++++++++
 .../TokenCollectorMessageExpressionTest.java  | 110 +++++++++++
 .../TokenCollectorMessageParameterTest.java   | 115 +++++++++++
 .../TokenCollectorTest.java                   |  64 ------
 4 files changed, 411 insertions(+), 64 deletions(-)
 create mode 100644 engine/src/test/java/org/hibernate/validator/test/internal/engine/messageinterpolation/AbstractTokenCollectorTest.java
 create mode 100644 engine/src/test/java/org/hibernate/validator/test/internal/engine/messageinterpolation/TokenCollectorMessageExpressionTest.java
 create mode 100644 engine/src/test/java/org/hibernate/validator/test/internal/engine/messageinterpolation/TokenCollectorMessageParameterTest.java
 delete mode 100644 engine/src/test/java/org/hibernate/validator/test/internal/engine/messageinterpolation/TokenCollectorTest.java

diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/engine/messageinterpolation/AbstractTokenCollectorTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/engine/messageinterpolation/AbstractTokenCollectorTest.java
new file mode 100644
index 000000000..004d5c9bd
--- /dev/null
+++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/messageinterpolation/AbstractTokenCollectorTest.java
@@ -0,0 +1,186 @@
+/*
+ * Hibernate Validator, declare and validate application constraints
+ *
+ * License: Apache License, Version 2.0
+ * See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
+ */
+package org.hibernate.validator.test.internal.engine.messageinterpolation;
+
+import org.hibernate.validator.internal.engine.messageinterpolation.InterpolationTermType;
+import org.hibernate.validator.internal.engine.messageinterpolation.parser.MessageDescriptorFormatException;
+import org.hibernate.validator.internal.engine.messageinterpolation.parser.Token;
+import org.hibernate.validator.internal.engine.messageinterpolation.parser.TokenCollector;
+
+import org.assertj.core.api.Assertions;
+import org.testng.annotations.Test;
+
+/**
+ * Abstract base for {@code TokenCollector} tests.
+ *
+ * @author Hardy Ferentschik
+ */
+public abstract class AbstractTokenCollectorTest {
+
+	protected abstract InterpolationTermType getInterpolationTermType();
+
+	@Test
+	public void testLiteral() {
+		Assertions.assertThat(
+				new TokenCollector( "foo bar", getInterpolationTermType() )
+						.getTokenList()
+		)
+				.hasSize( 1 )
+				.element( 0 )
+				.returns( "foo bar", Token::getTokenValue )
+				.returns( false, Token::isParameter );
+	}
+
+	@Test(expectedExceptions = MessageDescriptorFormatException.class, expectedExceptionsMessageRegExp = "HV000169.*")
+	public void testNestedParametersThrowException() {
+		new TokenCollector( "#{foo  {}", getInterpolationTermType() );
+	}
+
+	@Test(expectedExceptions = MessageDescriptorFormatException.class, expectedExceptionsMessageRegExp = "HV000168.*")
+	public void testClosingBraceWithoutOpeningBraceThrowsException() {
+		new TokenCollector( "foo}", getInterpolationTermType() );
+	}
+
+	@Test(expectedExceptions = MessageDescriptorFormatException.class, expectedExceptionsMessageRegExp = "HV000168.*")
+	public void testOpeningBraceWithoutClosingBraceThrowsException() {
+		new TokenCollector( "{foo", getInterpolationTermType() );
+	}
+
+	@Test
+	public void testBackslashEscapesNonMetaCharacter() {
+		Assertions.assertThat(
+				new TokenCollector( "foo \\bar", getInterpolationTermType() )
+						.getTokenList()
+		)
+				.hasSize( 1 )
+				.element( 0 )
+				// Backslashes are removed later, in AbstractMessageInterpolator.replaceEscapedLiterals
+				.returns( "foo \\bar", Token::getTokenValue )
+				.returns( false, Token::isParameter );
+	}
+
+	@Test
+	public void testBackslashEscapesDollar() {
+		Assertions.assertThat(
+				new TokenCollector( "foo \\$ bar", getInterpolationTermType() )
+						.getTokenList()
+		)
+				.hasSize( 1 )
+				.element( 0 )
+				// Backslashes are removed later, in AbstractMessageInterpolator.replaceEscapedLiterals
+				.returns( "foo \\$ bar", Token::getTokenValue )
+				.returns( false, Token::isParameter );
+	}
+
+	@Test
+	public void testBackslashEscapesOpeningBrace() {
+		Assertions.assertThat(
+				new TokenCollector( "foo \\{ bar", getInterpolationTermType() )
+						.getTokenList()
+		)
+				.hasSize( 1 )
+				.element( 0 )
+				// Backslashes are removed later, in AbstractMessageInterpolator.replaceEscapedLiterals
+				.returns( "foo \\{ bar", Token::getTokenValue )
+				.returns( false, Token::isParameter );
+	}
+
+	@Test
+	public void testBackslashEscapesClosingBrace() {
+		Assertions.assertThat(
+				new TokenCollector( "foo \\} bar", getInterpolationTermType() )
+						.getTokenList()
+		)
+				.hasSize( 1 )
+				.element( 0 )
+				// Backslashes are removed later, in AbstractMessageInterpolator.replaceEscapedLiterals
+				.returns( "foo \\} bar", Token::getTokenValue )
+				.returns( false, Token::isParameter );
+	}
+
+	@Test
+	public void testBackslashEscapesBackslash() {
+		Assertions.assertThat(
+				new TokenCollector( "foo \\\\ bar", getInterpolationTermType() )
+						.getTokenList()
+		)
+				.hasSize( 1 )
+				.element( 0 )
+				// Backslashes are removed later, in AbstractMessageInterpolator.replaceEscapedLiterals
+				.returns( "foo \\\\ bar", Token::getTokenValue )
+				.returns( false, Token::isParameter );
+	}
+
+	@Test
+	public void testBackslashEscapesEL() {
+		Assertions.assertThat(
+				new TokenCollector( "foo \\$\\{bar\\}", getInterpolationTermType() )
+						.getTokenList()
+		)
+				.hasSize( 1 )
+				.element( 0 )
+				// Backslashes are removed later, in AbstractMessageInterpolator.replaceEscapedLiterals
+				.returns( "foo \\$\\{bar\\}", Token::getTokenValue )
+				// What's important is that we did NOT detect the expression
+				.returns( false, Token::isParameter );
+	}
+
+	@Test
+	public void testBackslashEscapesParameter() {
+		Assertions.assertThat(
+				new TokenCollector( "foo \\{bar\\}", getInterpolationTermType() )
+						.getTokenList()
+		)
+				.hasSize( 1 )
+				.element( 0 )
+				// Backslashes are removed later, in AbstractMessageInterpolator.replaceEscapedLiterals
+				.returns( "foo \\{bar\\}", Token::getTokenValue )
+				// What's important is that we did NOT detect the parameter
+				.returns( false, Token::isParameter );
+	}
+
+	@Test(expectedExceptions = MessageDescriptorFormatException.class, expectedExceptionsMessageRegExp = "HV000168.*")
+	public void testTrailingClosingBraceThrowsException() {
+		new TokenCollector( "this message contains a invalid parameter start token {", getInterpolationTermType() );
+	}
+
+	@Test
+	public void testDollarThenNonMetaCharacterInterpretedAsLiteral() {
+		Assertions.assertThat(
+				new TokenCollector( "$a", getInterpolationTermType() )
+						.getTokenList()
+		)
+				.hasSize( 1 )
+				.element( 0 )
+				.returns( "$a", Token::getTokenValue )
+				.returns( false, Token::isParameter );
+	}
+
+	@Test
+	public void testTrailingDollarInterpretedAsLiteral() {
+		Assertions.assertThat(
+				new TokenCollector( "foo $", getInterpolationTermType() )
+						.getTokenList()
+		)
+				.hasSize( 1 )
+				.element( 0 )
+				.returns( "foo $", Token::getTokenValue )
+				.returns( false, Token::isParameter );
+	}
+
+	@Test
+	public void testTrailingBackslashInterpretedAsLiteral() {
+		Assertions.assertThat(
+				new TokenCollector( "foo \\", getInterpolationTermType() )
+						.getTokenList()
+		)
+				.hasSize( 1 )
+				.element( 0 )
+				.returns( "foo \\", Token::getTokenValue )
+				.returns( false, Token::isParameter );
+	}
+}
diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/engine/messageinterpolation/TokenCollectorMessageExpressionTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/engine/messageinterpolation/TokenCollectorMessageExpressionTest.java
new file mode 100644
index 000000000..229e34174
--- /dev/null
+++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/messageinterpolation/TokenCollectorMessageExpressionTest.java
@@ -0,0 +1,110 @@
+/*
+ * Hibernate Validator, declare and validate application constraints
+ *
+ * License: Apache License, Version 2.0
+ * See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
+ */
+package org.hibernate.validator.test.internal.engine.messageinterpolation;
+
+import org.hibernate.validator.internal.engine.messageinterpolation.InterpolationTermType;
+import org.hibernate.validator.internal.engine.messageinterpolation.parser.MessageDescriptorFormatException;
+import org.hibernate.validator.internal.engine.messageinterpolation.parser.Token;
+import org.hibernate.validator.internal.engine.messageinterpolation.parser.TokenCollector;
+
+import org.assertj.core.api.Assertions;
+import org.assertj.core.api.ListAssert;
+import org.testng.annotations.Test;
+
+/**
+ * Tests for {@code TokenCollector} in message expression mode.
+ *
+ * @author Hardy Ferentschik
+ */
+public class TokenCollectorMessageExpressionTest extends AbstractTokenCollectorTest {
+	@Override
+	protected InterpolationTermType getInterpolationTermType() {
+		return InterpolationTermType.EL;
+	}
+
+	// Several tests inherited from the abstract class
+
+	@Test
+	public void testMessageParameter() {
+		ListAssert<Token> assertion = Assertions.assertThat(
+				new TokenCollector( "foo {bar}", getInterpolationTermType() )
+						.getTokenList()
+		)
+				.hasSize( 2 );
+		assertion.element( 0 )
+				.returns( "foo ", Token::getTokenValue )
+				.returns( false, Token::isParameter );
+		assertion.element( 1 )
+				.returns( "{bar}", Token::getTokenValue )
+				.returns( false, Token::isParameter );
+	}
+
+	@Test
+	public void testMessageExpression() {
+		ListAssert<Token> assertion = Assertions.assertThat(
+				new TokenCollector( "foo ${bar}", getInterpolationTermType() )
+						.getTokenList()
+		)
+				.hasSize( 2 );
+		assertion.element( 0 )
+				.returns( "foo ", Token::getTokenValue )
+				.returns( false, Token::isParameter );
+		assertion.element( 1 )
+				.returns( "${bar}", Token::getTokenValue )
+				.returns( true, Token::isParameter );
+	}
+
+	@Test
+	public void testDollarThenDollarThenParameterInterpretedAsLiterals() {
+		ListAssert<Token> assertion = Assertions.assertThat(
+				new TokenCollector( "$${1+1}", getInterpolationTermType() )
+						.getTokenList()
+		)
+				.hasSize( 2 );
+		assertion.element( 0 )
+				.returns( "$$", Token::getTokenValue )
+				.returns( false, Token::isParameter );
+		assertion.element( 1 )
+				.returns( "{1+1}", Token::getTokenValue )
+				.returns( false, Token::isParameter );
+	}
+
+	@Test
+	public void testDollarThenDollarThenLiteralsInterpretedAsLiterals() {
+		ListAssert<Token> assertion = Assertions.assertThat(
+				new TokenCollector( "$$foo", getInterpolationTermType() )
+						.getTokenList()
+		)
+				.hasSize( 2 );
+		assertion.element( 0 )
+				.returns( "$$", Token::getTokenValue )
+				.returns( false, Token::isParameter );
+		assertion.element( 1 )
+				.returns( "foo", Token::getTokenValue )
+				.returns( false, Token::isParameter );
+	}
+
+	@Test(expectedExceptions = MessageDescriptorFormatException.class, expectedExceptionsMessageRegExp = "HV000168.*")
+	public void testDollarThenClosingBraceThrowsException() {
+		new TokenCollector( "$}", getInterpolationTermType() );
+	}
+
+	@Test
+	public void testDollarThenEscapeInterpretedAsLiterals() {
+		ListAssert<Token> assertion = Assertions.assertThat(
+				new TokenCollector( "$\\A{1+1}", getInterpolationTermType() )
+						.getTokenList()
+		)
+				.hasSize( 2 );
+		assertion.element( 0 )
+				.returns( "$\\A", Token::getTokenValue )
+				.returns( false, Token::isParameter );
+		assertion.element( 1 )
+				.returns( "{1+1}", Token::getTokenValue )
+				.returns( false, Token::isParameter );
+	}
+}
diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/engine/messageinterpolation/TokenCollectorMessageParameterTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/engine/messageinterpolation/TokenCollectorMessageParameterTest.java
new file mode 100644
index 000000000..9189f496b
--- /dev/null
+++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/messageinterpolation/TokenCollectorMessageParameterTest.java
@@ -0,0 +1,115 @@
+/*
+ * Hibernate Validator, declare and validate application constraints
+ *
+ * License: Apache License, Version 2.0
+ * See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
+ */
+package org.hibernate.validator.test.internal.engine.messageinterpolation;
+
+import org.hibernate.validator.internal.engine.messageinterpolation.InterpolationTermType;
+import org.hibernate.validator.internal.engine.messageinterpolation.parser.MessageDescriptorFormatException;
+import org.hibernate.validator.internal.engine.messageinterpolation.parser.Token;
+import org.hibernate.validator.internal.engine.messageinterpolation.parser.TokenCollector;
+
+import org.assertj.core.api.Assertions;
+import org.assertj.core.api.ListAssert;
+import org.testng.annotations.Test;
+
+/**
+ * Tests for {@code TokenCollector} in message parameter mode.
+ *
+ * @author Hardy Ferentschik
+ */
+public class TokenCollectorMessageParameterTest extends AbstractTokenCollectorTest {
+	@Override
+	protected InterpolationTermType getInterpolationTermType() {
+		return InterpolationTermType.PARAMETER;
+	}
+
+	// Several tests inherited from the abstract class
+
+	@Test
+	public void testMessageParameter() {
+		ListAssert<Token> assertion = Assertions.assertThat(
+				new TokenCollector( "foo {bar}", getInterpolationTermType() )
+						.getTokenList()
+		)
+				.hasSize( 2 );
+		assertion.element( 0 )
+				.returns( "foo ", Token::getTokenValue )
+				.returns( false, Token::isParameter );
+		assertion.element( 1 )
+				.returns( "{bar}", Token::getTokenValue )
+				.returns( true, Token::isParameter );
+	}
+
+	@Test
+	public void testMessageExpression() {
+		ListAssert<Token> assertion = Assertions.assertThat(
+				new TokenCollector( "foo ${bar}", getInterpolationTermType() )
+						.getTokenList()
+		)
+				.hasSize( 2 );
+		/*
+		 * 6.3.1.1:
+		 * Parameter interpolation has precedence over message expressions.
+		 * For example for the message descriptor ${value},
+		 * trying to evaluate {value} as message parameter has precedence
+		 * over evaluating ${value} as message expression.
+		 */
+		assertion.element( 0 )
+				.returns( "foo $", Token::getTokenValue )
+				.returns( false, Token::isParameter );
+		assertion.element( 1 )
+				.returns( "{bar}", Token::getTokenValue )
+				.returns( true, Token::isParameter );
+	}
+
+	@Test
+	public void testDollarThenDollarThenParameterInterpretedAsLiteralAndParameter() {
+		ListAssert<Token> assertion = Assertions.assertThat(
+				new TokenCollector( "$${1+1}", getInterpolationTermType() )
+						.getTokenList()
+		)
+				.hasSize( 2 );
+		assertion.element( 0 )
+				.returns( "$$", Token::getTokenValue )
+				.returns( false, Token::isParameter );
+		assertion.element( 1 )
+				.returns( "{1+1}", Token::getTokenValue )
+				.returns( true, Token::isParameter );
+	}
+
+	@Test
+	public void testDollarThenDollarThenLiteralsInterpretedAsLiterals() {
+		ListAssert<Token> assertion = Assertions.assertThat(
+				new TokenCollector( "$$foo", getInterpolationTermType() )
+						.getTokenList()
+		)
+				.hasSize( 1 );
+		assertion.element( 0 )
+				.returns( "$$foo", Token::getTokenValue )
+				.returns( false, Token::isParameter );
+	}
+
+	@Test(expectedExceptions = MessageDescriptorFormatException.class, expectedExceptionsMessageRegExp = "HV000168.*")
+	public void testDollarThenClosingBraceThrowsException() {
+		// Fails because of the dangling closing brace; the dollar sign is irrelevant
+		new TokenCollector( "$}", getInterpolationTermType() );
+	}
+
+	@Test
+	public void testDollarThenEscapeInterpretedAsLiterals() {
+		ListAssert<Token> assertion = Assertions.assertThat(
+				new TokenCollector( "$\\A{1+1}", getInterpolationTermType() )
+						.getTokenList()
+		)
+				.hasSize( 2 );
+		assertion.element( 0 )
+				.returns( "$\\A", Token::getTokenValue )
+				.returns( false, Token::isParameter );
+		assertion.element( 1 )
+				.returns( "{1+1}", Token::getTokenValue )
+				.returns( true, Token::isParameter );
+	}
+}
diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/engine/messageinterpolation/TokenCollectorTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/engine/messageinterpolation/TokenCollectorTest.java
deleted file mode 100644
index ab9299f17..000000000
--- a/engine/src/test/java/org/hibernate/validator/test/internal/engine/messageinterpolation/TokenCollectorTest.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Hibernate Validator, declare and validate application constraints
- *
- * License: Apache License, Version 2.0
- * See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
- */
-package org.hibernate.validator.test.internal.engine.messageinterpolation;
-
-import org.hibernate.validator.internal.engine.messageinterpolation.InterpolationTermType;
-import org.hibernate.validator.internal.engine.messageinterpolation.parser.MessageDescriptorFormatException;
-import org.hibernate.validator.internal.engine.messageinterpolation.parser.Token;
-import org.hibernate.validator.internal.engine.messageinterpolation.parser.TokenCollector;
-
-import org.assertj.core.api.Assertions;
-import org.assertj.core.api.ListAssert;
-import org.testng.annotations.Test;
-
-/**
- * Tests for {@code TokenCollector}.
- *
- * @author Hardy Ferentschik
- */
-public class TokenCollectorTest {
-
-	@Test(expectedExceptions = MessageDescriptorFormatException.class, expectedExceptionsMessageRegExp = "HV000169.*")
-	public void testNestedParametersThrowException() throws Exception {
-		new TokenCollector( "#{foo  {}", InterpolationTermType.PARAMETER );
-	}
-
-	@Test(expectedExceptions = MessageDescriptorFormatException.class, expectedExceptionsMessageRegExp = "HV000168.*")
-	public void testParameterWithoutOpeningBraceThrowsException() throws Exception {
-		new TokenCollector( "foo}", InterpolationTermType.PARAMETER );
-	}
-
-	@Test(expectedExceptions = MessageDescriptorFormatException.class, expectedExceptionsMessageRegExp = "HV000168.*")
-	public void testELExpressionDollarThenClosingBraceThrowsException() throws Exception {
-		new TokenCollector( "$}", InterpolationTermType.EL );
-	}
-
-	@Test
-	public void testELExpressionDollarThenEscapeInterpretedAsLiterals() {
-		ListAssert<Token> assertion = Assertions.assertThat(
-				new TokenCollector( "$\\A{1+1}", InterpolationTermType.EL )
-						.getTokenList()
-		)
-				.hasSize( 2 );
-		assertion.element( 0 )
-				.returns( "$\\A", Token::getTokenValue )
-				.returns( false, Token::isParameter );
-		assertion.element( 1 )
-				.returns( "{1+1}", Token::getTokenValue )
-				.returns( false, Token::isParameter );
-	}
-
-	@Test(expectedExceptions = MessageDescriptorFormatException.class, expectedExceptionsMessageRegExp = "HV000168.*")
-	public void testTermWithoutClosingBraceThrowsException() throws Exception {
-		new TokenCollector( "{foo", InterpolationTermType.PARAMETER );
-	}
-
-	@Test(expectedExceptions = MessageDescriptorFormatException.class, expectedExceptionsMessageRegExp = "HV000168.*")
-	public void testSingleClosingBraceThrowsException() throws Exception {
-		new TokenCollector( "this message contains a invalid parameter start token {", InterpolationTermType.EL );
-	}
-}
