11import { describe , expect , it } from 'vitest' ;
22import {
3+ getHttpSpanDetailsFromUrlObject ,
34 getSanitizedUrlString ,
45 getSanitizedUrlStringFromUrlObject ,
56 isURLObjectRelative ,
@@ -344,6 +345,48 @@ describe('getSanitizedUrlStringFromUrlObject', () => {
344345 [ 'url with port 4433' , 'http://172.31.12.144:4433/test' , 'http://172.31.12.144:4433/test' ] ,
345346 [ 'url with port 443' , 'http://172.31.12.144:443/test' , 'http://172.31.12.144/test' ] ,
346347 [ 'url with IP and port 80' , 'http://172.31.12.144:80/test' , 'http://172.31.12.144/test' ] ,
348+ [ 'invalid URL' , 'invalid-url' , '/invalid-url' ] ,
349+ [ 'valid absolute URL with base' , 'https://somedomain.com' , 'https://somedomain.com/' ] ,
350+ [ 'relative URL' , '/path/to/happiness' , '/path/to/happiness' ] ,
351+ [ 'relative URL with query' , '/path/to/happiness?q=1' , '/path/to/happiness' ] ,
352+ [ 'relative URL with hash' , '/path/to/happiness#section' , '/path/to/happiness' ] ,
353+ [ 'relative URL with query and hash' , '/path/to/happiness?q=1#section' , '/path/to/happiness' ] ,
354+ [
355+ 'URL with special chars' ,
356+ 'https://somedomain.com/path/with spaces/and/special@chars' ,
357+ 'https://somedomain.com/path/with%20spaces/and/special@chars' ,
358+ ] ,
359+ [
360+ 'URL with unicode' ,
361+ 'https://somedomain.com/path/with/unicode/测试' ,
362+ 'https://somedomain.com/path/with/unicode/%E6%B5%8B%E8%AF%95' ,
363+ ] ,
364+ [ 'URL with multiple query params' , 'https://somedomain.com/path?q1=1&q2=2&q3=3' , 'https://somedomain.com/path' ] ,
365+ [ 'URL with encoded chars' , 'https://somedomain.com/path/%20%2F%3F%23' , 'https://somedomain.com/path/%20%2F%3F%23' ] ,
366+ [ 'URL with IPv4' , 'https://192.168.1.1/path' , 'https://192.168.1.1/path' ] ,
367+ [ 'URL with IPv6' , 'https://[2001:db8::1]/path' , 'https://[2001:db8::1]/path' ] ,
368+ [ 'URL with subdomain' , 'https://sub.somedomain.com/path' , 'https://sub.somedomain.com/path' ] ,
369+ [ 'URL with multiple subdomains' , 'https://sub1.sub2.somedomain.com/path' , 'https://sub1.sub2.somedomain.com/path' ] ,
370+ [ 'URL with trailing slash' , 'https://somedomain.com/path/' , 'https://somedomain.com/path/' ] ,
371+ [ 'URL with empty path' , 'https://somedomain.com' , 'https://somedomain.com/' ] ,
372+ [ 'URL with root path' , 'https://somedomain.com/' , 'https://somedomain.com/' ] ,
373+ [ 'URL with file extension' , 'https://somedomain.com/path/file.html' , 'https://somedomain.com/path/file.html' ] ,
374+ [ 'URL with custom protocol' , 'custom://somedomain.com/path' , 'custom://somedomain.com/path' ] ,
375+ [
376+ 'URL with query containing special chars' ,
377+ 'https://somedomain.com/path?q=hello+world&x=1/2' ,
378+ 'https://somedomain.com/path' ,
379+ ] ,
380+ [
381+ 'URL with hash containing special chars' ,
382+ 'https://somedomain.com/path#section/1/2' ,
383+ 'https://somedomain.com/path' ,
384+ ] ,
385+ [
386+ 'URL with all components' ,
387+ 'https://user:[email protected] :8080/path/file.html?q=1#section' , 388+ 'https://%filtered%:%filtered%@sub.somedomain.com:8080/path/file.html' ,
389+ ] ,
347390 ] ) ( 'returns a sanitized URL for a %s' , ( _ , rawUrl : string , sanitizedURL : string ) => {
348391 const urlObject = parseStringToURLObject ( rawUrl ) ;
349392 if ( ! urlObject ) {
@@ -352,3 +395,227 @@ describe('getSanitizedUrlStringFromUrlObject', () => {
352395 expect ( getSanitizedUrlStringFromUrlObject ( urlObject ) ) . toEqual ( sanitizedURL ) ;
353396 } ) ;
354397} ) ;
398+
399+ describe ( 'getHttpSpanDetailsFromUrlObject' , ( ) => {
400+ it ( 'handles undefined URL object' , ( ) => {
401+ const [ name , attributes ] = getHttpSpanDetailsFromUrlObject ( undefined , 'test-origin' ) ;
402+ expect ( name ) . toBe ( 'GET /' ) ;
403+ expect ( attributes ) . toEqual ( {
404+ 'sentry.origin' : 'test-origin' ,
405+ 'sentry.source' : 'url' ,
406+ } ) ;
407+ } ) ;
408+
409+ it ( 'handles relative URL object' , ( ) => {
410+ const urlObject = parseStringToURLObject ( '/api/users' ) ! ;
411+ const [ name , attributes ] = getHttpSpanDetailsFromUrlObject ( urlObject , 'test-origin' ) ;
412+ expect ( name ) . toBe ( 'GET /api/users' ) ;
413+ expect ( attributes ) . toEqual ( {
414+ 'sentry.origin' : 'test-origin' ,
415+ 'sentry.source' : 'url' ,
416+ 'url.path' : '/api/users' ,
417+ } ) ;
418+ } ) ;
419+
420+ it ( 'handles absolute URL object' , ( ) => {
421+ const urlObject = parseStringToURLObject ( 'https://example.com/api/users?q=test#section' ) ! ;
422+ const [ name , attributes ] = getHttpSpanDetailsFromUrlObject ( urlObject , 'test-origin' ) ;
423+ expect ( name ) . toBe ( 'GET https://example.com/api/users' ) ;
424+ expect ( attributes ) . toEqual ( {
425+ 'sentry.origin' : 'test-origin' ,
426+ 'sentry.source' : 'url' ,
427+ 'url.path' : '/api/users' ,
428+ 'url.query' : '?q=test' ,
429+ 'url.fragment' : '#section' ,
430+ 'url.full' : 'https://example.com/api/users?q=test#section' ,
431+ 'server.address' : 'example.com' ,
432+ 'url.scheme' : 'https:' ,
433+ } ) ;
434+ } ) ;
435+
436+ it ( 'handles URL object with request method' , ( ) => {
437+ const urlObject = parseStringToURLObject ( 'https://example.com/api/users' ) ! ;
438+ const [ name , attributes ] = getHttpSpanDetailsFromUrlObject ( urlObject , 'test-origin' , { method : 'POST' } ) ;
439+ expect ( name ) . toBe ( 'POST https://example.com/api/users' ) ;
440+ expect ( attributes ) . toEqual ( {
441+ 'sentry.origin' : 'test-origin' ,
442+ 'sentry.source' : 'url' ,
443+ 'url.path' : '/api/users' ,
444+ 'url.full' : 'https://example.com/api/users' ,
445+ 'server.address' : 'example.com' ,
446+ 'url.scheme' : 'https:' ,
447+ 'http.request.method' : 'POST' ,
448+ } ) ;
449+ } ) ;
450+
451+ it ( 'handles URL object with route name' , ( ) => {
452+ const urlObject = parseStringToURLObject ( 'https://example.com/api/users' ) ! ;
453+ const [ name , attributes ] = getHttpSpanDetailsFromUrlObject ( urlObject , 'test-origin' , undefined , '/api/users/:id' ) ;
454+ expect ( name ) . toBe ( 'GET /api/users/:id' ) ;
455+ expect ( attributes ) . toEqual ( {
456+ 'sentry.origin' : 'test-origin' ,
457+ 'sentry.source' : 'route' ,
458+ 'url.path' : '/api/users' ,
459+ 'url.full' : 'https://example.com/api/users' ,
460+ 'server.address' : 'example.com' ,
461+ 'url.scheme' : 'https:' ,
462+ 'http.route' : '/api/users/:id' ,
463+ } ) ;
464+ } ) ;
465+
466+ it ( 'handles root path URL' , ( ) => {
467+ const urlObject = parseStringToURLObject ( 'https://example.com/' ) ! ;
468+ const [ name , attributes ] = getHttpSpanDetailsFromUrlObject ( urlObject , 'test-origin' ) ;
469+ expect ( name ) . toBe ( 'GET https://example.com/' ) ;
470+ expect ( attributes ) . toEqual ( {
471+ 'sentry.origin' : 'test-origin' ,
472+ 'sentry.source' : 'route' ,
473+ 'url.path' : '/' ,
474+ 'url.full' : 'https://example.com/' ,
475+ 'server.address' : 'example.com' ,
476+ 'url.scheme' : 'https:' ,
477+ } ) ;
478+ } ) ;
479+
480+ it ( 'handles URL with port' , ( ) => {
481+ const urlObject = parseStringToURLObject ( 'https://example.com:8080/api/users' ) ! ;
482+ const [ name , attributes ] = getHttpSpanDetailsFromUrlObject ( urlObject , 'test-origin' ) ;
483+ expect ( name ) . toBe ( 'GET https://example.com:8080/api/users' ) ;
484+ expect ( attributes ) . toEqual ( {
485+ 'sentry.origin' : 'test-origin' ,
486+ 'sentry.source' : 'url' ,
487+ 'url.path' : '/api/users' ,
488+ 'url.full' : 'https://example.com:8080/api/users' ,
489+ 'server.address' : 'example.com' ,
490+ 'url.scheme' : 'https:' ,
491+ 'url.port' : '8080' ,
492+ } ) ;
493+ } ) ;
494+
495+ it ( 'handles URL with non-standard port and request method' , ( ) => {
496+ const urlObject = parseStringToURLObject ( 'https://example.com:3000/api/users' ) ! ;
497+ const [ name , attributes ] = getHttpSpanDetailsFromUrlObject ( urlObject , 'test-origin' , { method : 'PUT' } ) ;
498+ expect ( name ) . toBe ( 'PUT https://example.com:3000/api/users' ) ;
499+ expect ( attributes ) . toEqual ( {
500+ 'sentry.origin' : 'test-origin' ,
501+ 'sentry.source' : 'url' ,
502+ 'url.path' : '/api/users' ,
503+ 'url.full' : 'https://example.com:3000/api/users' ,
504+ 'server.address' : 'example.com' ,
505+ 'url.scheme' : 'https:' ,
506+ 'url.port' : '3000' ,
507+ 'http.request.method' : 'PUT' ,
508+ } ) ;
509+ } ) ;
510+
511+ it ( 'handles URL with route name and request method' , ( ) => {
512+ const urlObject = parseStringToURLObject ( 'https://example.com/api/users/123' ) ! ;
513+ const [ name , attributes ] = getHttpSpanDetailsFromUrlObject (
514+ urlObject ,
515+ 'test-origin' ,
516+ { method : 'PATCH' } ,
517+ '/api/users/:id' ,
518+ ) ;
519+ expect ( name ) . toBe ( 'PATCH /api/users/:id' ) ;
520+ expect ( attributes ) . toEqual ( {
521+ 'sentry.origin' : 'test-origin' ,
522+ 'sentry.source' : 'route' ,
523+ 'url.path' : '/api/users/123' ,
524+ 'url.full' : 'https://example.com/api/users/123' ,
525+ 'server.address' : 'example.com' ,
526+ 'url.scheme' : 'https:' ,
527+ 'http.route' : '/api/users/:id' ,
528+ 'http.request.method' : 'PATCH' ,
529+ } ) ;
530+ } ) ;
531+
532+ it ( 'handles URL with query params and route name' , ( ) => {
533+ const urlObject = parseStringToURLObject ( 'https://example.com/api/search?q=test&page=1' ) ! ;
534+ const [ name , attributes ] = getHttpSpanDetailsFromUrlObject ( urlObject , 'test-origin' , undefined , '/api/search' ) ;
535+ expect ( name ) . toBe ( 'GET /api/search' ) ;
536+ expect ( attributes ) . toEqual ( {
537+ 'sentry.origin' : 'test-origin' ,
538+ 'sentry.source' : 'route' ,
539+ 'url.path' : '/api/search' ,
540+ 'url.query' : '?q=test&page=1' ,
541+ 'url.full' : 'https://example.com/api/search?q=test&page=1' ,
542+ 'server.address' : 'example.com' ,
543+ 'url.scheme' : 'https:' ,
544+ 'http.route' : '/api/search' ,
545+ } ) ;
546+ } ) ;
547+
548+ it ( 'handles URL with fragment and route name' , ( ) => {
549+ const urlObject = parseStringToURLObject ( 'https://example.com/api/docs#section-1' ) ! ;
550+ const [ name , attributes ] = getHttpSpanDetailsFromUrlObject ( urlObject , 'test-origin' , undefined , '/api/docs' ) ;
551+ expect ( name ) . toBe ( 'GET /api/docs' ) ;
552+ expect ( attributes ) . toEqual ( {
553+ 'sentry.origin' : 'test-origin' ,
554+ 'sentry.source' : 'route' ,
555+ 'url.path' : '/api/docs' ,
556+ 'url.fragment' : '#section-1' ,
557+ 'url.full' : 'https://example.com/api/docs#section-1' ,
558+ 'server.address' : 'example.com' ,
559+ 'url.scheme' : 'https:' ,
560+ 'http.route' : '/api/docs' ,
561+ } ) ;
562+ } ) ;
563+
564+ it ( 'handles URL with auth credentials' , ( ) => {
565+ const urlObject = parseStringToURLObject ( 'https://user:[email protected] /api/users' ) ! ; 566+ const [ name , attributes ] = getHttpSpanDetailsFromUrlObject ( urlObject , 'test-origin' ) ;
567+ expect ( name ) . toBe ( 'GET https://%filtered%:%filtered%@example.com/api/users' ) ;
568+ expect ( attributes ) . toEqual ( {
569+ 'sentry.origin' : 'test-origin' ,
570+ 'sentry.source' : 'url' ,
571+ 'url.path' : '/api/users' ,
572+ 'url.full' :
'https://user:[email protected] /api/users' , 573+ 'server.address' : 'example.com' ,
574+ 'url.scheme' : 'https:' ,
575+ } ) ;
576+ } ) ;
577+
578+ it ( 'handles URL with IPv4 address' , ( ) => {
579+ const urlObject = parseStringToURLObject ( 'https://192.168.1.1:8080/api/users' ) ! ;
580+ const [ name , attributes ] = getHttpSpanDetailsFromUrlObject ( urlObject , 'test-origin' ) ;
581+ expect ( name ) . toBe ( 'GET https://192.168.1.1:8080/api/users' ) ;
582+ expect ( attributes ) . toEqual ( {
583+ 'sentry.origin' : 'test-origin' ,
584+ 'sentry.source' : 'url' ,
585+ 'url.path' : '/api/users' ,
586+ 'url.full' : 'https://192.168.1.1:8080/api/users' ,
587+ 'server.address' : '192.168.1.1' ,
588+ 'url.scheme' : 'https:' ,
589+ 'url.port' : '8080' ,
590+ } ) ;
591+ } ) ;
592+
593+ it ( 'handles URL with IPv6 address' , ( ) => {
594+ const urlObject = parseStringToURLObject ( 'https://[2001:db8::1]:8080/api/users' ) ! ;
595+ const [ name , attributes ] = getHttpSpanDetailsFromUrlObject ( urlObject , 'test-origin' ) ;
596+ expect ( name ) . toBe ( 'GET https://[2001:db8::1]:8080/api/users' ) ;
597+ expect ( attributes ) . toEqual ( {
598+ 'sentry.origin' : 'test-origin' ,
599+ 'sentry.source' : 'url' ,
600+ 'url.path' : '/api/users' ,
601+ 'url.full' : 'https://[2001:db8::1]:8080/api/users' ,
602+ 'server.address' : '[2001:db8::1]' ,
603+ 'url.scheme' : 'https:' ,
604+ 'url.port' : '8080' ,
605+ } ) ;
606+ } ) ;
607+
608+ it ( 'handles URL with subdomain' , ( ) => {
609+ const urlObject = parseStringToURLObject ( 'https://api.example.com/users' ) ! ;
610+ const [ name , attributes ] = getHttpSpanDetailsFromUrlObject ( urlObject , 'test-origin' ) ;
611+ expect ( name ) . toBe ( 'GET https://api.example.com/users' ) ;
612+ expect ( attributes ) . toEqual ( {
613+ 'sentry.origin' : 'test-origin' ,
614+ 'sentry.source' : 'url' ,
615+ 'url.path' : '/users' ,
616+ 'url.full' : 'https://api.example.com/users' ,
617+ 'server.address' : 'api.example.com' ,
618+ 'url.scheme' : 'https:' ,
619+ } ) ;
620+ } ) ;
621+ } ) ;
0 commit comments