1+ using Xunit ;
2+
3+ namespace Serilog . Expressions . Tests ;
4+
5+ public class ExpressionValidationTests
6+ {
7+ [ Theory ]
8+ [ InlineData ( "IsMatch(Name, '[invalid')" , "Invalid regular expression" ) ]
9+ [ InlineData ( "IndexOfMatch(Text, '(?<')" , "Invalid regular expression" ) ]
10+ [ InlineData ( "IsMatch(Name, '(?P<name>)')" , "Invalid regular expression" ) ]
11+ [ InlineData ( "IsMatch(Name, '(unclosed')" , "Invalid regular expression" ) ]
12+ [ InlineData ( "IndexOfMatch(Text, '*invalid')" , "Invalid regular expression" ) ]
13+ public void InvalidRegularExpressionsAreReportedGracefully ( string expression , string expectedErrorFragment )
14+ {
15+ var result = SerilogExpression . TryCompile ( expression , out var compiled , out var error ) ;
16+ Assert . False ( result ) ;
17+ Assert . Contains ( expectedErrorFragment , error ) ;
18+ Assert . Null ( compiled ) ;
19+ }
20+
21+ [ Theory ]
22+ [ InlineData ( "UnknownFunction()" , "The function name `UnknownFunction` was not recognized." ) ]
23+ [ InlineData ( "foo(1, 2, 3)" , "The function name `foo` was not recognized." ) ]
24+ [ InlineData ( "MyCustomFunc(Name)" , "The function name `MyCustomFunc` was not recognized." ) ]
25+ [ InlineData ( "notAFunction()" , "The function name `notAFunction` was not recognized." ) ]
26+ public void UnknownFunctionNamesAreReportedGracefully ( string expression , string expectedError )
27+ {
28+ var result = SerilogExpression . TryCompile ( expression , out var compiled , out var error ) ;
29+ Assert . False ( result ) ;
30+ Assert . Equal ( expectedError , error ) ;
31+ Assert . Null ( compiled ) ;
32+ }
33+
34+ [ Theory ]
35+ [ InlineData ( "Length(Name) ci" , "The function `Length` does not support case-insensitive operation." ) ]
36+ [ InlineData ( "Round(Value, 2) ci" , "The function `Round` does not support case-insensitive operation." ) ]
37+ [ InlineData ( "Now() ci" , "The function `Now` does not support case-insensitive operation." ) ]
38+ [ InlineData ( "TypeOf(Value) ci" , "The function `TypeOf` does not support case-insensitive operation." ) ]
39+ [ InlineData ( "IsDefined(Prop) ci" , "The function `IsDefined` does not support case-insensitive operation." ) ]
40+ public void InvalidCiModifierUsageIsReported ( string expression , string expectedError )
41+ {
42+ var result = SerilogExpression . TryCompile ( expression , out var compiled , out var error ) ;
43+ Assert . False ( result ) ;
44+ Assert . Equal ( expectedError , error ) ;
45+ Assert . Null ( compiled ) ;
46+ }
47+
48+ [ Theory ]
49+ [ InlineData ( "Contains(Name, 'test') ci" ) ]
50+ [ InlineData ( "StartsWith(Path, '/api') ci" ) ]
51+ [ InlineData ( "EndsWith(File, '.txt') ci" ) ]
52+ [ InlineData ( "IsMatch(Email, '@example') ci" ) ]
53+ [ InlineData ( "IndexOfMatch(Text, 'pattern') ci" ) ]
54+ [ InlineData ( "IndexOf(Name, 'x') ci" ) ]
55+ [ InlineData ( "Name = 'test' ci" ) ]
56+ [ InlineData ( "Name <> 'test' ci" ) ]
57+ [ InlineData ( "Name like '%test%' ci" ) ]
58+ public void ValidCiModifierUsageCompiles ( string expression )
59+ {
60+ var result = SerilogExpression . TryCompile ( expression , out var compiled , out var error ) ;
61+ Assert . True ( result , $ "Failed to compile: { error } ") ;
62+ Assert . NotNull ( compiled ) ;
63+ Assert . Null ( error ) ;
64+ }
65+
66+ [ Fact ]
67+ public void MultipleErrorsAreCollectedAndReported ( )
68+ {
69+ var expression = "UnknownFunc() and IsMatch(Name, '[invalid') and Length(Value) ci" ;
70+ var result = SerilogExpression . TryCompile ( expression , out var compiled , out var error ) ;
71+
72+ Assert . False ( result ) ;
73+ Assert . Null ( compiled ) ;
74+
75+ // Should report all three errors
76+ Assert . Contains ( "UnknownFunc" , error ) ;
77+ Assert . Contains ( "Invalid regular expression" , error ) ;
78+ Assert . Contains ( "does not support case-insensitive" , error ) ;
79+ Assert . Contains ( "Multiple errors found" , error ) ;
80+ }
81+
82+ [ Fact ]
83+ public void ValidExpressionsStillCompileWithoutErrors ( )
84+ {
85+ var validExpressions = new [ ]
86+ {
87+ "IsMatch(Name, '^[A-Z]')" ,
88+ "IndexOfMatch(Text, '\\ d+')" ,
89+ "Contains(Name, 'test') ci" ,
90+ "Length(Items) > 5" ,
91+ "Round(Value, 2)" ,
92+ "TypeOf(Data) = 'String'" ,
93+ "Name like '%test%'" ,
94+ "StartsWith(Path, '/') and EndsWith(Path, '.json')"
95+ } ;
96+
97+ foreach ( var expr in validExpressions )
98+ {
99+ var result = SerilogExpression . TryCompile ( expr , out var compiled , out var error ) ;
100+ Assert . True ( result , $ "Failed to compile: { expr } . Error: { error } ") ;
101+ Assert . NotNull ( compiled ) ;
102+ Assert . Null ( error ) ;
103+ }
104+ }
105+
106+ [ Fact ]
107+ public void CompileMethodStillThrowsForInvalidExpressions ( )
108+ {
109+ // Ensure Compile() method (not TryCompile) maintains throwing behavior for invalid expressions
110+ Assert . Throws < ArgumentException > ( ( ) =>
111+ SerilogExpression . Compile ( "UnknownFunction()" ) ) ;
112+
113+ Assert . Throws < ArgumentException > ( ( ) =>
114+ SerilogExpression . Compile ( "IsMatch(Name, '[invalid')" ) ) ;
115+
116+ Assert . Throws < ArgumentException > ( ( ) =>
117+ SerilogExpression . Compile ( "Length(Name) ci" ) ) ;
118+ }
119+
120+ [ Theory ]
121+ [ InlineData ( "IsMatch(Name, Name)" ) ] // Non-constant pattern
122+ [ InlineData ( "IndexOfMatch(Text, Value)" ) ] // Non-constant pattern
123+ public void NonConstantRegexPatternsHandledGracefully ( string expression )
124+ {
125+ // These should compile but may log warnings (not errors)
126+ var result = SerilogExpression . TryCompile ( expression , out var compiled , out var error ) ;
127+
128+ // These compile successfully but return undefined at runtime
129+ Assert . True ( result ) ;
130+ Assert . NotNull ( compiled ) ;
131+ Assert . Null ( error ) ;
132+ }
133+
134+ [ Fact ]
135+ public void RegexTimeoutIsRespected ( )
136+ {
137+ // This regex should compile fine - timeout only matters at runtime
138+ var expression = @"IsMatch(Text, '(a+)+b')" ;
139+
140+ var result = SerilogExpression . TryCompile ( expression , out var compiled , out var error ) ;
141+
142+ Assert . True ( result ) ;
143+ Assert . NotNull ( compiled ) ;
144+ Assert . Null ( error ) ;
145+ }
146+
147+ [ Fact ]
148+ public void ComplexExpressionsWithMixedIssues ( )
149+ {
150+ var expression = "(UnknownFunc1() or IsMatch(Name, '(invalid')) and NotRealFunc() ci" ;
151+ var result = SerilogExpression . TryCompile ( expression , out var compiled , out var error ) ;
152+
153+ Assert . False ( result ) ;
154+ Assert . Null ( compiled ) ;
155+ Assert . NotNull ( error ) ;
156+
157+ // Should report multiple errors
158+ Assert . Contains ( "Multiple errors found" , error ) ;
159+ Assert . Contains ( "UnknownFunc1" , error ) ;
160+ Assert . Contains ( "NotRealFunc" , error ) ;
161+ Assert . Contains ( "Invalid regular expression" , error ) ;
162+ }
163+ }
0 commit comments