@@ -17,7 +17,8 @@ import {
1717 refreshAuthorization ,
1818 registerClient ,
1919 selectClientAuthMethod ,
20- startAuthorization
20+ startAuthorization ,
21+ validateClientMetadataUrl
2122} from '../../src/client/auth.js' ;
2223import { createPrivateKeyJwtAuth } from '../../src/client/authExtensions.js' ;
2324
@@ -3734,4 +3735,78 @@ describe('OAuth Authorization', () => {
37343735 } ) ;
37353736 } ) ;
37363737 } ) ;
3738+
3739+ describe ( 'validateClientMetadataUrl' , ( ) => {
3740+ it ( 'passes for valid HTTPS URL with path' , ( ) => {
3741+ expect ( ( ) => validateClientMetadataUrl ( 'https://client.example.com/.well-known/oauth-client' ) ) . not . toThrow ( ) ;
3742+ } ) ;
3743+
3744+ it ( 'passes for valid HTTPS URL with multi-segment path' , ( ) => {
3745+ expect ( ( ) => validateClientMetadataUrl ( 'https://example.com/clients/metadata.json' ) ) . not . toThrow ( ) ;
3746+ } ) ;
3747+
3748+ it ( 'throws OAuthError for HTTP URL' , ( ) => {
3749+ expect ( ( ) => validateClientMetadataUrl ( 'http://client.example.com/.well-known/oauth-client' ) ) . toThrow ( OAuthError ) ;
3750+ try {
3751+ validateClientMetadataUrl ( 'http://client.example.com/.well-known/oauth-client' ) ;
3752+ } catch ( error ) {
3753+ expect ( error ) . toBeInstanceOf ( OAuthError ) ;
3754+ expect ( ( error as OAuthError ) . code ) . toBe ( OAuthErrorCode . InvalidClientMetadata ) ;
3755+ expect ( ( error as OAuthError ) . message ) . toContain ( 'http://client.example.com/.well-known/oauth-client' ) ;
3756+ }
3757+ } ) ;
3758+
3759+ it ( 'throws OAuthError for non-URL string' , ( ) => {
3760+ expect ( ( ) => validateClientMetadataUrl ( 'not-a-url' ) ) . toThrow ( OAuthError ) ;
3761+ try {
3762+ validateClientMetadataUrl ( 'not-a-url' ) ;
3763+ } catch ( error ) {
3764+ expect ( error ) . toBeInstanceOf ( OAuthError ) ;
3765+ expect ( ( error as OAuthError ) . code ) . toBe ( OAuthErrorCode . InvalidClientMetadata ) ;
3766+ expect ( ( error as OAuthError ) . message ) . toContain ( 'not-a-url' ) ;
3767+ }
3768+ } ) ;
3769+
3770+ it ( 'passes silently for empty string' , ( ) => {
3771+ expect ( ( ) => validateClientMetadataUrl ( '' ) ) . not . toThrow ( ) ;
3772+ } ) ;
3773+
3774+ it ( 'throws OAuthError for root-path HTTPS URL with trailing slash' , ( ) => {
3775+ expect ( ( ) => validateClientMetadataUrl ( 'https://client.example.com/' ) ) . toThrow ( OAuthError ) ;
3776+ try {
3777+ validateClientMetadataUrl ( 'https://client.example.com/' ) ;
3778+ } catch ( error ) {
3779+ expect ( error ) . toBeInstanceOf ( OAuthError ) ;
3780+ expect ( ( error as OAuthError ) . code ) . toBe ( OAuthErrorCode . InvalidClientMetadata ) ;
3781+ expect ( ( error as OAuthError ) . message ) . toContain ( 'https://client.example.com/' ) ;
3782+ }
3783+ } ) ;
3784+
3785+ it ( 'throws OAuthError for root-path HTTPS URL without trailing slash' , ( ) => {
3786+ expect ( ( ) => validateClientMetadataUrl ( 'https://client.example.com' ) ) . toThrow ( OAuthError ) ;
3787+ try {
3788+ validateClientMetadataUrl ( 'https://client.example.com' ) ;
3789+ } catch ( error ) {
3790+ expect ( error ) . toBeInstanceOf ( OAuthError ) ;
3791+ expect ( ( error as OAuthError ) . code ) . toBe ( OAuthErrorCode . InvalidClientMetadata ) ;
3792+ expect ( ( error as OAuthError ) . message ) . toContain ( 'https://client.example.com' ) ;
3793+ }
3794+ } ) ;
3795+
3796+ it ( 'passes silently for undefined' , ( ) => {
3797+ expect ( ( ) => validateClientMetadataUrl ( undefined ) ) . not . toThrow ( ) ;
3798+ } ) ;
3799+
3800+ it ( 'error message matches expected format' , ( ) => {
3801+ expect ( ( ) => validateClientMetadataUrl ( 'http://example.com/path' ) ) . toThrow ( OAuthError ) ;
3802+ try {
3803+ validateClientMetadataUrl ( 'http://example.com/path' ) ;
3804+ } catch ( error ) {
3805+ expect ( error ) . toBeInstanceOf ( OAuthError ) ;
3806+ expect ( ( error as OAuthError ) . message ) . toBe (
3807+ 'clientMetadataUrl must be a valid HTTPS URL with a non-root pathname, got: http://example.com/path'
3808+ ) ;
3809+ }
3810+ } ) ;
3811+ } ) ;
37373812} ) ;
0 commit comments