@@ -63,6 +63,136 @@ public async Task TokenHandler_AccessorOrganizationTakesPrecedenceOverConfig()
6363 . MustHaveHappenedOnceExactly ( ) ;
6464 }
6565
66+ [ Fact ]
67+ public async Task TokenHandler_UsesOrganizationResolver_WhenAccessorAndConfigAreEmpty ( )
68+ {
69+ var cache = A . Fake < IAuth0TokenCache > ( ) ;
70+ A . CallTo ( ( ) => cache . GetTokenAsync ( A < string > . _ , A < string ? > . _ , A < CancellationToken > . _ ) )
71+ . Returns ( "access-token" ) ;
72+
73+ var accessor = new HttpClientOrganizationAccessor ( ) ;
74+ var config = new Auth0TokenHandlerConfig
75+ {
76+ Audience = "api://test" ,
77+ OrganizationResolver = _ => "org-from-resolver"
78+ } ;
79+
80+ using var invoker = BuildInvoker ( cache , config , accessor ) ;
81+ await invoker . SendAsync ( new HttpRequestMessage ( HttpMethod . Get , "https://example.com" ) , CancellationToken . None ) ;
82+
83+ A . CallTo ( ( ) => cache . GetTokenAsync ( "api://test" , "org-from-resolver" , A < CancellationToken > . _ ) )
84+ . MustHaveHappenedOnceExactly ( ) ;
85+ }
86+
87+ [ Fact ]
88+ public async Task TokenHandler_ConfigOrganizationTakesPrecedenceOverResolver ( )
89+ {
90+ var cache = A . Fake < IAuth0TokenCache > ( ) ;
91+ A . CallTo ( ( ) => cache . GetTokenAsync ( A < string > . _ , A < string ? > . _ , A < CancellationToken > . _ ) )
92+ . Returns ( "access-token" ) ;
93+
94+ var accessor = new HttpClientOrganizationAccessor ( ) ;
95+ var config = new Auth0TokenHandlerConfig
96+ {
97+ Audience = "api://test" ,
98+ Organization = "org-from-config" ,
99+ OrganizationResolver = _ => "org-from-resolver"
100+ } ;
101+
102+ using var invoker = BuildInvoker ( cache , config , accessor ) ;
103+ await invoker . SendAsync ( new HttpRequestMessage ( HttpMethod . Get , "https://example.com" ) , CancellationToken . None ) ;
104+
105+ A . CallTo ( ( ) => cache . GetTokenAsync ( "api://test" , "org-from-config" , A < CancellationToken > . _ ) )
106+ . MustHaveHappenedOnceExactly ( ) ;
107+ }
108+
109+ [ Fact ]
110+ public async Task TokenHandler_AccessorTakesPrecedenceOverResolver ( )
111+ {
112+ var cache = A . Fake < IAuth0TokenCache > ( ) ;
113+ A . CallTo ( ( ) => cache . GetTokenAsync ( A < string > . _ , A < string ? > . _ , A < CancellationToken > . _ ) )
114+ . Returns ( "access-token" ) ;
115+
116+ var accessor = new HttpClientOrganizationAccessor { Organization = "org-from-accessor" } ;
117+ var config = new Auth0TokenHandlerConfig
118+ {
119+ Audience = "api://test" ,
120+ OrganizationResolver = _ => "org-from-resolver"
121+ } ;
122+
123+ using var invoker = BuildInvoker ( cache , config , accessor ) ;
124+ await invoker . SendAsync ( new HttpRequestMessage ( HttpMethod . Get , "https://example.com" ) , CancellationToken . None ) ;
125+
126+ A . CallTo ( ( ) => cache . GetTokenAsync ( "api://test" , "org-from-accessor" , A < CancellationToken > . _ ) )
127+ . MustHaveHappenedOnceExactly ( ) ;
128+ }
129+
130+ [ Fact ]
131+ public async Task TokenHandler_OrganizationResolver_ReceivesHttpRequestMessage ( )
132+ {
133+ var cache = A . Fake < IAuth0TokenCache > ( ) ;
134+ A . CallTo ( ( ) => cache . GetTokenAsync ( A < string > . _ , A < string ? > . _ , A < CancellationToken > . _ ) )
135+ . Returns ( "access-token" ) ;
136+
137+ HttpRequestMessage ? capturedRequest = null ;
138+
139+ var accessor = new HttpClientOrganizationAccessor ( ) ;
140+ var config = new Auth0TokenHandlerConfig
141+ {
142+ Audience = "api://test" ,
143+ OrganizationResolver = req =>
144+ {
145+ capturedRequest = req ;
146+ return "org-resolved" ;
147+ }
148+ } ;
149+
150+ using var invoker = BuildInvoker ( cache , config , accessor ) ;
151+ var request = new HttpRequestMessage ( HttpMethod . Get , "https://example.com/api/resource" ) ;
152+ await invoker . SendAsync ( request , CancellationToken . None ) ;
153+
154+ Assert . NotNull ( capturedRequest ) ;
155+ Assert . Equal ( "https://example.com/api/resource" , capturedRequest . RequestUri ! . ToString ( ) ) ;
156+ }
157+
158+ [ Fact ]
159+ public async Task TokenHandler_OrganizationResolver_ReturnsNull_PassesNullToCache ( )
160+ {
161+ var cache = A . Fake < IAuth0TokenCache > ( ) ;
162+ A . CallTo ( ( ) => cache . GetTokenAsync ( A < string > . _ , A < string ? > . _ , A < CancellationToken > . _ ) )
163+ . Returns ( "access-token" ) ;
164+
165+ var accessor = new HttpClientOrganizationAccessor ( ) ;
166+ var config = new Auth0TokenHandlerConfig
167+ {
168+ Audience = "api://test" ,
169+ OrganizationResolver = _ => null
170+ } ;
171+
172+ using var invoker = BuildInvoker ( cache , config , accessor ) ;
173+ await invoker . SendAsync ( new HttpRequestMessage ( HttpMethod . Get , "https://example.com" ) , CancellationToken . None ) ;
174+
175+ A . CallTo ( ( ) => cache . GetTokenAsync ( "api://test" , null , A < CancellationToken > . _ ) )
176+ . MustHaveHappenedOnceExactly ( ) ;
177+ }
178+
179+ [ Fact ]
180+ public async Task TokenHandler_NoOrganizationConfigured_PassesNullToCache ( )
181+ {
182+ var cache = A . Fake < IAuth0TokenCache > ( ) ;
183+ A . CallTo ( ( ) => cache . GetTokenAsync ( A < string > . _ , A < string ? > . _ , A < CancellationToken > . _ ) )
184+ . Returns ( "access-token" ) ;
185+
186+ var accessor = new HttpClientOrganizationAccessor ( ) ;
187+ var config = new Auth0TokenHandlerConfig { Audience = "api://test" } ;
188+
189+ using var invoker = BuildInvoker ( cache , config , accessor ) ;
190+ await invoker . SendAsync ( new HttpRequestMessage ( HttpMethod . Get , "https://example.com" ) , CancellationToken . None ) ;
191+
192+ A . CallTo ( ( ) => cache . GetTokenAsync ( "api://test" , null , A < CancellationToken > . _ ) )
193+ . MustHaveHappenedOnceExactly ( ) ;
194+ }
195+
66196 private static HttpMessageInvoker BuildInvoker (
67197 IAuth0TokenCache cache ,
68198 Auth0TokenHandlerConfig config ,
0 commit comments