@@ -588,4 +588,150 @@ describe('Events', () => {
588588 } ) ;
589589 } ) ;
590590 } ) ;
591+
592+ describe ( 'URL construction and encoding (direct API client tests)' , ( ) => {
593+ it ( 'should construct URLs with properly encoded query parameters' , async ( ) => {
594+ // Test the URL construction by creating a URL and manually calling setQueryStrings
595+ const baseUrl = new URL ( 'https://api.nylas.com/v3/grants/grant-id/events' ) ;
596+ const queryParams = {
597+ 598+ start : '1640995200' ,
599+ end : '1641081600' ,
600+ limit : 50 ,
601+ metadataPair : { 'custom-key' : 'custom-value' } ,
602+ } ;
603+
604+ // Create a real API client to test actual URL construction
605+ const realApiClient = new APIClient ( {
606+ apiKey : 'test-api-key' ,
607+ apiUri : 'https://api.nylas.com' ,
608+ timeout : 30 ,
609+ headers : { } ,
610+ } ) ;
611+
612+ // Access the private method through bracket notation for testing
613+ const setQueryStrings = ( realApiClient as any ) . setQueryStrings . bind ( realApiClient ) ;
614+ const finalUrl = setQueryStrings ( baseUrl , queryParams ) ;
615+
616+ // Verify the actual URL that was constructed
617+ const requestUrl = finalUrl . toString ( ) ;
618+ expect ( requestUrl ) . toContain ( '?' ) ; // Should have proper query separator
619+ expect ( requestUrl ) . not . toContain ( '%3F' ) ; // Should not have encoded question mark
620+ expect ( requestUrl ) . toContain ( 'calendar%40example.com' ) ; // Email should be properly encoded
621+ expect ( requestUrl ) . toContain ( 'start=1640995200' ) ;
622+ expect ( requestUrl ) . toContain ( 'end=1641081600' ) ;
623+ expect ( requestUrl ) . toContain ( 'limit=50' ) ;
624+ expect ( requestUrl ) . toContain ( 'metadata_pair=custom-key%3Acustom-value' ) ; // metadataPair should be properly formatted
625+ } ) ;
626+
627+ it ( 'should handle special characters in query parameters without double encoding' , async ( ) => {
628+ const baseUrl = new URL ( 'https://api.nylas.com/v3/grants/grant-id/events' ) ;
629+ const queryParams = {
630+ 631+ title : 'meeting with ? special chars' ,
632+ metadataPair : { 'key with spaces' : 'value with & symbols' } ,
633+ } ;
634+
635+ const realApiClient = new APIClient ( {
636+ apiKey : 'test-api-key' ,
637+ apiUri : 'https://api.nylas.com' ,
638+ timeout : 30 ,
639+ headers : { } ,
640+ } ) ;
641+
642+ const setQueryStrings = ( realApiClient as any ) . setQueryStrings . bind ( realApiClient ) ;
643+ const finalUrl = setQueryStrings ( baseUrl , queryParams ) ;
644+ const requestUrl = finalUrl . toString ( ) ;
645+
646+ // Verify proper encoding
647+ expect ( requestUrl ) . toContain ( '?' ) ; // Should have proper query separator
648+ expect ( requestUrl ) . not . toContain ( '%3F' ) ; // Should not have encoded question mark
649+ expect ( requestUrl ) . toContain ( 'calendar%2Btest%40example.com' ) ; // + and @ should be properly encoded
650+ expect ( requestUrl ) . toContain ( 'title=meeting%20with%20%3F%20special%20chars' ) ; // Space and ? should be encoded
651+ expect ( requestUrl ) . toContain ( 'metadata_pair=key%20with%20spaces%3Avalue%20with%20%26%20symbols' ) ; // Complex metadata
652+ } ) ;
653+
654+ it ( 'should not double-encode already encoded parameters' , async ( ) => {
655+ const baseUrl = new URL ( 'https://api.nylas.com/v3/grants/grant-id/events' ) ;
656+ const queryParams = {
657+ calendarId : 'calendar%40example.com' , // Already URL encoded
658+ title : 'meeting%20with%20special%20chars' , // Already URL encoded
659+ } ;
660+
661+ const realApiClient = new APIClient ( {
662+ apiKey : 'test-api-key' ,
663+ apiUri : 'https://api.nylas.com' ,
664+ timeout : 30 ,
665+ headers : { } ,
666+ } ) ;
667+
668+ const setQueryStrings = ( realApiClient as any ) . setQueryStrings . bind ( realApiClient ) ;
669+ const finalUrl = setQueryStrings ( baseUrl , queryParams ) ;
670+ const requestUrl = finalUrl . toString ( ) ;
671+
672+ // Verify no double encoding occurred
673+ expect ( requestUrl ) . toContain ( '?' ) ; // Should have proper query separator
674+ expect ( requestUrl ) . not . toContain ( '%3F' ) ; // Should not have encoded question mark
675+ expect ( requestUrl ) . toContain ( 'calendar%40example.com' ) ; // Should remain as provided (no double encoding)
676+ expect ( requestUrl ) . toContain ( 'title=meeting%20with%20special%20chars' ) ; // Should remain as provided
677+ } ) ;
678+
679+ it ( 'should handle array parameters correctly' , async ( ) => {
680+ const baseUrl = new URL ( 'https://api.nylas.com/v3/grants/grant-id/events' ) ;
681+ const queryParams = {
682+ calendarId : 'primary' ,
683+ 684+ eventType : [ 'default' as const , 'outOfOffice' as const ] ,
685+ } ;
686+
687+ const realApiClient = new APIClient ( {
688+ apiKey : 'test-api-key' ,
689+ apiUri : 'https://api.nylas.com' ,
690+ timeout : 30 ,
691+ headers : { } ,
692+ } ) ;
693+
694+ const setQueryStrings = ( realApiClient as any ) . setQueryStrings . bind ( realApiClient ) ;
695+ const finalUrl = setQueryStrings ( baseUrl , queryParams ) ;
696+ const requestUrl = finalUrl . toString ( ) ;
697+
698+ // Verify array parameters are handled correctly
699+ expect ( requestUrl ) . toContain ( '?' ) ; // Should have proper query separator
700+ expect ( requestUrl ) . not . toContain ( '%3F' ) ; // Should not have encoded question mark
701+ expect ( requestUrl ) . toContain ( 'attendees=user1%40example.com' ) ;
702+ expect ( requestUrl ) . toContain ( 'attendees=user2%40example.com' ) ;
703+ expect ( requestUrl ) . toContain ( 'event_type=default' ) ;
704+ expect ( requestUrl ) . toContain ( 'event_type=outOfOffice' ) ;
705+ } ) ;
706+
707+ it ( 'should handle complex metadata pairs correctly' , async ( ) => {
708+ const baseUrl = new URL ( 'https://api.nylas.com/v3/grants/grant-id/events' ) ;
709+ const queryParams = {
710+ calendarId : 'primary' ,
711+ metadataPair : {
712+ 'key with spaces' : 'value with & symbols' ,
713+ 'another-key' : 'another-value' ,
714+ 'special-chars' : 'value with ? and = signs'
715+ } ,
716+ } ;
717+
718+ const realApiClient = new APIClient ( {
719+ apiKey : 'test-api-key' ,
720+ apiUri : 'https://api.nylas.com' ,
721+ timeout : 30 ,
722+ headers : { } ,
723+ } ) ;
724+
725+ const setQueryStrings = ( realApiClient as any ) . setQueryStrings . bind ( realApiClient ) ;
726+ const finalUrl = setQueryStrings ( baseUrl , queryParams ) ;
727+ const requestUrl = finalUrl . toString ( ) ;
728+
729+ // Verify metadata pairs are handled correctly
730+ expect ( requestUrl ) . toContain ( '?' ) ; // Should have proper query separator
731+ expect ( requestUrl ) . not . toContain ( '%3F' ) ; // Should not have encoded question mark
732+ expect ( requestUrl ) . toContain ( 'metadata_pair=key%20with%20spaces%3Avalue%20with%20%26%20symbols' ) ;
733+ expect ( requestUrl ) . toContain ( 'metadata_pair=another-key%3Aanother-value' ) ;
734+ expect ( requestUrl ) . toContain ( 'metadata_pair=special-chars%3Avalue%20with%20%3F%20and%20%3D%20signs' ) ;
735+ } ) ;
736+ } ) ;
591737} ) ;
0 commit comments