package graph import ( "net/http" "syscall" "testing" "time" "github.com/alcionai/clues" "github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/microsoftgraph/msgraph-sdk-go/users" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/internal/tester/tconfig" "github.com/alcionai/corso/src/pkg/account" ) type GraphIntgSuite struct { tester.Suite fakeCredentials account.M365Config credentials account.M365Config } func TestGraphIntgSuite(t *testing.T) { suite.Run(t, &GraphIntgSuite{ Suite: tester.NewIntegrationSuite( t, [][]string{tconfig.M365AcctCredEnvs}), }) } func (suite *GraphIntgSuite) SetupSuite() { t := suite.T() ctx, flush := tester.NewContext(t) defer flush() fakeAcct := tconfig.NewFakeM365Account(t) acct := tconfig.NewM365Account(t) m365, err := fakeAcct.M365Config() require.NoError(t, err, clues.ToCore(err)) suite.fakeCredentials = m365 m365, err = acct.M365Config() require.NoError(t, err, clues.ToCore(err)) suite.credentials = m365 InitializeConcurrencyLimiter(ctx, false, 0) } func (suite *GraphIntgSuite) TestCreateAdapter() { t := suite.T() adpt, err := CreateAdapter( suite.fakeCredentials.AzureTenantID, suite.fakeCredentials.AzureClientID, suite.fakeCredentials.AzureClientSecret) assert.NoError(t, err, clues.ToCore(err)) assert.NotNil(t, adpt) } func (suite *GraphIntgSuite) TestHTTPClient() { table := []struct { name string opts []Option check func(*testing.T, *http.Client) checkConfig func(*testing.T, *clientConfig) }{ { name: "no options", opts: []Option{}, check: func(t *testing.T, c *http.Client) { assert.Equal(t, defaultHTTPClientTimeout, c.Timeout, "default timeout") }, checkConfig: func(t *testing.T, c *clientConfig) { assert.Equal(t, defaultDelay, c.minDelay, "default delay") assert.Equal(t, defaultMaxRetries, c.maxRetries, "max retries") assert.Equal(t, defaultMaxRetries, c.maxConnectionRetries, "max connection retries") }, }, { name: "configured options", opts: []Option{ NoTimeout(), MaxRetries(4), MaxConnectionRetries(2), MinimumBackoff(999 * time.Millisecond), }, check: func(t *testing.T, c *http.Client) { // FIXME: Change to 0 one upstream issue is fixed assert.Equal(t, time.Duration(48*time.Hour), c.Timeout, "unlimited timeout") }, checkConfig: func(t *testing.T, c *clientConfig) { assert.Equal(t, 999*time.Millisecond, c.minDelay, "minimum delay") assert.Equal(t, 4, c.maxRetries, "max retries") assert.Equal(t, 2, c.maxConnectionRetries, "max connection retries") }, }, { name: "below minimums", opts: []Option{ NoTimeout(), MaxRetries(-1), MaxConnectionRetries(-1), MinimumBackoff(0), }, check: func(t *testing.T, c *http.Client) { // FIXME: Change to 0 one upstream issue is fixed assert.Equal(t, time.Duration(48*time.Hour), c.Timeout, "unlimited timeout") }, checkConfig: func(t *testing.T, c *clientConfig) { assert.Equal(t, 100*time.Millisecond, c.minDelay, "minimum delay") assert.Equal(t, 0, c.maxRetries, "max retries") assert.Equal(t, 0, c.maxConnectionRetries, "max connection retries") }, }, { name: "above maximums", opts: []Option{ NoTimeout(), MaxRetries(9001), MaxConnectionRetries(9001), MinimumBackoff(999 * time.Second), }, check: func(t *testing.T, c *http.Client) { // FIXME: Change to 0 one upstream issue is fixed assert.Equal(t, time.Duration(48*time.Hour), c.Timeout, "unlimited timeout") }, checkConfig: func(t *testing.T, c *clientConfig) { assert.Equal(t, 5*time.Second, c.minDelay, "minimum delay") assert.Equal(t, 5, c.maxRetries, "max retries") assert.Equal(t, 5, c.maxConnectionRetries, "max connection retries") }, }, } for _, test := range table { suite.Run(test.name, func() { t := suite.T() cli, cc := KiotaHTTPClient(test.opts...) assert.NotNil(t, cli) test.check(t, cli) test.checkConfig(t, cc) }) } } func (suite *GraphIntgSuite) TestSerializationEndPoint() { t := suite.T() adpt, err := CreateAdapter( suite.fakeCredentials.AzureTenantID, suite.fakeCredentials.AzureClientID, suite.fakeCredentials.AzureClientSecret) require.NoError(t, err, clues.ToCore(err)) serv := NewService(adpt) email := models.NewMessage() subject := "TestSerializationEndPoint" email.SetSubject(&subject) byteArray, err := serv.Serialize(email) assert.NoError(t, err, clues.ToCore(err)) assert.NotNil(t, byteArray) t.Log(string(byteArray)) } func (suite *GraphIntgSuite) TestAdapterWrap_catchesPanic() { t := suite.T() ctx, flush := tester.NewContext(t) defer flush() url := "https://graph.microsoft.com/fnords/beaux/regard" // the panics should get caught and returned as errors alwaysPanicMiddleware := mwForceResp{ alternate: func(req *http.Request) (bool, *http.Response, error) { panic(clues.New("intentional panic")) }, } adpt, err := CreateAdapter( suite.credentials.AzureTenantID, suite.credentials.AzureClientID, suite.credentials.AzureClientSecret, appendMiddleware(&alwaysPanicMiddleware)) require.NoError(t, err, clues.ToCore(err)) // the query doesn't matter _, err = users.NewItemCalendarsItemEventsDeltaRequestBuilder(url, adpt).Get(ctx, nil) require.Error(t, err, clues.ToCore(err)) require.Contains(t, err.Error(), "panic", clues.ToCore(err)) // the query doesn't matter _, err = NewService(adpt).Client().Users().Get(ctx, nil) require.Error(t, err, clues.ToCore(err)) require.Contains(t, err.Error(), "panic", clues.ToCore(err)) } func (suite *GraphIntgSuite) TestAdapterWrap_retriesConnectionClose() { t := suite.T() ctx, flush := tester.NewContext(t) defer flush() url := "https://graph.microsoft.com/fnords/beaux/regard" count := 0 // the panics should get caught and returned as errors alwaysECONNRESET := mwForceResp{ err: syscall.ECONNRESET, alternate: func(req *http.Request) (bool, *http.Response, error) { count++ return false, nil, nil }, } adpt, err := CreateAdapter( suite.credentials.AzureTenantID, suite.credentials.AzureClientID, suite.credentials.AzureClientSecret, appendMiddleware(&alwaysECONNRESET)) require.NoError(t, err, clues.ToCore(err)) // the query doesn't matter _, err = users.NewItemCalendarsItemEventsDeltaRequestBuilder(url, adpt).Get(ctx, nil) require.ErrorIs(t, err, syscall.ECONNRESET, clues.ToCore(err)) require.Equal(t, 16, count, "number of retries") count = 0 // the query doesn't matter _, err = NewService(adpt).Client().Users().Get(ctx, nil) require.ErrorIs(t, err, syscall.ECONNRESET, clues.ToCore(err)) require.Equal(t, 16, count, "number of retries") }