@@ -1424,4 +1424,162 @@ public void matcherList_example3_nestedMatcher() {
14241424 assertThat (result .actions ).hasSize (1 );
14251425 assertThat (result .actions .get (0 ).getName ()).isEqualTo ("inner_matcher_2" );
14261426 }
1427+
1428+ @ Test
1429+ public void resolveInput_malformedProto_throws () {
1430+ // Create a config with correct typeUrl but corrupted/invalid bytes
1431+ TypedExtensionConfig config = TypedExtensionConfig .newBuilder ()
1432+ .setTypedConfig (com .google .protobuf .Any .newBuilder ()
1433+ .setTypeUrl ("type.googleapis.com/envoy.type.matcher.v3.HttpRequestHeaderMatchInput" )
1434+ .setValue (com .google .protobuf .ByteString .copyFromUtf8 ("invalid-proto-data" ))
1435+ .build ())
1436+ .build ();
1437+ try {
1438+ UnifiedMatcher .resolveInput (config );
1439+ org .junit .Assert .fail ("Should have thrown IllegalArgumentException" );
1440+ } catch (IllegalArgumentException e ) {
1441+ assertThat (e ).hasMessageThat ().contains ("Invalid input config" );
1442+ }
1443+ }
1444+
1445+ @ Test
1446+ public void matchInput_headerName_invalidCharacters_throws () {
1447+ // A lowercase header name that is still invalid for gRPC Metadata keys
1448+ io .envoyproxy .envoy .type .matcher .v3 .HttpRequestHeaderMatchInput proto =
1449+ io .envoyproxy .envoy .type .matcher .v3 .HttpRequestHeaderMatchInput .newBuilder ()
1450+ .setHeaderName ("invalid$header" )
1451+ .build ();
1452+ TypedExtensionConfig config = TypedExtensionConfig .newBuilder ()
1453+ .setTypedConfig (Any .pack (proto ))
1454+ .build ();
1455+ try {
1456+ UnifiedMatcher .resolveInput (config );
1457+ org .junit .Assert .fail ("Should have thrown IllegalArgumentException" );
1458+ } catch (IllegalArgumentException e ) {
1459+ assertThat (e ).hasMessageThat ().contains ("Invalid header name" );
1460+ }
1461+ }
1462+
1463+ @ Test
1464+ public void matchInput_headerName_binary_missing () {
1465+ MatchContext context = mock (MatchContext .class );
1466+ when (context .getMetadata ()).thenReturn (new Metadata ());
1467+
1468+ io .envoyproxy .envoy .type .matcher .v3 .HttpRequestHeaderMatchInput proto =
1469+ io .envoyproxy .envoy .type .matcher .v3 .HttpRequestHeaderMatchInput .newBuilder ()
1470+ .setHeaderName ("test-bin" ) // Binary suffix
1471+ .build ();
1472+ MatchInput <MatchContext > input = UnifiedMatcher .resolveInput (
1473+ TypedExtensionConfig .newBuilder ()
1474+ .setTypedConfig (com .google .protobuf .Any .pack (proto )).build ());
1475+
1476+ assertThat (input .apply (context )).isNull ();
1477+ }
1478+
1479+ @ Test
1480+ public void noOpMatcher_noOnNoMatch_returnsNoMatch () {
1481+ // An empty Matcher proto defaults to a NoOpMatcher with no onNoMatch action
1482+ Matcher proto = Matcher .getDefaultInstance ();
1483+ UnifiedMatcher matcher = UnifiedMatcher .fromProto (proto );
1484+
1485+ MatchResult result = matcher .match (mock (MatchContext .class ), 0 );
1486+ assertThat (result .matched ).isFalse ();
1487+ }
1488+
1489+ @ Test
1490+ public void noOpMatcher_runtimeRecursionLimit_returnsNoMatch () {
1491+ Matcher proto = Matcher .getDefaultInstance ();
1492+ UnifiedMatcher matcher = UnifiedMatcher .fromProto (proto );
1493+
1494+ // Manually trigger the runtime recursion check in NoOpMatcher
1495+ MatchResult result = matcher .match (mock (MatchContext .class ), 17 );
1496+ assertThat (result .matched ).isFalse ();
1497+ }
1498+
1499+ @ Test
1500+ public void singlePredicate_celInputWithStringMatcher_throws () {
1501+ // Tests the explicit check that blocks CEL input for StringMatchers
1502+ Matcher .MatcherList .Predicate .SinglePredicate predicate =
1503+ Matcher .MatcherList .Predicate .SinglePredicate .newBuilder ()
1504+ .setInput (TypedExtensionConfig .newBuilder ()
1505+ .setTypedConfig (com .google .protobuf .Any .pack (
1506+ com .github .xds .type .matcher .v3 .HttpAttributesCelMatchInput .getDefaultInstance ())))
1507+ .setValueMatch (com .github .xds .type .matcher .v3 .StringMatcher .newBuilder ().setExact ("foo" ))
1508+ .build ();
1509+
1510+ try {
1511+ PredicateEvaluator .fromProto (
1512+ Matcher .MatcherList .Predicate .newBuilder ().setSinglePredicate (predicate ).build ());
1513+ org .junit .Assert .fail ("Should have thrown IllegalArgumentException" );
1514+ } catch (IllegalArgumentException e ) {
1515+ assertThat (e ).hasMessageThat ().contains (
1516+ "HttpAttributesCelMatchInput cannot be used with StringMatcher" );
1517+ }
1518+ }
1519+
1520+ @ Test
1521+ public void singlePredicate_headerInputWithCelMatcher_throws () {
1522+ // Tests the inverse check: CelMatcher must use HttpAttributesCelMatchInput
1523+ com .github .xds .type .matcher .v3 .CelMatcher celMatcher = createCelMatcher ("true" );
1524+ Matcher .MatcherList .Predicate .SinglePredicate predicate =
1525+ Matcher .MatcherList .Predicate .SinglePredicate .newBuilder ()
1526+ .setInput (TypedExtensionConfig .newBuilder ()
1527+ .setTypedConfig (com .google .protobuf .Any .pack (
1528+ io .envoyproxy .envoy .type .matcher .v3 .HttpRequestHeaderMatchInput .newBuilder ()
1529+ .setHeaderName ("host" ).build ())))
1530+ .setCustomMatch (TypedExtensionConfig .newBuilder ()
1531+ .setTypedConfig (com .google .protobuf .Any .pack (celMatcher )))
1532+ .build ();
1533+
1534+ try {
1535+ PredicateEvaluator .fromProto (
1536+ Matcher .MatcherList .Predicate .newBuilder ().setSinglePredicate (predicate ).build ());
1537+ org .junit .Assert .fail ("Should have thrown IllegalArgumentException" );
1538+ } catch (IllegalArgumentException e ) {
1539+ assertThat (e ).hasMessageThat ().contains (
1540+ "CelMatcher can only be used with HttpAttributesCelMatchInput" );
1541+ }
1542+ }
1543+
1544+ @ Test
1545+ public void singlePredicate_noMatcher_throws () {
1546+ // Triggers: "SinglePredicate must have either value_match or custom_match"
1547+ Matcher .MatcherList .Predicate .SinglePredicate predicate =
1548+ Matcher .MatcherList .Predicate .SinglePredicate .newBuilder ()
1549+ .setInput (TypedExtensionConfig .newBuilder ().setTypedConfig (com .google .protobuf .Any .pack (
1550+ com .github .xds .type .matcher .v3 .HttpAttributesCelMatchInput .getDefaultInstance ())))
1551+ .build ();
1552+
1553+ try {
1554+ PredicateEvaluator .fromProto (
1555+ Matcher .MatcherList .Predicate .newBuilder ().setSinglePredicate (predicate ).build ());
1556+ org .junit .Assert .fail ("Should have thrown" );
1557+ } catch (IllegalArgumentException e ) {
1558+ assertThat (e ).hasMessageThat ().contains ("must have either value_match or custom_match" );
1559+ }
1560+ }
1561+
1562+ @ Test
1563+ public void compoundMatchers_tooFewPredicates_throws () {
1564+ // Coverage for OrMatcher and AndMatcher minimum predicate requirements
1565+ Matcher .MatcherList .Predicate p = createHeaderMatchPredicate ("h" , "v" );
1566+ Matcher .MatcherList .Predicate .PredicateList list =
1567+ Matcher .MatcherList .Predicate .PredicateList .newBuilder ().addPredicate (p ).build ();
1568+
1569+ try {
1570+ PredicateEvaluator .fromProto (
1571+ Matcher .MatcherList .Predicate .newBuilder ().setOrMatcher (list ).build ());
1572+ org .junit .Assert .fail ();
1573+ } catch (IllegalArgumentException e ) {
1574+ assertThat (e ).hasMessageThat ().contains ("OrMatcher must have at least 2 predicates" );
1575+ }
1576+
1577+ try {
1578+ PredicateEvaluator .fromProto (
1579+ Matcher .MatcherList .Predicate .newBuilder ().setAndMatcher (list ).build ());
1580+ org .junit .Assert .fail ();
1581+ } catch (IllegalArgumentException e ) {
1582+ assertThat (e ).hasMessageThat ().contains ("AndMatcher must have at least 2 predicates" );
1583+ }
1584+ }
14271585}
0 commit comments