@@ -9,6 +9,11 @@ import {
99 formatDuration ,
1010 formatTime ,
1111 generateApiKey ,
12+ getInvalidCharacters ,
13+ getTimezoneAbbreviation ,
14+ isValidName ,
15+ redactApiKeys ,
16+ validateName ,
1217} from './utils'
1318
1419// Mock crypto module for encryption/decryption tests
@@ -197,3 +202,204 @@ describe('formatDuration', () => {
197202 expect ( result ) . toBe ( '1h 2m' )
198203 } )
199204} )
205+
206+ describe ( 'getTimezoneAbbreviation' , ( ) => {
207+ it ( 'should return UTC for UTC timezone' , ( ) => {
208+ const result = getTimezoneAbbreviation ( 'UTC' )
209+ expect ( result ) . toBe ( 'UTC' )
210+ } )
211+
212+ it ( 'should return PST/PDT for Los Angeles timezone' , ( ) => {
213+ const winterDate = new Date ( '2023-01-15' ) // Standard time
214+ const summerDate = new Date ( '2023-07-15' ) // Daylight time
215+
216+ const winterResult = getTimezoneAbbreviation ( 'America/Los_Angeles' , winterDate )
217+ const summerResult = getTimezoneAbbreviation ( 'America/Los_Angeles' , summerDate )
218+
219+ expect ( [ 'PST' , 'PDT' ] ) . toContain ( winterResult )
220+ expect ( [ 'PST' , 'PDT' ] ) . toContain ( summerResult )
221+ } )
222+
223+ it ( 'should return JST for Tokyo timezone (no DST)' , ( ) => {
224+ const winterDate = new Date ( '2023-01-15' )
225+ const summerDate = new Date ( '2023-07-15' )
226+
227+ const winterResult = getTimezoneAbbreviation ( 'Asia/Tokyo' , winterDate )
228+ const summerResult = getTimezoneAbbreviation ( 'Asia/Tokyo' , summerDate )
229+
230+ expect ( winterResult ) . toBe ( 'JST' )
231+ expect ( summerResult ) . toBe ( 'JST' )
232+ } )
233+
234+ it ( 'should return full timezone name for unknown timezones' , ( ) => {
235+ const result = getTimezoneAbbreviation ( 'Unknown/Timezone' )
236+ expect ( result ) . toBe ( 'Unknown/Timezone' )
237+ } )
238+ } )
239+
240+ describe ( 'redactApiKeys' , ( ) => {
241+ it ( 'should redact API keys in objects' , ( ) => {
242+ const obj = {
243+ apiKey : 'secret-key' ,
244+ api_key : 'another-secret' ,
245+ access_token : 'token-value' ,
246+ secret : 'secret-value' ,
247+ password : 'password-value' ,
248+ normalField : 'normal-value' ,
249+ }
250+
251+ const result = redactApiKeys ( obj )
252+
253+ expect ( result . apiKey ) . toBe ( '***REDACTED***' )
254+ expect ( result . api_key ) . toBe ( '***REDACTED***' )
255+ expect ( result . access_token ) . toBe ( '***REDACTED***' )
256+ expect ( result . secret ) . toBe ( '***REDACTED***' )
257+ expect ( result . password ) . toBe ( '***REDACTED***' )
258+ expect ( result . normalField ) . toBe ( 'normal-value' )
259+ } )
260+
261+ it ( 'should redact API keys in nested objects' , ( ) => {
262+ const obj = {
263+ config : {
264+ apiKey : 'secret-key' ,
265+ normalField : 'normal-value' ,
266+ } ,
267+ }
268+
269+ const result = redactApiKeys ( obj )
270+
271+ expect ( result . config . apiKey ) . toBe ( '***REDACTED***' )
272+ expect ( result . config . normalField ) . toBe ( 'normal-value' )
273+ } )
274+
275+ it ( 'should redact API keys in arrays' , ( ) => {
276+ const arr = [ { apiKey : 'secret-key-1' } , { apiKey : 'secret-key-2' } ]
277+
278+ const result = redactApiKeys ( arr )
279+
280+ expect ( result [ 0 ] . apiKey ) . toBe ( '***REDACTED***' )
281+ expect ( result [ 1 ] . apiKey ) . toBe ( '***REDACTED***' )
282+ } )
283+
284+ it ( 'should handle primitive values' , ( ) => {
285+ expect ( redactApiKeys ( 'string' ) ) . toBe ( 'string' )
286+ expect ( redactApiKeys ( 123 ) ) . toBe ( 123 )
287+ expect ( redactApiKeys ( null ) ) . toBe ( null )
288+ expect ( redactApiKeys ( undefined ) ) . toBe ( undefined )
289+ } )
290+
291+ it ( 'should handle complex nested structures' , ( ) => {
292+ const obj = {
293+ users : [
294+ {
295+ name : 'John' ,
296+ credentials : {
297+ apiKey : 'secret-key' ,
298+ username : 'john_doe' ,
299+ } ,
300+ } ,
301+ ] ,
302+ config : {
303+ database : {
304+ password : 'db-password' ,
305+ host : 'localhost' ,
306+ } ,
307+ } ,
308+ }
309+
310+ const result = redactApiKeys ( obj )
311+
312+ expect ( result . users [ 0 ] . name ) . toBe ( 'John' )
313+ expect ( result . users [ 0 ] . credentials . apiKey ) . toBe ( '***REDACTED***' )
314+ expect ( result . users [ 0 ] . credentials . username ) . toBe ( 'john_doe' )
315+ expect ( result . config . database . password ) . toBe ( '***REDACTED***' )
316+ expect ( result . config . database . host ) . toBe ( 'localhost' )
317+ } )
318+ } )
319+
320+ describe ( 'validateName' , ( ) => {
321+ it ( 'should remove invalid characters' , ( ) => {
322+ const result = validateName ( 'test@#$%name' )
323+ expect ( result ) . toBe ( 'testname' )
324+ } )
325+
326+ it ( 'should keep valid characters' , ( ) => {
327+ const result = validateName ( 'test_name_123' )
328+ expect ( result ) . toBe ( 'test_name_123' )
329+ } )
330+
331+ it ( 'should keep spaces' , ( ) => {
332+ const result = validateName ( 'test name' )
333+ expect ( result ) . toBe ( 'test name' )
334+ } )
335+
336+ it ( 'should handle empty string' , ( ) => {
337+ const result = validateName ( '' )
338+ expect ( result ) . toBe ( '' )
339+ } )
340+
341+ it ( 'should handle string with only invalid characters' , ( ) => {
342+ const result = validateName ( '@#$%' )
343+ expect ( result ) . toBe ( '' )
344+ } )
345+
346+ it ( 'should handle mixed valid and invalid characters' , ( ) => {
347+ const result = validateName ( 'my-workflow@2023!' )
348+ expect ( result ) . toBe ( 'myworkflow2023' )
349+ } )
350+
351+ it ( 'should collapse multiple spaces into single spaces' , ( ) => {
352+ const result = validateName ( 'test multiple spaces' )
353+ expect ( result ) . toBe ( 'test multiple spaces' )
354+ } )
355+
356+ it ( 'should handle mixed whitespace and invalid characters' , ( ) => {
357+ const result = validateName ( 'test@#$ name' )
358+ expect ( result ) . toBe ( 'test name' )
359+ } )
360+ } )
361+
362+ describe ( 'isValidName' , ( ) => {
363+ it ( 'should return true for valid names' , ( ) => {
364+ expect ( isValidName ( 'test_name' ) ) . toBe ( true )
365+ expect ( isValidName ( 'test123' ) ) . toBe ( true )
366+ expect ( isValidName ( 'test name' ) ) . toBe ( true )
367+ expect ( isValidName ( 'TestName' ) ) . toBe ( true )
368+ expect ( isValidName ( '' ) ) . toBe ( true )
369+ } )
370+
371+ it ( 'should return false for invalid names' , ( ) => {
372+ expect ( isValidName ( 'test@name' ) ) . toBe ( false )
373+ expect ( isValidName ( 'test-name' ) ) . toBe ( false )
374+ expect ( isValidName ( 'test#name' ) ) . toBe ( false )
375+ expect ( isValidName ( 'test$name' ) ) . toBe ( false )
376+ expect ( isValidName ( 'test%name' ) ) . toBe ( false )
377+ } )
378+ } )
379+
380+ describe ( 'getInvalidCharacters' , ( ) => {
381+ it ( 'should return empty array for valid names' , ( ) => {
382+ const result = getInvalidCharacters ( 'test_name_123' )
383+ expect ( result ) . toEqual ( [ ] )
384+ } )
385+
386+ it ( 'should return invalid characters' , ( ) => {
387+ const result = getInvalidCharacters ( 'test@#$name' )
388+ expect ( result ) . toEqual ( [ '@' , '#' , '$' ] )
389+ } )
390+
391+ it ( 'should return unique invalid characters' , ( ) => {
392+ const result = getInvalidCharacters ( 'test@@##name' )
393+ expect ( result ) . toEqual ( [ '@' , '#' ] )
394+ } )
395+
396+ it ( 'should handle empty string' , ( ) => {
397+ const result = getInvalidCharacters ( '' )
398+ expect ( result ) . toEqual ( [ ] )
399+ } )
400+
401+ it ( 'should handle string with only invalid characters' , ( ) => {
402+ const result = getInvalidCharacters ( '@#$%' )
403+ expect ( result ) . toEqual ( [ '@' , '#' , '$' , '%' ] )
404+ } )
405+ } )
0 commit comments