@@ -32,6 +32,232 @@ describe('keywords guardrail', () => {
3232 expect ( result . tripwireTriggered ) . toBe ( false ) ;
3333 expect ( result . info ?. matchedKeywords ) . toEqual ( [ ] ) ;
3434 } ) ;
35+
36+ it ( 'does not match partial words' , ( ) => {
37+ const result = keywordsCheck (
38+ { } ,
39+ 'Hello, world!' ,
40+ KeywordsConfig . parse ( { keywords : [ 'orld' ] } )
41+ ) as GuardrailResult ;
42+
43+ expect ( result . tripwireTriggered ) . toBe ( false ) ;
44+ } ) ;
45+
46+ it ( 'matches numbers' , ( ) => {
47+ const result = keywordsCheck (
48+ { } ,
49+ 'Hello, world123' ,
50+ KeywordsConfig . parse ( { keywords : [ 'world123' ] } )
51+ ) as GuardrailResult ;
52+
53+ expect ( result . tripwireTriggered ) . toBe ( true ) ;
54+ expect ( result . info ?. matchedKeywords ) . toEqual ( [ 'world123' ] ) ;
55+ } ) ;
56+
57+ it ( 'does not match partial numbers' , ( ) => {
58+ const result = keywordsCheck (
59+ { } ,
60+ 'Hello, world12345' ,
61+ KeywordsConfig . parse ( { keywords : [ 'world123' ] } )
62+ ) as GuardrailResult ;
63+
64+ expect ( result . tripwireTriggered ) . toBe ( false ) ;
65+ } ) ;
66+
67+ it ( 'matches underscores' , ( ) => {
68+ const result = keywordsCheck (
69+ { } ,
70+ 'Hello, w_o_r_l_d' ,
71+ KeywordsConfig . parse ( { keywords : [ 'w_o_r_l_d' ] } )
72+ ) as GuardrailResult ;
73+
74+ expect ( result . tripwireTriggered ) . toBe ( true ) ;
75+ expect ( result . info ?. matchedKeywords ) . toEqual ( [ 'w_o_r_l_d' ] ) ;
76+ } ) ;
77+
78+ it ( 'does not match when underscores appear inside other words' , ( ) => {
79+ const result = keywordsCheck (
80+ { } ,
81+ 'Hello, test_world_test' ,
82+ KeywordsConfig . parse ( { keywords : [ 'world' ] } )
83+ ) as GuardrailResult ;
84+
85+ expect ( result . tripwireTriggered ) . toBe ( false ) ;
86+ } ) ;
87+
88+ it ( 'matches chinese characters' , ( ) => {
89+ const result = keywordsCheck (
90+ { } ,
91+ '你好' ,
92+ KeywordsConfig . parse ( { keywords : [ '你好' ] } )
93+ ) as GuardrailResult ;
94+
95+ expect ( result . tripwireTriggered ) . toBe ( true ) ;
96+ } ) ;
97+
98+ it ( 'matches chinese characters with numbers' , ( ) => {
99+ const result = keywordsCheck (
100+ { } ,
101+ '你好123' ,
102+ KeywordsConfig . parse ( { keywords : [ '你好123' ] } )
103+ ) as GuardrailResult ;
104+
105+ expect ( result . tripwireTriggered ) . toBe ( true ) ;
106+ expect ( result . info ?. matchedKeywords ) . toEqual ( [ '你好123' ] ) ;
107+ } ) ;
108+
109+ it ( 'does not match partial chinese characters with numbers' , ( ) => {
110+ const result = keywordsCheck (
111+ { } ,
112+ '你好12345' ,
113+ KeywordsConfig . parse ( { keywords : [ '你好123' ] } )
114+ ) as GuardrailResult ;
115+
116+ expect ( result . tripwireTriggered ) . toBe ( false ) ;
117+ } ) ;
118+
119+ it ( 'applies word boundaries across multi-keyword patterns' , ( ) => {
120+ const result = keywordsCheck (
121+ { } ,
122+ 'testing hello world' ,
123+ KeywordsConfig . parse ( { keywords : [ 'test' , 'hello' , 'world' ] } )
124+ ) as GuardrailResult ;
125+
126+ expect ( result . tripwireTriggered ) . toBe ( true ) ;
127+ expect ( result . info ?. matchedKeywords ) . toEqual ( [ 'hello' , 'world' ] ) ;
128+ } ) ;
129+
130+ it ( 'matches keywords that start with special characters embedded in text' , ( ) => {
131+ const result = keywordsCheck (
132+ { } ,
133+ 'Reach me via example@foo.com later' ,
134+ KeywordsConfig . parse ( { keywords : [ '@foo' ] } )
135+ ) as GuardrailResult ;
136+
137+ expect ( result . tripwireTriggered ) . toBe ( true ) ;
138+ expect ( result . info ?. matchedKeywords ) . toEqual ( [ '@foo' ] ) ;
139+ } ) ;
140+
141+ it ( 'matches keywords that start with # even when preceded by letters' , ( ) => {
142+ const result = keywordsCheck (
143+ { } ,
144+ 'Use example#foo for the ID' ,
145+ KeywordsConfig . parse ( { keywords : [ '#foo' ] } )
146+ ) as GuardrailResult ;
147+
148+ expect ( result . tripwireTriggered ) . toBe ( true ) ;
149+ expect ( result . info ?. matchedKeywords ) . toEqual ( [ '#foo' ] ) ;
150+ } ) ;
151+
152+ it ( 'ignores keywords that become empty after sanitization' , ( ) => {
153+ const result = keywordsCheck (
154+ { } ,
155+ 'Totally benign text' ,
156+ KeywordsConfig . parse ( { keywords : [ '!!!' ] } )
157+ ) as GuardrailResult ;
158+
159+ expect ( result . tripwireTriggered ) . toBe ( false ) ;
160+ expect ( result . info ?. matchedKeywords ) . toEqual ( [ ] ) ;
161+ expect ( result . info ?. sanitizedKeywords ) . toEqual ( [ '' ] ) ;
162+ } ) ;
163+
164+ it ( 'still matches other keywords when some sanitize to empty strings' , ( ) => {
165+ const result = keywordsCheck (
166+ { } ,
167+ 'Please keep this secret!' ,
168+ KeywordsConfig . parse ( { keywords : [ '...' , 'secret!!!' ] } )
169+ ) as GuardrailResult ;
170+
171+ expect ( result . tripwireTriggered ) . toBe ( true ) ;
172+ expect ( result . info ?. matchedKeywords ) . toEqual ( [ 'secret' ] ) ;
173+ } ) ;
174+
175+ it ( 'matches keywords ending with special characters' , ( ) => {
176+ const result = keywordsCheck (
177+ { } ,
178+ 'Use foo@ in the config' ,
179+ KeywordsConfig . parse ( { keywords : [ 'foo@' ] } )
180+ ) as GuardrailResult ;
181+
182+ expect ( result . tripwireTriggered ) . toBe ( true ) ;
183+ expect ( result . info ?. matchedKeywords ) . toEqual ( [ 'foo@' ] ) ;
184+ } ) ;
185+
186+ it ( 'matches keywords ending with punctuation when followed by word characters' , ( ) => {
187+ const result = keywordsCheck (
188+ { } ,
189+ 'Check foo@example' ,
190+ KeywordsConfig . parse ( { keywords : [ 'foo@' ] } )
191+ ) as GuardrailResult ;
192+
193+ expect ( result . tripwireTriggered ) . toBe ( true ) ;
194+ expect ( result . info ?. matchedKeywords ) . toEqual ( [ 'foo@' ] ) ;
195+ } ) ;
196+
197+ it ( 'matches mixed script keywords' , ( ) => {
198+ const result = keywordsCheck (
199+ { } ,
200+ 'Welcome to hello你好world section' ,
201+ KeywordsConfig . parse ( { keywords : [ 'hello你好world' ] } )
202+ ) as GuardrailResult ;
203+
204+ expect ( result . tripwireTriggered ) . toBe ( true ) ;
205+ expect ( result . info ?. matchedKeywords ) . toEqual ( [ 'hello你好world' ] ) ;
206+ } ) ;
207+
208+ it ( 'does not match partial mixed script keywords' , ( ) => {
209+ const result = keywordsCheck (
210+ { } ,
211+ 'This is hello你好worldextra' ,
212+ KeywordsConfig . parse ( { keywords : [ 'hello你好world' ] } )
213+ ) as GuardrailResult ;
214+
215+ expect ( result . tripwireTriggered ) . toBe ( false ) ;
216+ } ) ;
217+
218+ it ( 'matches Arabic characters' , ( ) => {
219+ const result = keywordsCheck (
220+ { } ,
221+ 'مرحبا بك' ,
222+ KeywordsConfig . parse ( { keywords : [ 'مرحبا' ] } )
223+ ) as GuardrailResult ;
224+
225+ expect ( result . tripwireTriggered ) . toBe ( true ) ;
226+ expect ( result . info ?. matchedKeywords ) . toEqual ( [ 'مرحبا' ] ) ;
227+ } ) ;
228+
229+ it ( 'matches Cyrillic characters' , ( ) => {
230+ const result = keywordsCheck (
231+ { } ,
232+ 'Привет мир' ,
233+ KeywordsConfig . parse ( { keywords : [ 'Привет' ] } )
234+ ) as GuardrailResult ;
235+
236+ expect ( result . tripwireTriggered ) . toBe ( true ) ;
237+ expect ( result . info ?. matchedKeywords ) . toEqual ( [ 'Привет' ] ) ;
238+ } ) ;
239+
240+ it ( 'matches keywords with only punctuation' , ( ) => {
241+ const result = keywordsCheck (
242+ { } ,
243+ 'Use the @@ symbol' ,
244+ KeywordsConfig . parse ( { keywords : [ '@@' ] } )
245+ ) as GuardrailResult ;
246+
247+ expect ( result . tripwireTriggered ) . toBe ( true ) ;
248+ expect ( result . info ?. matchedKeywords ) . toEqual ( [ '@@' ] ) ;
249+ } ) ;
250+
251+ it ( 'matches mixed punctuation and alphanumeric keywords' , ( ) => {
252+ const result = keywordsCheck (
253+ { } ,
254+ 'Contact via @user123@' ,
255+ KeywordsConfig . parse ( { keywords : [ '@user123@' ] } )
256+ ) as GuardrailResult ;
257+
258+ expect ( result . tripwireTriggered ) . toBe ( true ) ;
259+ expect ( result . info ?. matchedKeywords ) . toEqual ( [ '@user123@' ] ) ;
260+ } ) ;
35261} ) ;
36262
37263describe ( 'urls guardrail' , ( ) => {
0 commit comments