@@ -17,6 +17,9 @@ package http
1717import (
1818 "encoding/json"
1919 "fmt"
20+ "io"
21+ "net/http"
22+ "net/http/httptest"
2023 "testing"
2124
2225 "github.com/stretchr/testify/require"
@@ -204,3 +207,95 @@ func TestClientAuth(t *testing.T) {
204207 require .NoError (t , c .auth (ctx ))
205208 require .NoError (t , c .refresh (ctx ))
206209}
210+
211+ func TestOAuthClientCredentials (t * testing.T ) {
212+ // 1. Create a mock OAuth server
213+ ts := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
214+ if r .URL .Path == "/token" {
215+ // Verify Header
216+ contentType := r .Header .Get ("Content-Type" )
217+ if contentType != "application/x-www-form-urlencoded" {
218+ w .WriteHeader (http .StatusBadRequest )
219+ w .Write ([]byte (fmt .Sprintf ("Invalid Content-Type: %s" , contentType )))
220+ return
221+ }
222+
223+ // Verify Body
224+ body , err := io .ReadAll (r .Body )
225+ if err != nil {
226+ w .WriteHeader (http .StatusInternalServerError )
227+ return
228+ }
229+ bodyStr := string (body )
230+ expectedBody := "grant_type=client_credentials&client_id=test&client_secret=test&scope=https://eventhubs.azure.net/.default"
231+ if bodyStr != expectedBody {
232+ w .WriteHeader (http .StatusBadRequest )
233+ w .Write ([]byte (fmt .Sprintf ("Invalid Body: %s" , bodyStr )))
234+ return
235+ }
236+
237+ // Return Token
238+ w .Header ().Set ("Content-Type" , "application/json" )
239+ json .NewEncoder (w ).Encode (map [string ]interface {}{
240+ "access_token" : "mock_access_token" ,
241+ "expires_in" : 3600 ,
242+ })
243+ return
244+ }
245+
246+ // Verify Protected Resource Access
247+ if r .URL .Path == "/data" {
248+ authHeader := r .Header .Get ("Authorization" )
249+ if authHeader != "Bearer mock_access_token" {
250+ w .WriteHeader (http .StatusUnauthorized )
251+ return
252+ }
253+ w .WriteHeader (http .StatusOK )
254+ w .Write ([]byte (`{"status":"ok"}` ))
255+ return
256+ }
257+
258+ w .WriteHeader (http .StatusNotFound )
259+ }))
260+ defer ts .Close ()
261+
262+ // 2. Configure Client with OAuth
263+ ctx := mockContext .NewMockContext ("rule1" , "op1" )
264+ c := & ClientConf {}
265+
266+ // Simulation of user configuration
267+ props := map [string ]interface {}{
268+ "url" : ts .URL + "/data" ,
269+ "method" : "POST" ,
270+ "headers" : map [string ]interface {}{
271+ "Authorization" : "Bearer {{.access_token}}" ,
272+ },
273+ "oauth" : map [string ]interface {}{
274+ "access" : map [string ]interface {}{
275+ "url" : ts .URL + "/token" ,
276+ // Manually constructed body for client credentials
277+ "body" : "grant_type=client_credentials&client_id=test&client_secret=test&scope=https://eventhubs.azure.net/.default" ,
278+ // WORKAROUND: Explicitly set Content-Type header
279+ "headers" : map [string ]interface {}{
280+ "Content-Type" : "application/x-www-form-urlencoded" ,
281+ },
282+ "expire" : "3600" ,
283+ },
284+ },
285+ }
286+
287+ err := c .InitConf (ctx , "" , props )
288+ require .NoError (t , err )
289+
290+ // 3. Connect (Triggers Auth)
291+ // This is where the auth flow happens. If it fails (e.g. wrong content type), this should error.
292+ err = c .Conn (ctx )
293+ require .NoError (t , err , "Connection failed, likely due to auth failure" )
294+
295+ // 4. Send Data (Verifies Token Usage)
296+ data , _ := json .Marshal (map [string ]interface {}{"data" : 123 })
297+ resp , err := c .Send (ctx , "json" , "POST" , c .config .Url , c .parsedHeaders , nil , "" , data )
298+ require .NoError (t , err )
299+ defer resp .Body .Close ()
300+ require .Equal (t , http .StatusOK , resp .StatusCode )
301+ }
0 commit comments