github.com/alwitt/goutils@v0.6.4/oauth_test.go (about)

     1  package goutils_test
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/alwitt/goutils"
    13  	"github.com/apex/log"
    14  	"github.com/go-resty/resty/v2"
    15  	"github.com/google/uuid"
    16  	"github.com/jarcoal/httpmock"
    17  	"github.com/stretchr/testify/assert"
    18  )
    19  
    20  func TestClientCredOAuthTokenManager(t *testing.T) {
    21  	assert := assert.New(t)
    22  	log.SetLevel(log.DebugLevel)
    23  
    24  	utCtxt := context.Background()
    25  
    26  	httpmock.Activate()
    27  	defer httpmock.DeactivateAndReset()
    28  
    29  	testClient := resty.New()
    30  	// Install with mock
    31  	httpmock.ActivateNonDefault(testClient.GetClient())
    32  
    33  	// ------------------------------------------------------------------------------------
    34  	// Prepare mock
    35  
    36  	idpBaseURL := "http://idp.testing.dev"
    37  	configURL := fmt.Sprintf("%s/.well-known/openid-configuration", idpBaseURL)
    38  	tokenURL := fmt.Sprintf("%s/auth/token", idpBaseURL)
    39  
    40  	testCfg := goutils.ClientCredOAuthTokenManagerParam{
    41  		IDPIssuerURL:       idpBaseURL,
    42  		ClientID:           uuid.NewString(),
    43  		ClientSecret:       uuid.NewString(),
    44  		TargetAudience:     "http://application.testing.dev",
    45  		LogTags:            log.Fields{"module": "goutils", "component": "client-cred-oauth"},
    46  		CustomLogModifiers: []goutils.LogMetadataModifier{},
    47  	}
    48  
    49  	// Prepare to receive IDP config call
    50  	httpmock.RegisterResponder(
    51  		"GET",
    52  		configURL,
    53  		func(r *http.Request) (*http.Response, error) {
    54  			return httpmock.NewJsonResponse(200, map[string]string{
    55  				"token_endpoint": tokenURL,
    56  			})
    57  		},
    58  	)
    59  
    60  	uut, err := goutils.GetNewClientCredOAuthTokenManager(utCtxt, testClient, testCfg)
    61  	assert.Nil(err)
    62  
    63  	currentTime := time.Now().UTC()
    64  
    65  	// Case 0: get token
    66  	testToken0 := map[string]interface{}{
    67  		"access_token": uuid.NewString(),
    68  		"expires_in":   300,
    69  	}
    70  	{
    71  		// Prepare mock
    72  		httpmock.RegisterResponder(
    73  			"POST",
    74  			tokenURL,
    75  			func(r *http.Request) (*http.Response, error) {
    76  				req, err := io.ReadAll(r.Body)
    77  				assert.Nil(err)
    78  
    79  				reqBody := map[string]string{}
    80  				assert.Nil(json.Unmarshal(req, &reqBody))
    81  
    82  				clientID, ok := reqBody["client_id"]
    83  				assert.True(ok)
    84  				assert.Equal(testCfg.ClientID, clientID)
    85  
    86  				clientSecret, ok := reqBody["client_secret"]
    87  				assert.True(ok)
    88  				assert.Equal(testCfg.ClientSecret, clientSecret)
    89  
    90  				audience, ok := reqBody["audience"]
    91  				assert.True(ok)
    92  				assert.Equal(testCfg.TargetAudience, audience)
    93  
    94  				grantType, ok := reqBody["grant_type"]
    95  				assert.True(ok)
    96  				assert.Equal("client_credentials", grantType)
    97  
    98  				// Return the token
    99  				return httpmock.NewJsonResponse(200, testToken0)
   100  			},
   101  		)
   102  
   103  		waitChan := make(chan bool, 1)
   104  
   105  		lclCtxt, lclCancel := context.WithTimeout(utCtxt, time.Second)
   106  		go func() {
   107  			workingToken, err := uut.GetToken(lclCtxt, currentTime)
   108  			assert.Nil(err)
   109  			assert.Equal(testToken0["access_token"], workingToken)
   110  			waitChan <- true
   111  		}()
   112  
   113  		select {
   114  		case <-lclCtxt.Done():
   115  			assert.False(true, "request timed out")
   116  		case <-waitChan:
   117  			break
   118  		}
   119  		lclCancel()
   120  	}
   121  
   122  	// Clear mocks
   123  	httpmock.Reset()
   124  
   125  	// Case 1: get token again
   126  	{
   127  		lclCtxt, lclCancel := context.WithTimeout(utCtxt, time.Second)
   128  		workingToken, err := uut.GetToken(lclCtxt, currentTime)
   129  		assert.Nil(err)
   130  		assert.Equal(testToken0["access_token"], workingToken)
   131  		lclCancel()
   132  	}
   133  
   134  	// Case 2: token timeout, get new token
   135  	testToken1 := map[string]interface{}{
   136  		"access_token": uuid.NewString(),
   137  		"expires_in":   300,
   138  	}
   139  	currentTime = currentTime.Add(time.Second * 400)
   140  	{
   141  		// Prepare mock
   142  		httpmock.RegisterResponder(
   143  			"POST",
   144  			tokenURL,
   145  			func(r *http.Request) (*http.Response, error) {
   146  				return httpmock.NewJsonResponse(200, testToken1)
   147  			},
   148  		)
   149  
   150  		waitChan := make(chan bool, 1)
   151  
   152  		lclCtxt, lclCancel := context.WithTimeout(utCtxt, time.Second)
   153  		go func() {
   154  			workingToken, err := uut.GetToken(lclCtxt, currentTime)
   155  			assert.Nil(err)
   156  			assert.Equal(testToken1["access_token"], workingToken)
   157  			waitChan <- true
   158  		}()
   159  
   160  		select {
   161  		case <-lclCtxt.Done():
   162  			assert.False(true, "request timed out")
   163  		case <-waitChan:
   164  			break
   165  		}
   166  		lclCancel()
   167  	}
   168  
   169  	// Clean up
   170  	assert.Nil(uut.Stop(utCtxt))
   171  }