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" ) ]
36+ [ InlineData ( "Round(Value, 2) ci" ) ]
37+ [ InlineData ( "Now() ci" ) ]
38+ [ InlineData ( "TypeOf(Value) ci" ) ]
39+ [ InlineData ( "IsDefined(Prop) ci" ) ]
40+ public void InvalidCiModifierUsageCompilesWithWarning ( string expression )
41+ {
42+ var result = SerilogExpression . TryCompile ( expression , out var compiled , out var error ) ;
43+ Assert . True ( result , $ "Failed to compile: { error } ") ;
44+ Assert . NotNull ( compiled ) ;
45+ Assert . Null ( error ) ;
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 FirstErrorIsReportedInComplexExpressions ( )
68+ {
69+ var expression = "UnknownFunc() and Length(Value) > 5" ;
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 the first error encountered
76+ Assert . Contains ( "UnknownFunc" , error ) ;
77+ Assert . NotNull ( error ) ;
78+ }
79+
80+ [ Fact ]
81+ public void ValidExpressionsStillCompileWithoutErrors ( )
82+ {
83+ var validExpressions = new [ ]
84+ {
85+ "IsMatch(Name, '^[A-Z]')" ,
86+ "IndexOfMatch(Text, '\\ d+')" ,
87+ "Contains(Name, 'test') ci" ,
88+ "Length(Items) > 5" ,
89+ "Round(Value, 2)" ,
90+ "TypeOf(Data) = 'String'" ,
91+ "Name like '%test%'" ,
92+ "StartsWith(Path, '/') and EndsWith(Path, '.json')"
93+ } ;
94+
95+ foreach ( var expr in validExpressions )
96+ {
97+ var result = SerilogExpression . TryCompile ( expr , out var compiled , out var error ) ;
98+ Assert . True ( result , $ "Failed to compile: { expr } . Error: { error } ") ;
99+ Assert . NotNull ( compiled ) ;
100+ Assert . Null ( error ) ;
101+ }
102+ }
103+
104+ [ Fact ]
105+ public void CompileMethodStillThrowsForInvalidExpressions ( )
106+ {
107+ // Ensure Compile() method (not TryCompile) maintains throwing behavior for invalid expressions
108+ Assert . Throws < ArgumentException > ( ( ) =>
109+ SerilogExpression . Compile ( "UnknownFunction()" ) ) ;
110+
111+ Assert . Throws < ArgumentException > ( ( ) =>
112+ SerilogExpression . Compile ( "IsMatch(Name, '[invalid')" ) ) ;
113+
114+ // CI modifier on non-supporting functions compiles with warning
115+ var compiledWithCi = SerilogExpression . Compile ( "Length(Name) ci" ) ;
116+ Assert . NotNull ( compiledWithCi ) ;
117+
118+ Assert . Throws < ArgumentException > ( ( ) =>
119+ SerilogExpression . Compile ( "IndexOfMatch(Text, '(?<')" ) ) ;
120+ }
121+
122+ [ Theory ]
123+ [ InlineData ( "IsMatch(Name, Name)" ) ] // Non-constant pattern
124+ [ InlineData ( "IndexOfMatch(Text, Value)" ) ] // Non-constant pattern
125+ public void NonConstantRegexPatternsHandledGracefully ( string expression )
126+ {
127+ // These should compile but may log warnings (not errors)
128+ var result = SerilogExpression . TryCompile ( expression , out var compiled , out var error ) ;
129+
130+ // These compile successfully but return undefined at runtime
131+ Assert . True ( result ) ;
132+ Assert . NotNull ( compiled ) ;
133+ Assert . Null ( error ) ;
134+ }
135+
136+ [ Fact ]
137+ public void RegexTimeoutIsRespected ( )
138+ {
139+ // This regex should compile fine - timeout only matters at runtime
140+ var expression = @"IsMatch(Text, '(a+)+b')" ;
141+
142+ var result = SerilogExpression . TryCompile ( expression , out var compiled , out var error ) ;
143+
144+ Assert . True ( result ) ;
145+ Assert . NotNull ( compiled ) ;
146+ Assert . Null ( error ) ;
147+ }
148+
149+ [ Fact ]
150+ public void ComplexExpressionsReportFirstError ( )
151+ {
152+ var expression = "UnknownFunc1() or Length(Value) > 5" ;
153+ var result = SerilogExpression . TryCompile ( expression , out var compiled , out var error ) ;
154+
155+ Assert . False ( result ) ;
156+ Assert . Null ( compiled ) ;
157+ Assert . NotNull ( error ) ;
158+
159+ // Should report the first error encountered during compilation
160+ Assert . Contains ( "UnknownFunc1" , error ) ;
161+ }
162+
163+ [ Fact ]
164+ public void BackwardCompatibilityPreservedForInvalidCiUsage ( )
165+ {
166+ // These previously compiled (CI was silently ignored)
167+ // They should still compile with the new changes
168+ var expressions = new [ ]
169+ {
170+ "undefined() ci" ,
171+ "null = undefined() ci" ,
172+ "Length(Name) ci" ,
173+ "Round(Value, 2) ci"
174+ } ;
175+
176+ foreach ( var expr in expressions )
177+ {
178+ var result = SerilogExpression . TryCompile ( expr , out var compiled , out var error ) ;
179+ Assert . True ( result , $ "Breaking change detected: { expr } no longer compiles. Error: { error } ") ;
180+ Assert . NotNull ( compiled ) ;
181+ Assert . Null ( error ) ;
182+ }
183+ }
184+ }
0 commit comments