@@ -2065,3 +2065,160 @@ func TestFMICacheIsolation(t *testing.T) {
20652065 t .Fatalf ("Expected 2 cache entries (1 regular + 1 FMI), got %d" , len (cache ))
20662066 }
20672067}
2068+
2069+ // TestWithAttribute validates that the WithAttribute option correctly passes
2070+ // the attribute value to extraBodyParameters in the token request
2071+ func TestWithAttribute (t * testing.T ) {
2072+ tests := []struct {
2073+ name string
2074+ attrValue string
2075+ expectInBody bool
2076+ expectedValue string
2077+ }{
2078+ {
2079+ name : "simple attribute" ,
2080+ attrValue : `{"FavoriteColor":"Blue"}` ,
2081+ expectInBody : true ,
2082+ expectedValue : `{"FavoriteColor":"Blue"}` ,
2083+ },
2084+ {
2085+ name : "nested JSON attribute" ,
2086+ attrValue : `{"FavoriteColor": "Blue", "file:/c/users/foobar/documents/info.txt": "{\"permissions\":[\"read\",\"write\"]}"}` ,
2087+ expectInBody : true ,
2088+ expectedValue : `{"FavoriteColor": "Blue", "file:/c/users/foobar/documents/info.txt": "{\"permissions\":[\"read\",\"write\"]}"}` ,
2089+ },
2090+ {
2091+ name : "empty attribute" ,
2092+ attrValue : "" ,
2093+ expectInBody : false ,
2094+ expectedValue : "" ,
2095+ },
2096+ {
2097+ name : "complex nested structure" ,
2098+ attrValue : `{"user":"admin","config":"{\"level\":5,\"permissions\":[\"read\",\"write\",\"execute\"]}"}` ,
2099+ expectInBody : true ,
2100+ expectedValue : `{"user":"admin","config":"{\"level\":5,\"permissions\":[\"read\",\"write\",\"execute\"]}"}` ,
2101+ },
2102+ }
2103+
2104+ for _ , tt := range tests {
2105+ t .Run (tt .name , func (t * testing.T ) {
2106+ cred , err := NewCredFromSecret (fakeSecret )
2107+ if err != nil {
2108+ t .Fatal (err )
2109+ }
2110+
2111+ lmo := "login.microsoftonline.com"
2112+ tenant := "test-tenant"
2113+ accessToken := "test-access-token"
2114+ authority := fmt .Sprintf (authorityFmt , lmo , tenant )
2115+
2116+ mockClient := mock .NewClient ()
2117+ mockClient .AppendResponse (mock .WithBody (mock .GetTenantDiscoveryBody (lmo , tenant )))
2118+ mockClient .AppendResponse (
2119+ mock .WithBody (mock .GetAccessTokenBody (accessToken , mock .GetIDToken (tenant , authority ), "" , "" , 3600 , 0 )),
2120+ mock .WithCallback (func (r * http.Request ) {
2121+ // Parse the request body
2122+ if err := r .ParseForm (); err != nil {
2123+ t .Fatal (err )
2124+ }
2125+
2126+ // Check if "attributes" parameter is present in the request body
2127+ if tt .expectInBody {
2128+ if ! r .Form .Has ("attributes" ) {
2129+ t .Fatal ("Expected 'attributes' parameter in request body, but it was not found" )
2130+ }
2131+
2132+ actualValue := r .Form .Get ("attributes" )
2133+ if actualValue != tt .expectedValue {
2134+ t .Fatalf ("Expected attributes value %q, got %q" , tt .expectedValue , actualValue )
2135+ }
2136+ } else {
2137+ if r .Form .Has ("attributes" ) {
2138+ t .Fatalf ("Did not expect 'attributes' parameter in request body, but found: %q" , r .Form .Get ("attributes" ))
2139+ }
2140+ }
2141+ }),
2142+ )
2143+
2144+ client , err := New (authority , fakeClientID , cred , WithHTTPClient (mockClient ), WithInstanceDiscovery (false ))
2145+ if err != nil {
2146+ t .Fatal (err )
2147+ }
2148+
2149+ ctx := context .Background ()
2150+ var ar AuthResult
2151+ if tt .attrValue != "" {
2152+ ar , err = client .AcquireTokenByCredential (ctx , tokenScope , WithAttribute (tt .attrValue ))
2153+ } else {
2154+ ar , err = client .AcquireTokenByCredential (ctx , tokenScope , WithAttribute (tt .attrValue ))
2155+ }
2156+
2157+ if err != nil {
2158+ t .Fatalf ("AcquireTokenByCredential failed: %v" , err )
2159+ }
2160+
2161+ if ar .AccessToken != accessToken {
2162+ t .Fatalf ("Expected access token %q, got %q" , accessToken , ar .AccessToken )
2163+ }
2164+ })
2165+ }
2166+ }
2167+
2168+ // TestWithAttributeAndFMIPath validates that WithAttribute and WithFMIPath can be used together
2169+ // and both values are correctly passed to extraBodyParameters
2170+ func TestWithAttributeAndFMIPath (t * testing.T ) {
2171+ cred , err := NewCredFromSecret (fakeSecret )
2172+ if err != nil {
2173+ t .Fatal (err )
2174+ }
2175+
2176+ lmo := "login.microsoftonline.com"
2177+ tenant := "test-tenant"
2178+ accessToken := "test-access-token"
2179+ authority := fmt .Sprintf (authorityFmt , lmo , tenant )
2180+ fmiPath := "test/fmi/path"
2181+ attrValue := `{"color":"blue","size":"large"}`
2182+
2183+ mockClient := mock .NewClient ()
2184+ mockClient .AppendResponse (mock .WithBody (mock .GetTenantDiscoveryBody (lmo , tenant )))
2185+ mockClient .AppendResponse (
2186+ mock .WithBody (mock .GetAccessTokenBody (accessToken , mock .GetIDToken (tenant , authority ), "" , "" , 3600 , 0 )),
2187+ mock .WithCallback (func (r * http.Request ) {
2188+ if err := r .ParseForm (); err != nil {
2189+ t .Fatal (err )
2190+ }
2191+
2192+ // Verify both parameters are present
2193+ if ! r .Form .Has ("fmi_path" ) {
2194+ t .Fatal ("Expected 'fmi_path' parameter in request body" )
2195+ }
2196+ if ! r .Form .Has ("attributes" ) {
2197+ t .Fatal ("Expected 'attributes' parameter in request body" )
2198+ }
2199+
2200+ // Verify values
2201+ if actualFMI := r .Form .Get ("fmi_path" ); actualFMI != fmiPath {
2202+ t .Fatalf ("Expected fmi_path %q, got %q" , fmiPath , actualFMI )
2203+ }
2204+ if actualAttr := r .Form .Get ("attributes" ); actualAttr != attrValue {
2205+ t .Fatalf ("Expected attributes %q, got %q" , attrValue , actualAttr )
2206+ }
2207+ }),
2208+ )
2209+
2210+ client , err := New (authority , fakeClientID , cred , WithHTTPClient (mockClient ), WithInstanceDiscovery (false ))
2211+ if err != nil {
2212+ t .Fatal (err )
2213+ }
2214+
2215+ ctx := context .Background ()
2216+ ar , err := client .AcquireTokenByCredential (ctx , tokenScope , WithFMIPath (fmiPath ), WithAttribute (attrValue ))
2217+ if err != nil {
2218+ t .Fatalf ("AcquireTokenByCredential failed: %v" , err )
2219+ }
2220+
2221+ if ar .AccessToken != accessToken {
2222+ t .Fatalf ("Expected access token %q, got %q" , accessToken , ar .AccessToken )
2223+ }
2224+ }
0 commit comments