diff --git a/README.md b/README.md index ecff0b6..60cabe9 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,7 @@ Returns a new Salesforce instance given a user's credentials. - `creds`: a struct containing the necessary credentials to authenticate into a Salesforce org - [Creating a Connected App in Salesforce](https://help.salesforce.com/s/articleView?id=sf.connected_app_create.htm&type=5) - [Review Salesforce oauth flows](https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_flows.htm&type=5) +- If an operation fails with the Error Code `INVALID_SESSION_ID`, go-salesforce will attempt to refresh the session by resubmitting the same credentials used during initialization [Client Credentials Flow](https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_client_credentials_flow.htm&type=5) @@ -655,7 +656,7 @@ Create Bulk API Jobs to query, insert, update, upsert, and delete large collecti - [Review Salesforce REST API resources for Bulk v2](https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/bulk_api_2_0.htm) - Work with large lists of records by passing either a slice or records or the path to a csv file -- Jobs can run asynchronously and optionally wait for them to finish so errors are available +- Jobs can run asynchronously or synchronously ### QueryBulkExport @@ -716,7 +717,7 @@ Inserts a list of salesforce records using Bulk API v2, returning a list of Job - `sObjectName`: API name of Salesforce object - `records`: a slice of salesforce records - `batchSize`: `1 <= batchSize <= 10000` -- `waitForResults`: denotes whether to wait for jobs to finish and return any errors if they are encountered during the operation +- `waitForResults`: denotes whether to wait for jobs to finish ```go type Contact struct { @@ -748,7 +749,7 @@ Inserts a collection of salesforce records from a csv file using Bulk API v2, re - `sObjectName`: API name of Salesforce object - `filePath`: path to a csv file containing salesforce data - `batchSize`: `1 <= batchSize <= 10000` -- `waitForResults`: denotes whether to wait for jobs to finish and return any errors if they are encountered during the operation +- `waitForResults`: denotes whether to wait for jobs to finish `data/avengers.csv` @@ -776,7 +777,7 @@ Updates a list of salesforce records using Bulk API v2, returning a list of Job - `records`: a slice of salesforce records - An Id is required - `batchSize`: `1 <= batchSize <= 10000` -- `waitForResults`: denotes whether to wait for jobs to finish and return any errors if they are encountered during the operation +- `waitForResults`: denotes whether to wait for jobs to finish ```go type Contact struct { @@ -812,7 +813,7 @@ Updates a collection of salesforce records from a csv file using Bulk API v2, re - `filePath`: path to a csv file containing salesforce data - An Id is required within csv data - `batchSize`: `1 <= batchSize <= 10000` -- `waitForResults`: denotes whether to wait for jobs to finish and return any errors if they are encountered during the operation +- `waitForResults`: denotes whether to wait for jobs to finish `data/update_avengers.csv` @@ -844,7 +845,7 @@ Updates (or inserts) a list of salesforce records using Bulk API v2, returning a - `records`: a slice of salesforce records - A value for the External Id is required - `batchSize`: `1 <= batchSize <= 10000` -- `waitForResults`: denotes whether to wait for jobs to finish and return any errors if they are encountered during the operation +- `waitForResults`: denotes whether to wait for jobs to finish ```go type ContactWithExternalId struct { @@ -881,7 +882,7 @@ Updates (or inserts) a collection of salesforce records from a csv file using Bu - `filePath`: path to a csv file containing salesforce data - A value for the External Id is required within csv data - `batchSize`: `1 <= batchSize <= 10000` -- `waitForResults`: denotes whether to wait for jobs to finish and return any errors if they are encountered during the operation +- `waitForResults`: denotes whether to wait for jobs to finish `data/upsert_avengers.csv` @@ -910,7 +911,7 @@ Deletes a list of salesforce records using Bulk API v2, returning a list of Job - `records`: a slice of salesforce records - should only contain Ids - `batchSize`: `1 <= batchSize <= 10000` -- `waitForResults`: denotes whether to wait for jobs to finish and return any errors if they are encountered during the operation +- `waitForResults`: denotes whether to wait for jobs to finish ```go type Contact struct { @@ -943,7 +944,7 @@ Deletes a collection of salesforce records from a csv file using Bulk API v2, re - `filePath`: path to a csv file containing salesforce data - should only contain Ids - `batchSize`: `1 <= batchSize <= 10000` -- `waitForResults`: denotes whether to wait for jobs to finish and return any errors if they are encountered during the operation +- `waitForResults`: denotes whether to wait for jobs to finish `data/delete_avengers.csv` diff --git a/auth.go b/auth.go index 7366745..c12bc51 100644 --- a/auth.go +++ b/auth.go @@ -17,6 +17,8 @@ type authentication struct { Scope string `json:"scope"` IssuedAt string `json:"issued_at"` Signature string `json:"signature"` + grantType string + creds Creds } type Creds struct { @@ -30,8 +32,9 @@ type Creds struct { } const ( - grantTypePassword = "password" + grantTypeUsernamePassword = "password" grantTypeClientCredentials = "client_credentials" + grantTypeAccessToken = "access_token" ) func validateAuth(sf Salesforce) error { @@ -45,7 +48,11 @@ func validateSession(auth authentication) error { if err := validateAuth(Salesforce{auth: &auth}); err != nil { return err } - _, err := doRequest(http.MethodGet, "/limits", jsonType, auth, "") + _, err := doRequest(&auth, requestPayload{ + method: http.MethodGet, + uri: "/limits", + content: jsonType, + }) if err != nil { return err } @@ -53,6 +60,46 @@ func validateSession(auth authentication) error { return nil } +func refreshSession(auth *authentication) error { + var refreshedAuth *authentication + var err error + + switch grantType := auth.grantType; grantType { + case grantTypeClientCredentials: + refreshedAuth, err = clientCredentialsFlow( + auth.InstanceUrl, + auth.creds.ConsumerKey, + auth.creds.ConsumerSecret, + ) + case grantTypeUsernamePassword: + refreshedAuth, err = usernamePasswordFlow( + auth.InstanceUrl, + auth.creds.Username, + auth.creds.Password, + auth.creds.SecurityToken, + auth.creds.ConsumerKey, + auth.creds.ConsumerSecret, + ) + default: + return errors.New("invalid session, unable to refresh session") + } + + if err != nil { + return err + } + + if refreshedAuth == nil { + return errors.New("missing refresh auth") + } + + auth.AccessToken = refreshedAuth.AccessToken + auth.IssuedAt = refreshedAuth.IssuedAt + auth.Signature = refreshedAuth.Signature + auth.Id = refreshedAuth.Id + + return nil +} + func doAuth(url string, body *strings.Reader) (*authentication, error) { resp, err := http.Post(url, "application/x-www-form-urlencoded", body) if err != nil { @@ -79,7 +126,7 @@ func doAuth(url string, body *strings.Reader) (*authentication, error) { func usernamePasswordFlow(domain string, username string, password string, securityToken string, consumerKey string, consumerSecret string) (*authentication, error) { payload := url.Values{ - "grant_type": {grantTypePassword}, + "grant_type": {grantTypeUsernamePassword}, "client_id": {consumerKey}, "client_secret": {consumerSecret}, "username": {username}, @@ -91,6 +138,7 @@ func usernamePasswordFlow(domain string, username string, password string, secur if err != nil { return nil, err } + auth.grantType = grantTypeUsernamePassword return auth, nil } @@ -106,6 +154,7 @@ func clientCredentialsFlow(domain string, consumerKey string, consumerSecret str if err != nil { return nil, err } + auth.grantType = grantTypeClientCredentials return auth, nil } @@ -114,5 +163,6 @@ func setAccessToken(domain string, accessToken string) (*authentication, error) if err := validateSession(*auth); err != nil { return nil, err } + auth.grantType = grantTypeAccessToken return auth, nil } diff --git a/auth_test.go b/auth_test.go index 2c7e8a6..14a467d 100644 --- a/auth_test.go +++ b/auth_test.go @@ -48,6 +48,7 @@ func Test_usernamePasswordFlow(t *testing.T) { Id: "123abc", IssuedAt: "01/01/1970", Signature: "signed", + grantType: grantTypeUsernamePassword, } server, _ := setupTestServer(auth, http.StatusOK) defer server.Close() @@ -117,6 +118,7 @@ func Test_clientCredentialsFlow(t *testing.T) { Id: "123abc", IssuedAt: "01/01/1970", Signature: "signed", + grantType: grantTypeClientCredentials, } server, _ := setupTestServer(auth, http.StatusOK) defer server.Close() @@ -233,3 +235,73 @@ func Test_setAccessToken(t *testing.T) { }) } } + +func Test_refreshSession(t *testing.T) { + refreshedAuth := authentication{ + AccessToken: "1234", + InstanceUrl: "example.com", + Id: "123abc", + IssuedAt: "01/01/1970", + Signature: "signed", + } + serverClientCredentials, sfAuthClientCredentials := setupTestServer(refreshedAuth, http.StatusOK) + defer serverClientCredentials.Close() + sfAuthClientCredentials.grantType = grantTypeClientCredentials + + serverUserNamePassword, sfAuthUserNamePassword := setupTestServer(refreshedAuth, http.StatusOK) + defer serverUserNamePassword.Close() + sfAuthUserNamePassword.grantType = grantTypeUsernamePassword + + serverNoGrantType, sfAuthNoGrantType := setupTestServer(refreshedAuth, http.StatusOK) + defer serverNoGrantType.Close() + + serverBadRequest, sfAuthBadRequest := setupTestServer("", http.StatusBadGateway) + defer serverBadRequest.Close() + sfAuthBadRequest.grantType = grantTypeClientCredentials + + serverNoRefresh, sfAuthNoRefresh := setupTestServer("", http.StatusOK) + defer serverNoRefresh.Close() + sfAuthNoRefresh.grantType = grantTypeClientCredentials + + type args struct { + auth *authentication + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "refresh_client_credentials", + args: args{auth: &sfAuthClientCredentials}, + wantErr: false, + }, + { + name: "refresh_username_password", + args: args{auth: &sfAuthUserNamePassword}, + wantErr: false, + }, + { + name: "error_no_grant_type", + args: args{auth: &sfAuthNoGrantType}, + wantErr: true, + }, + { + name: "error_bad_request", + args: args{auth: &sfAuthBadRequest}, + wantErr: true, + }, + { + name: "no_refresh", + args: args{auth: &sfAuthNoRefresh}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := refreshSession(tt.args.auth); (err != nil) != tt.wantErr { + t.Errorf("refreshSession() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/bulk.go b/bulk.go index c5ad93d..aaae8e3 100644 --- a/bulk.go +++ b/bulk.go @@ -65,10 +65,15 @@ const ( var appFs = afero.NewOsFs() // afero.Fs type is a wrapper around os functions, allowing us to mock it in tests -func updateJobState(job bulkJob, state string, auth authentication) error { +func updateJobState(job bulkJob, state string, auth *authentication) error { job.State = state body, _ := json.Marshal(job) - _, err := doRequest(http.MethodPatch, "/jobs/ingest/"+job.Id, jsonType, auth, string(body)) + _, err := doRequest(auth, requestPayload{ + method: http.MethodPatch, + uri: "/jobs/ingest/" + job.Id, + content: jsonType, + body: string(body), + }) if err != nil { return err } @@ -76,8 +81,13 @@ func updateJobState(job bulkJob, state string, auth authentication) error { return nil } -func createBulkJob(auth authentication, jobType string, body []byte) (bulkJob, error) { - resp, err := doRequest(http.MethodPost, "/jobs/"+jobType, jsonType, auth, string(body)) +func createBulkJob(auth *authentication, jobType string, body []byte) (bulkJob, error) { + resp, err := doRequest(auth, requestPayload{ + method: http.MethodPost, + uri: "/jobs/" + jobType, + content: jsonType, + body: string(body), + }) if err != nil { return bulkJob{}, err } @@ -96,8 +106,13 @@ func createBulkJob(auth authentication, jobType string, body []byte) (bulkJob, e return *newJob, nil } -func uploadJobData(auth authentication, data string, bulkJob bulkJob) error { - _, uploadDataErr := doRequest("PUT", "/jobs/ingest/"+bulkJob.Id+"/batches", csvType, auth, data) +func uploadJobData(auth *authentication, data string, bulkJob bulkJob) error { + _, uploadDataErr := doRequest(auth, requestPayload{ + method: http.MethodPut, + uri: "/jobs/ingest/" + bulkJob.Id + "/batches", + content: csvType, + body: data, + }) if uploadDataErr != nil { if err := updateJobState(bulkJob, jobStateAborted, auth); err != nil { return err @@ -112,8 +127,12 @@ func uploadJobData(auth authentication, data string, bulkJob bulkJob) error { return nil } -func getJobResults(auth authentication, jobType string, bulkJobId string) (BulkJobResults, error) { - resp, err := doRequest(http.MethodGet, "/jobs/"+jobType+"/"+bulkJobId, jsonType, auth, "") +func getJobResults(auth *authentication, jobType string, bulkJobId string) (BulkJobResults, error) { + resp, err := doRequest(auth, requestPayload{ + method: http.MethodGet, + uri: "/jobs/" + jobType + "/" + bulkJobId, + content: jsonType, + }) if err != nil { return BulkJobResults{}, err } @@ -132,7 +151,7 @@ func getJobResults(auth authentication, jobType string, bulkJobId string) (BulkJ return *bulkJobResults, nil } -func getJobRecordResults(auth authentication, bulkJobResults BulkJobResults) (BulkJobResults, error) { +func getJobRecordResults(auth *authentication, bulkJobResults BulkJobResults) (BulkJobResults, error) { successfulRecords, err := getBulkJobRecords(auth, bulkJobResults.Id, successfulResults) if err != nil { return bulkJobResults, fmt.Errorf("failed to get SuccessfulRecords: %w", err) @@ -146,8 +165,12 @@ func getJobRecordResults(auth authentication, bulkJobResults BulkJobResults) (Bu return bulkJobResults, err } -func getBulkJobRecords(auth authentication, bulkJobId string, resultType string) ([]map[string]any, error) { - resp, err := doRequest(http.MethodGet, "/jobs/ingest/"+bulkJobId+"/"+resultType, jsonType, auth, "") +func getBulkJobRecords(auth *authentication, bulkJobId string, resultType string) ([]map[string]any, error) { + resp, err := doRequest(auth, requestPayload{ + method: http.MethodGet, + uri: "/jobs/ingest/" + bulkJobId + "/" + resultType, + content: jsonType, + }) if err != nil { return nil, err } @@ -160,7 +183,7 @@ func getBulkJobRecords(auth authentication, bulkJobId string, resultType string) return results, nil } -func waitForJobResultsAsync(auth authentication, bulkJobId string, jobType string, interval time.Duration, c chan error) { +func waitForJobResultsAsync(auth *authentication, bulkJobId string, jobType string, interval time.Duration, c chan error) { err := wait.PollUntilContextTimeout(context.Background(), interval, time.Minute, false, func(context.Context) (bool, error) { bulkJob, reqErr := getJobResults(auth, jobType, bulkJobId) if reqErr != nil { @@ -171,7 +194,7 @@ func waitForJobResultsAsync(auth authentication, bulkJobId string, jobType strin c <- err } -func waitForJobResults(auth authentication, bulkJobId string, jobType string, interval time.Duration) error { +func waitForJobResults(auth *authentication, bulkJobId string, jobType string, interval time.Duration) error { err := wait.PollUntilContextTimeout(context.Background(), interval, time.Minute, false, func(context.Context) (bool, error) { bulkJob, reqErr := getJobResults(auth, jobType, bulkJobId) if reqErr != nil { @@ -195,12 +218,12 @@ func isBulkJobDone(bulkJob BulkJobResults) (bool, error) { return false, nil } -func getQueryJobResults(auth authentication, bulkJobId string, locator string) (bulkJobQueryResults, error) { +func getQueryJobResults(auth *authentication, bulkJobId string, locator string) (bulkJobQueryResults, error) { uri := "/jobs/query/" + bulkJobId + "/results" if locator != "" { uri = uri + "/?locator=" + locator } - resp, err := doRequest(http.MethodGet, uri, jsonType, auth, "") + resp, err := doRequest(auth, requestPayload{method: http.MethodGet, uri: uri, content: jsonType}) if err != nil { return bulkJobQueryResults{}, err } @@ -225,7 +248,7 @@ func getQueryJobResults(auth authentication, bulkJobId string, locator string) ( return queryResults, nil } -func collectQueryResults(auth authentication, bulkJobId string) ([][]string, error) { +func collectQueryResults(auth *authentication, bulkJobId string) ([][]string, error) { queryResults, resultsErr := getQueryJobResults(auth, bulkJobId, "") if resultsErr != nil { return nil, resultsErr @@ -336,7 +359,7 @@ func writeCSVFile(filePath string, data [][]string) error { return nil } -func constructBulkJobRequest(auth authentication, sObjectName string, operation string, fieldName string) (bulkJob, error) { +func constructBulkJobRequest(auth *authentication, sObjectName string, operation string, fieldName string) (bulkJob, error) { jobReq := bulkJobCreationRequest{ Object: sObjectName, Operation: operation, @@ -356,7 +379,7 @@ func constructBulkJobRequest(auth authentication, sObjectName string, operation return job, nil } -func doBulkJob(auth authentication, sObjectName string, fieldName string, operation string, records any, batchSize int, waitForResults bool) ([]string, error) { +func doBulkJob(auth *authentication, sObjectName string, fieldName string, operation string, records any, batchSize int, waitForResults bool) ([]string, error) { recordMap, err := convertToSliceOfMaps(records) if err != nil { return []string{}, err @@ -402,7 +425,7 @@ func doBulkJob(auth authentication, sObjectName string, fieldName string, operat return jobIds, jobErrors } -func doBulkJobWithFile(auth authentication, sObjectName string, fieldName string, operation string, filePath string, batchSize int, waitForResults bool) ([]string, error) { +func doBulkJobWithFile(auth *authentication, sObjectName string, fieldName string, operation string, filePath string, batchSize int, waitForResults bool) ([]string, error) { var jobErrors error var jobIds []string @@ -461,7 +484,7 @@ func doBulkJobWithFile(auth authentication, sObjectName string, fieldName string return jobIds, jobErrors } -func doQueryBulk(auth authentication, filePath string, query string) error { +func doQueryBulk(auth *authentication, filePath string, query string) error { queryJobReq := bulkQueryJobCreationRequest{ Operation: queryJobType, Query: query, diff --git a/bulk_test.go b/bulk_test.go index 347fafa..953c560 100644 --- a/bulk_test.go +++ b/bulk_test.go @@ -47,7 +47,7 @@ func Test_createBulkJob(t *testing.T) { queryBody, _ := json.Marshal(queryJobReq) type args struct { - auth authentication + auth *authentication jobType string body []byte } @@ -60,7 +60,7 @@ func Test_createBulkJob(t *testing.T) { { name: "create_bulk_ingest_job", args: args{ - auth: sfAuth, + auth: &sfAuth, jobType: ingestJobType, body: ingestBody, }, @@ -70,7 +70,7 @@ func Test_createBulkJob(t *testing.T) { { name: "create_bulk_query_job", args: args{ - auth: sfAuth, + auth: &sfAuth, jobType: queryJobType, body: queryBody, }, @@ -80,7 +80,7 @@ func Test_createBulkJob(t *testing.T) { { name: "bad_response", args: args{ - auth: badRespSfAuth, + auth: &badRespSfAuth, jobType: queryJobType, body: queryBody, }, @@ -118,7 +118,7 @@ func Test_getJobResults(t *testing.T) { defer badRespServer.Close() type args struct { - auth authentication + auth *authentication jobType string bulkJobId string } @@ -131,7 +131,7 @@ func Test_getJobResults(t *testing.T) { { name: "get_job_results", args: args{ - auth: sfAuth, + auth: &sfAuth, jobType: ingestJobType, bulkJobId: "1234", }, @@ -141,7 +141,7 @@ func Test_getJobResults(t *testing.T) { { name: "bad_request", args: args{ - auth: badReqSfAuth, + auth: &badReqSfAuth, jobType: ingestJobType, bulkJobId: "1234", }, @@ -150,7 +150,7 @@ func Test_getJobResults(t *testing.T) { { name: "bad_response", args: args{ - auth: badRespSfAuth, + auth: &badRespSfAuth, jobType: ingestJobType, bulkJobId: "1234", }, @@ -267,7 +267,7 @@ func Test_getQueryJobResults(t *testing.T) { defer badServer.Close() type args struct { - auth authentication + auth *authentication bulkJobId string locator string } @@ -280,7 +280,7 @@ func Test_getQueryJobResults(t *testing.T) { { name: "get_single_query_job_result", args: args{ - auth: sfAuth, + auth: &sfAuth, bulkJobId: "1234", locator: "", }, @@ -294,7 +294,7 @@ func Test_getQueryJobResults(t *testing.T) { { name: "bad_request", args: args{ - auth: badSfAuth, + auth: &badSfAuth, bulkJobId: "1234", locator: "", }, @@ -395,7 +395,7 @@ func Test_constructBulkJobRequest(t *testing.T) { defer badReqServer.Close() type args struct { - auth authentication + auth *authentication sObjectName string operation string fieldName string @@ -409,7 +409,7 @@ func Test_constructBulkJobRequest(t *testing.T) { { name: "construct_bulk_job_success", args: args{ - auth: sfAuth, + auth: &sfAuth, sObjectName: "Account", operation: insertOperation, fieldName: "", @@ -420,7 +420,7 @@ func Test_constructBulkJobRequest(t *testing.T) { { name: "construct_bulk_job_fail", args: args{ - auth: badJobSfAuth, + auth: &badJobSfAuth, sObjectName: "Account", operation: insertOperation, fieldName: "", @@ -431,7 +431,7 @@ func Test_constructBulkJobRequest(t *testing.T) { { name: "bad_request", args: args{ - auth: badReqSfAuth, + auth: &badReqSfAuth, sObjectName: "Account", operation: insertOperation, fieldName: "", @@ -442,7 +442,7 @@ func Test_constructBulkJobRequest(t *testing.T) { { name: "bad_response", args: args{ - auth: authentication{}, + auth: &authentication{}, sObjectName: "Account", operation: insertOperation, fieldName: "", @@ -524,7 +524,7 @@ func Test_doBulkJob(t *testing.T) { } type args struct { - auth authentication + auth *authentication sObjectName string fieldName string operation string @@ -541,7 +541,7 @@ func Test_doBulkJob(t *testing.T) { { name: "bulk_insert_batch_size_200", args: args{ - auth: sfAuth, + auth: &sfAuth, sObjectName: "Account", fieldName: "", operation: insertOperation, @@ -562,7 +562,7 @@ func Test_doBulkJob(t *testing.T) { { name: "bulk_upsert_batch_size_1", args: args{ - auth: sfAuth, + auth: &sfAuth, sObjectName: "Account", fieldName: "externalId", operation: upsertOperation, @@ -585,7 +585,7 @@ func Test_doBulkJob(t *testing.T) { { name: "bad_request", args: args{ - auth: badReqSfAuth, + auth: &badReqSfAuth, sObjectName: "Account", fieldName: "externalId", operation: upsertOperation, @@ -603,7 +603,7 @@ func Test_doBulkJob(t *testing.T) { { name: "bulk_insert_wait_for_results", args: args{ - auth: waitingSfAuth, + auth: &waitingSfAuth, sObjectName: "Account", fieldName: "", operation: insertOperation, @@ -621,7 +621,7 @@ func Test_doBulkJob(t *testing.T) { { name: "bad_request_upload_fail", args: args{ - auth: uploadFailSfAuth, + auth: &uploadFailSfAuth, sObjectName: "Account", fieldName: "", operation: insertOperation, @@ -639,7 +639,7 @@ func Test_doBulkJob(t *testing.T) { { name: "bad_data", args: args{ - auth: sfAuth, + auth: &sfAuth, sObjectName: "Account", fieldName: "", operation: insertOperation, @@ -677,7 +677,7 @@ func Test_waitForJobResultsAsync(t *testing.T) { defer badServer.Close() type args struct { - auth authentication + auth *authentication bulkJobId string jobType string interval time.Duration @@ -691,7 +691,7 @@ func Test_waitForJobResultsAsync(t *testing.T) { { name: "wait_for_ingest_result", args: args{ - auth: sfAuth, + auth: &sfAuth, bulkJobId: "1234", jobType: ingestJobType, interval: time.Nanosecond, @@ -702,7 +702,7 @@ func Test_waitForJobResultsAsync(t *testing.T) { { name: "wait_for_query_result", args: args{ - auth: sfAuth, + auth: &sfAuth, bulkJobId: "1234", jobType: queryJobType, interval: time.Nanosecond, @@ -713,7 +713,7 @@ func Test_waitForJobResultsAsync(t *testing.T) { { name: "bad_request", args: args{ - auth: badSfAuth, + auth: &badSfAuth, bulkJobId: "", jobType: queryJobType, interval: time.Nanosecond, @@ -745,7 +745,7 @@ func Test_waitForJobResults(t *testing.T) { defer badServer.Close() type args struct { - auth authentication + auth *authentication bulkJobId string jobType string interval time.Duration @@ -759,7 +759,7 @@ func Test_waitForJobResults(t *testing.T) { { name: "wait_for_ingest_result", args: args{ - auth: sfAuth, + auth: &sfAuth, bulkJobId: "1234", jobType: ingestJobType, interval: time.Nanosecond, @@ -769,7 +769,7 @@ func Test_waitForJobResults(t *testing.T) { { name: "wait_for_query_result", args: args{ - auth: sfAuth, + auth: &sfAuth, bulkJobId: "1234", jobType: queryJobType, interval: time.Nanosecond, @@ -779,7 +779,7 @@ func Test_waitForJobResults(t *testing.T) { { name: "bad_request", args: args{ - auth: badSfAuth, + auth: &badSfAuth, bulkJobId: "", jobType: queryJobType, interval: time.Nanosecond, @@ -820,7 +820,7 @@ func Test_collectQueryResults(t *testing.T) { defer badServer.Close() type args struct { - auth authentication + auth *authentication bulkJobId string } tests := []struct { @@ -832,7 +832,7 @@ func Test_collectQueryResults(t *testing.T) { { name: "query_with_locator", args: args{ - auth: sfAuth, + auth: &sfAuth, bulkJobId: "123", }, want: [][]string{{"col"}, {"row"}, {"row"}}, @@ -841,7 +841,7 @@ func Test_collectQueryResults(t *testing.T) { { name: "bad_request", args: args{ - auth: badSfAuth, + auth: &badSfAuth, bulkJobId: "123", }, wantErr: true, @@ -891,7 +891,7 @@ func Test_uploadJobData(t *testing.T) { defer badRequestServer.Close() type args struct { - auth authentication + auth *authentication data string bulkJob bulkJob } @@ -903,7 +903,7 @@ func Test_uploadJobData(t *testing.T) { { name: "update_job_state_success", args: args{ - auth: sfAuth, + auth: &sfAuth, data: "data", bulkJob: bulkJob{}, }, @@ -912,7 +912,7 @@ func Test_uploadJobData(t *testing.T) { { name: "batch_req_fail", args: args{ - auth: badBatchReqAuth, + auth: &badBatchReqAuth, data: "data", bulkJob: bulkJob{}, }, @@ -921,7 +921,7 @@ func Test_uploadJobData(t *testing.T) { { name: "update_job_state_fail_aborted", args: args{ - auth: badBatchAndUpdateJobStateReqAuth, + auth: &badBatchAndUpdateJobStateReqAuth, data: "data", bulkJob: bulkJob{}, }, @@ -930,7 +930,7 @@ func Test_uploadJobData(t *testing.T) { { name: "update_job_state_fail_complete", args: args{ - auth: badRequestSfAuth, + auth: &badRequestSfAuth, data: "data", bulkJob: bulkJob{}, }, @@ -1042,7 +1042,7 @@ func Test_updateJobState(t *testing.T) { type args struct { job bulkJob state string - auth authentication + auth *authentication } tests := []struct { name string @@ -1054,7 +1054,7 @@ func Test_updateJobState(t *testing.T) { args: args{ job: bulkJob{}, state: "", - auth: badSfAuth, + auth: &badSfAuth, }, wantErr: true, }, @@ -1131,7 +1131,7 @@ func Test_doBulkJobWithFile(t *testing.T) { } type args struct { - auth authentication + auth *authentication sObjectName string fieldName string operation string @@ -1148,7 +1148,7 @@ func Test_doBulkJobWithFile(t *testing.T) { { name: "bulk_insert_batch_size_200", args: args{ - auth: sfAuth, + auth: &sfAuth, sObjectName: "Account", fieldName: "", operation: insertOperation, @@ -1162,7 +1162,7 @@ func Test_doBulkJobWithFile(t *testing.T) { { name: "bulk_insert_batch_size_1", args: args{ - auth: sfAuth, + auth: &sfAuth, sObjectName: "Account", fieldName: "", operation: insertOperation, @@ -1176,7 +1176,7 @@ func Test_doBulkJobWithFile(t *testing.T) { { name: "bad_request", args: args{ - auth: badReqSfAuth, + auth: &badReqSfAuth, sObjectName: "Account", fieldName: "externalId", operation: upsertOperation, @@ -1189,7 +1189,7 @@ func Test_doBulkJobWithFile(t *testing.T) { { name: "bulk_insert_wait_for_results", args: args{ - auth: waitingSfAuth, + auth: &waitingSfAuth, sObjectName: "Account", fieldName: "", operation: insertOperation, @@ -1203,7 +1203,7 @@ func Test_doBulkJobWithFile(t *testing.T) { { name: "bad_request_upload_fail", args: args{ - auth: uploadFailSfAuth, + auth: &uploadFailSfAuth, sObjectName: "Account", fieldName: "", operation: insertOperation, @@ -1272,7 +1272,7 @@ func Test_doQueryBulk(t *testing.T) { } type args struct { - auth authentication + auth *authentication filePath string query string } @@ -1284,7 +1284,7 @@ func Test_doQueryBulk(t *testing.T) { { name: "bad_job_creation", args: args{ - auth: badJobCreationSfAuth, + auth: &badJobCreationSfAuth, filePath: "data/data.csv", query: "SELECT Id FROM Account", }, @@ -1293,7 +1293,7 @@ func Test_doQueryBulk(t *testing.T) { { name: "get_results_fail", args: args{ - auth: badResultsSfAuth, + auth: &badResultsSfAuth, filePath: "data/data.csv", query: "SELECT Id FROM Account", }, @@ -1341,7 +1341,7 @@ func Test_getJobRecordResults(t *testing.T) { defer successThenFailServer.Close() type args struct { - auth authentication + auth *authentication bulkJobResults BulkJobResults } tests := []struct { @@ -1353,7 +1353,7 @@ func Test_getJobRecordResults(t *testing.T) { { name: "successful_get_job_record_results", args: args{ - auth: sfAuth, + auth: &sfAuth, bulkJobResults: BulkJobResults{Id: "1234"}, }, want: BulkJobResults{ @@ -1370,7 +1370,7 @@ func Test_getJobRecordResults(t *testing.T) { { name: "failed_to_get_successful_records", args: args{ - auth: badRequestAuth, + auth: &badRequestAuth, bulkJobResults: BulkJobResults{Id: "1234"}, }, want: BulkJobResults{Id: "1234"}, @@ -1379,7 +1379,7 @@ func Test_getJobRecordResults(t *testing.T) { { name: "failed_to_get_failed_records", args: args{ - auth: successThenFailAuth, + auth: &successThenFailAuth, bulkJobResults: BulkJobResults{Id: "1234"}, }, want: BulkJobResults{ @@ -1433,7 +1433,7 @@ func Test_getBulkJobRecords(t *testing.T) { defer badDataServer.Close() type args struct { - auth authentication + auth *authentication bulkJobId string resultType string } @@ -1446,7 +1446,7 @@ func Test_getBulkJobRecords(t *testing.T) { { name: "successful_get_failed_job_records", args: args{ - auth: sfAuth, + auth: &sfAuth, bulkJobId: "1234", resultType: failedResults, }, @@ -1458,7 +1458,7 @@ func Test_getBulkJobRecords(t *testing.T) { { name: "failed_bad_request", args: args{ - auth: badReqAuth, + auth: &badReqAuth, bulkJobId: "1234", resultType: failedResults, }, @@ -1468,7 +1468,7 @@ func Test_getBulkJobRecords(t *testing.T) { { name: "failed_conversion", args: args{ - auth: badDataAuth, + auth: &badDataAuth, bulkJobId: "1234", resultType: failedResults, }, diff --git a/composite.go b/composite.go index 326c89d..dc6527a 100644 --- a/composite.go +++ b/composite.go @@ -33,12 +33,17 @@ type compositeSubRequestResult struct { ReferenceId string `json:"referenceId"` } -func doCompositeRequest(auth authentication, compReq compositeRequest) (SalesforceResults, error) { +func doCompositeRequest(auth *authentication, compReq compositeRequest) (SalesforceResults, error) { body, jsonErr := json.Marshal(compReq) if jsonErr != nil { return SalesforceResults{}, jsonErr } - resp, httpErr := doRequest(http.MethodPost, "/composite", jsonType, auth, string(body)) + resp, httpErr := doRequest(auth, requestPayload{ + method: http.MethodPost, + uri: "/composite", + content: jsonType, + body: string(body), + }) if httpErr != nil { return SalesforceResults{}, httpErr } @@ -122,7 +127,7 @@ func processCompositeResponse(resp http.Response, allOrNone bool) (SalesforceRes return results, nil } -func doInsertComposite(auth authentication, sObjectName string, records any, allOrNone bool, batchSize int) (SalesforceResults, error) { +func doInsertComposite(auth *authentication, sObjectName string, records any, allOrNone bool, batchSize int) (SalesforceResults, error) { recordMap, err := convertToSliceOfMaps(records) if err != nil { return SalesforceResults{}, err @@ -146,7 +151,7 @@ func doInsertComposite(auth authentication, sObjectName string, records any, all return results, nil } -func doUpdateComposite(auth authentication, sObjectName string, records any, allOrNone bool, batchSize int) (SalesforceResults, error) { +func doUpdateComposite(auth *authentication, sObjectName string, records any, allOrNone bool, batchSize int) (SalesforceResults, error) { recordMap, err := convertToSliceOfMaps(records) if err != nil { return SalesforceResults{}, err @@ -173,7 +178,7 @@ func doUpdateComposite(auth authentication, sObjectName string, records any, all return results, nil } -func doUpsertComposite(auth authentication, sObjectName string, fieldName string, records any, allOrNone bool, batchSize int) (SalesforceResults, error) { +func doUpsertComposite(auth *authentication, sObjectName string, fieldName string, records any, allOrNone bool, batchSize int) (SalesforceResults, error) { recordMap, err := convertToSliceOfMaps(records) if err != nil { return SalesforceResults{}, err @@ -200,7 +205,7 @@ func doUpsertComposite(auth authentication, sObjectName string, fieldName string return results, nil } -func doDeleteComposite(auth authentication, sObjectName string, records any, allOrNone bool, batchSize int) (SalesforceResults, error) { +func doDeleteComposite(auth *authentication, sObjectName string, records any, allOrNone bool, batchSize int) (SalesforceResults, error) { recordMap, err := convertToSliceOfMaps(records) if err != nil { return SalesforceResults{}, err diff --git a/composite_test.go b/composite_test.go index 69e6ef3..979cf5a 100644 --- a/composite_test.go +++ b/composite_test.go @@ -306,7 +306,7 @@ func Test_doCompositeRequest(t *testing.T) { } type args struct { - auth authentication + auth *authentication compReq compositeRequest } tests := []struct { @@ -318,7 +318,7 @@ func Test_doCompositeRequest(t *testing.T) { { name: "successful_request", args: args{ - auth: sfAuth, + auth: &sfAuth, compReq: compReq, }, want: SalesforceResults{ @@ -330,7 +330,7 @@ func Test_doCompositeRequest(t *testing.T) { { name: "bad_request", args: args{ - auth: badReqSfAuth, + auth: &badReqSfAuth, compReq: compReq, }, want: SalesforceResults{}, @@ -339,7 +339,7 @@ func Test_doCompositeRequest(t *testing.T) { { name: "salesforce_errors", args: args{ - auth: sfErrorSfAuth, + auth: &sfErrorSfAuth, compReq: compReq, }, want: sfResultsFail, @@ -378,7 +378,7 @@ func Test_doInsertComposite(t *testing.T) { defer server.Close() type args struct { - auth authentication + auth *authentication sObjectName string records any allOrNone bool @@ -393,7 +393,7 @@ func Test_doInsertComposite(t *testing.T) { { name: "successful_insert_composite", args: args{ - auth: sfAuth, + auth: &sfAuth, sObjectName: "Account", records: []account{ { @@ -415,7 +415,7 @@ func Test_doInsertComposite(t *testing.T) { { name: "bad_data", args: args{ - auth: sfAuth, + auth: &sfAuth, sObjectName: "Account", records: "1", batchSize: 200, @@ -458,7 +458,7 @@ func Test_doUpdateComposite(t *testing.T) { defer server.Close() type args struct { - auth authentication + auth *authentication sObjectName string records any allOrNone bool @@ -473,7 +473,7 @@ func Test_doUpdateComposite(t *testing.T) { { name: "successful_update_composite", args: args{ - auth: sfAuth, + auth: &sfAuth, sObjectName: "Account", records: []account{ { @@ -497,7 +497,7 @@ func Test_doUpdateComposite(t *testing.T) { { name: "bad_data", args: args{ - auth: sfAuth, + auth: &sfAuth, sObjectName: "Account", records: "1", batchSize: 200, @@ -509,7 +509,7 @@ func Test_doUpdateComposite(t *testing.T) { { name: "fail_no_id", args: args{ - auth: sfAuth, + auth: &sfAuth, sObjectName: "Account", records: []account{ { @@ -556,7 +556,7 @@ func Test_doUpsertComposite(t *testing.T) { defer server.Close() type args struct { - auth authentication + auth *authentication sObjectName string fieldName string records any @@ -572,7 +572,7 @@ func Test_doUpsertComposite(t *testing.T) { { name: "successful_upsert_composite", args: args{ - auth: sfAuth, + auth: &sfAuth, sObjectName: "Account", fieldName: "ExternalId__c", records: []account{ @@ -597,7 +597,7 @@ func Test_doUpsertComposite(t *testing.T) { { name: "bad_data", args: args{ - auth: sfAuth, + auth: &sfAuth, sObjectName: "Account", fieldName: "ExternalId__c", records: "1", @@ -610,7 +610,7 @@ func Test_doUpsertComposite(t *testing.T) { { name: "fail_no_external_id", args: args{ - auth: sfAuth, + auth: &sfAuth, sObjectName: "Account", fieldName: "ExternalId__c", records: []account{ @@ -657,7 +657,7 @@ func Test_doDeleteComposite(t *testing.T) { defer server.Close() type args struct { - auth authentication + auth *authentication sObjectName string records any allOrNone bool @@ -672,7 +672,7 @@ func Test_doDeleteComposite(t *testing.T) { { name: "successful_delete_composite_single_batch", args: args{ - auth: sfAuth, + auth: &sfAuth, sObjectName: "Account", records: []account{ { @@ -694,7 +694,7 @@ func Test_doDeleteComposite(t *testing.T) { { name: "successful_delete_composite_multi_batch", args: args{ - auth: sfAuth, + auth: &sfAuth, sObjectName: "Account", records: []account{ { @@ -716,7 +716,7 @@ func Test_doDeleteComposite(t *testing.T) { { name: "bad_data", args: args{ - auth: sfAuth, + auth: &sfAuth, sObjectName: "Account", records: "1", batchSize: 200, @@ -728,7 +728,7 @@ func Test_doDeleteComposite(t *testing.T) { { name: "fail_no_id", args: args{ - auth: sfAuth, + auth: &sfAuth, sObjectName: "Account", records: []account{{}}, batchSize: 200, diff --git a/dml.go b/dml.go index 62e86c7..d88927d 100644 --- a/dml.go +++ b/dml.go @@ -55,7 +55,7 @@ func processSalesforceResponse(resp http.Response) ([]SalesforceResult, error) { return results, nil } -func doBatchedRequestsForCollection(auth authentication, method string, url string, batchSize int, recordMap []map[string]any) (SalesforceResults, error) { +func doBatchedRequestsForCollection(auth *authentication, method string, url string, batchSize int, recordMap []map[string]any) (SalesforceResults, error) { var results = []SalesforceResult{} for len(recordMap) > 0 { @@ -77,7 +77,12 @@ func doBatchedRequestsForCollection(auth authentication, method string, url stri return SalesforceResults{Results: results}, err } - resp, err := doRequest(method, url, jsonType, auth, string(body)) + resp, err := doRequest(auth, requestPayload{ + method: method, + uri: url, + content: jsonType, + body: string(body), + }) if err != nil { return SalesforceResults{Results: results}, err } @@ -105,7 +110,7 @@ func decodeResponseBody(response *http.Response) (value SalesforceResult, err er return value, err } -func doInsertOne(auth authentication, sObjectName string, record any) (SalesforceResult, error) { +func doInsertOne(auth *authentication, sObjectName string, record any) (SalesforceResult, error) { recordMap, err := convertToMap(record) if err != nil { return SalesforceResult{}, err @@ -118,7 +123,12 @@ func doInsertOne(auth authentication, sObjectName string, record any) (Salesforc return SalesforceResult{}, err } - resp, err := doRequest(http.MethodPost, "/sobjects/"+sObjectName, jsonType, auth, string(body)) + resp, err := doRequest(auth, requestPayload{ + method: http.MethodPost, + uri: "/sobjects/" + sObjectName, + content: jsonType, + body: string(body), + }) if err != nil { return SalesforceResult{}, err } @@ -132,7 +142,7 @@ func doInsertOne(auth authentication, sObjectName string, record any) (Salesforc return data, nil } -func doUpdateOne(auth authentication, sObjectName string, record any) error { +func doUpdateOne(auth *authentication, sObjectName string, record any) error { recordMap, err := convertToMap(record) if err != nil { return err @@ -151,7 +161,12 @@ func doUpdateOne(auth authentication, sObjectName string, record any) error { return err } - _, err = doRequest(http.MethodPatch, "/sobjects/"+sObjectName+"/"+recordId, jsonType, auth, string(body)) + _, err = doRequest(auth, requestPayload{ + method: http.MethodPatch, + uri: "/sobjects/" + sObjectName + "/" + recordId, + content: jsonType, + body: string(body), + }) if err != nil { return err } @@ -159,7 +174,7 @@ func doUpdateOne(auth authentication, sObjectName string, record any) error { return nil } -func doUpsertOne(auth authentication, sObjectName string, fieldName string, record any) (SalesforceResult, error) { +func doUpsertOne(auth *authentication, sObjectName string, fieldName string, record any) (SalesforceResult, error) { recordMap, err := convertToMap(record) if err != nil { return SalesforceResult{}, err @@ -179,7 +194,12 @@ func doUpsertOne(auth authentication, sObjectName string, fieldName string, reco return SalesforceResult{}, err } - resp, err := doRequest(http.MethodPatch, "/sobjects/"+sObjectName+"/"+fieldName+"/"+externalIdValue, jsonType, auth, string(body)) + resp, err := doRequest(auth, requestPayload{ + method: http.MethodPatch, + uri: "/sobjects/" + sObjectName + "/" + fieldName + "/" + externalIdValue, + content: jsonType, + body: string(body), + }) if err != nil { return SalesforceResult{}, err } @@ -193,7 +213,7 @@ func doUpsertOne(auth authentication, sObjectName string, fieldName string, reco return data, nil } -func doDeleteOne(auth authentication, sObjectName string, record any) error { +func doDeleteOne(auth *authentication, sObjectName string, record any) error { recordMap, err := convertToMap(record) if err != nil { return err @@ -204,7 +224,11 @@ func doDeleteOne(auth authentication, sObjectName string, record any) error { return errors.New("salesforce id not found in object data") } - _, err = doRequest(http.MethodDelete, "/sobjects/"+sObjectName+"/"+recordId, jsonType, auth, "") + _, err = doRequest(auth, requestPayload{ + method: http.MethodDelete, + uri: "/sobjects/" + sObjectName + "/" + recordId, + content: jsonType, + }) if err != nil { return err } @@ -212,7 +236,7 @@ func doDeleteOne(auth authentication, sObjectName string, record any) error { return nil } -func doInsertCollection(auth authentication, sObjectName string, records any, batchSize int) (SalesforceResults, error) { +func doInsertCollection(auth *authentication, sObjectName string, records any, batchSize int) (SalesforceResults, error) { recordMap, err := convertToSliceOfMaps(records) if err != nil { return SalesforceResults{}, err @@ -225,7 +249,7 @@ func doInsertCollection(auth authentication, sObjectName string, records any, ba return doBatchedRequestsForCollection(auth, http.MethodPost, "/composite/sobjects/", batchSize, recordMap) } -func doUpdateCollection(auth authentication, sObjectName string, records any, batchSize int) (SalesforceResults, error) { +func doUpdateCollection(auth *authentication, sObjectName string, records any, batchSize int) (SalesforceResults, error) { recordMap, err := convertToSliceOfMaps(records) if err != nil { return SalesforceResults{}, err @@ -241,7 +265,7 @@ func doUpdateCollection(auth authentication, sObjectName string, records any, ba return doBatchedRequestsForCollection(auth, http.MethodPatch, "/composite/sobjects/", batchSize, recordMap) } -func doUpsertCollection(auth authentication, sObjectName string, fieldName string, records any, batchSize int) (SalesforceResults, error) { +func doUpsertCollection(auth *authentication, sObjectName string, fieldName string, records any, batchSize int) (SalesforceResults, error) { recordMap, err := convertToSliceOfMaps(records) if err != nil { return SalesforceResults{}, err @@ -259,7 +283,7 @@ func doUpsertCollection(auth authentication, sObjectName string, fieldName strin } -func doDeleteCollection(auth authentication, sObjectName string, records any, batchSize int) (SalesforceResults, error) { +func doDeleteCollection(auth *authentication, sObjectName string, records any, batchSize int) (SalesforceResults, error) { recordMap, err := convertToSliceOfMaps(records) if err != nil { return SalesforceResults{}, err @@ -295,7 +319,11 @@ func doDeleteCollection(auth authentication, sObjectName string, records any, ba var results = []SalesforceResult{} for i := range batchedIds { - resp, err := doRequest(http.MethodDelete, "/composite/sobjects/?ids="+batchedIds[i]+"&allOrNone=false", jsonType, auth, "") + resp, err := doRequest(auth, requestPayload{ + method: http.MethodDelete, + uri: "/composite/sobjects/?ids=" + batchedIds[i] + "&allOrNone=false", + content: jsonType, + }) if err != nil { return SalesforceResults{Results: results}, err } diff --git a/dml_test.go b/dml_test.go index f46e61e..c6a73c5 100644 --- a/dml_test.go +++ b/dml_test.go @@ -200,7 +200,7 @@ func Test_doBatchedRequestsForCollection(t *testing.T) { defer sfErrorServer.Close() type args struct { - auth authentication + auth *authentication method string url string batchSize int @@ -215,7 +215,7 @@ func Test_doBatchedRequestsForCollection(t *testing.T) { { name: "single_record", args: args{ - auth: sfAuth, + auth: &sfAuth, method: http.MethodPost, url: "", batchSize: 200, @@ -234,7 +234,7 @@ func Test_doBatchedRequestsForCollection(t *testing.T) { { name: "multiple_batches", args: args{ - auth: sfAuth, + auth: &sfAuth, method: http.MethodPost, url: "", batchSize: 1, @@ -256,7 +256,7 @@ func Test_doBatchedRequestsForCollection(t *testing.T) { { name: "bad_request", args: args{ - auth: badReqSfAuth, + auth: &badReqSfAuth, method: http.MethodPost, url: "", batchSize: 1, @@ -272,7 +272,7 @@ func Test_doBatchedRequestsForCollection(t *testing.T) { { name: "salesforce_error", args: args{ - auth: sfErrorSfAuth, + auth: &sfErrorSfAuth, method: http.MethodPost, url: "", batchSize: 1, @@ -320,7 +320,7 @@ func Test_doInsertOne(t *testing.T) { defer badReqServer.Close() type args struct { - auth authentication + auth *authentication sObjectName string record any } @@ -333,7 +333,7 @@ func Test_doInsertOne(t *testing.T) { { name: "successful_insert", args: args{ - auth: sfAuth, + auth: &sfAuth, sObjectName: "Account", record: account{ Name: "test account", @@ -345,7 +345,7 @@ func Test_doInsertOne(t *testing.T) { { name: "bad_request", args: args{ - auth: badReqSfAuth, + auth: &badReqSfAuth, sObjectName: "Account", record: account{ Name: "test account", @@ -357,7 +357,7 @@ func Test_doInsertOne(t *testing.T) { { name: "bad_data", args: args{ - auth: sfAuth, + auth: &sfAuth, sObjectName: "Account", record: "1", }, @@ -391,7 +391,7 @@ func Test_doUpdateOne(t *testing.T) { defer badReqServer.Close() type args struct { - auth authentication + auth *authentication sObjectName string record any } @@ -403,7 +403,7 @@ func Test_doUpdateOne(t *testing.T) { { name: "successful_update", args: args{ - auth: sfAuth, + auth: &sfAuth, sObjectName: "Account", record: account{ Id: "1234", @@ -415,7 +415,7 @@ func Test_doUpdateOne(t *testing.T) { { name: "bad_request", args: args{ - auth: badReqSfAuth, + auth: &badReqSfAuth, sObjectName: "Account", record: account{ Id: "1234", @@ -427,7 +427,7 @@ func Test_doUpdateOne(t *testing.T) { { name: "bad_data", args: args{ - auth: sfAuth, + auth: &sfAuth, sObjectName: "Account", record: "1", }, @@ -462,7 +462,7 @@ func Test_doUpsertOne(t *testing.T) { defer badReqServer.Close() type args struct { - auth authentication + auth *authentication sObjectName string fieldName string record any @@ -476,7 +476,7 @@ func Test_doUpsertOne(t *testing.T) { { name: "successful_upsert", args: args{ - auth: sfAuth, + auth: &sfAuth, sObjectName: "Account", fieldName: "ExternalId__c", record: account{ @@ -490,7 +490,7 @@ func Test_doUpsertOne(t *testing.T) { { name: "bad_request", args: args{ - auth: badReqSfAuth, + auth: &badReqSfAuth, sObjectName: "Account", fieldName: "ExternalId__c", record: account{ @@ -504,7 +504,7 @@ func Test_doUpsertOne(t *testing.T) { { name: "bad_data", args: args{ - auth: sfAuth, + auth: &sfAuth, sObjectName: "Account", fieldName: "ExternalId__c", record: "1", @@ -538,7 +538,7 @@ func Test_doDeleteOne(t *testing.T) { defer badReqServer.Close() type args struct { - auth authentication + auth *authentication sObjectName string record any } @@ -550,7 +550,7 @@ func Test_doDeleteOne(t *testing.T) { { name: "successful_delete", args: args{ - auth: sfAuth, + auth: &sfAuth, sObjectName: "Account", record: account{ Id: "1234", @@ -561,7 +561,7 @@ func Test_doDeleteOne(t *testing.T) { { name: "bad_request", args: args{ - auth: badReqSfAuth, + auth: &badReqSfAuth, sObjectName: "Account", record: account{ Id: "1234", @@ -572,7 +572,7 @@ func Test_doDeleteOne(t *testing.T) { { name: "bad_data", args: args{ - auth: sfAuth, + auth: &sfAuth, sObjectName: "Account", record: "1", }, @@ -606,7 +606,7 @@ func Test_doInsertCollection(t *testing.T) { defer server.Close() type args struct { - auth authentication + auth *authentication sObjectName string records any batchSize int @@ -620,7 +620,7 @@ func Test_doInsertCollection(t *testing.T) { { name: "successful_insert_collection", args: args{ - auth: sfAuth, + auth: &sfAuth, sObjectName: "Account", records: []account{ { @@ -638,7 +638,7 @@ func Test_doInsertCollection(t *testing.T) { { name: "bad_data", args: args{ - auth: sfAuth, + auth: &sfAuth, sObjectName: "Account", records: "1", batchSize: 200, @@ -675,7 +675,7 @@ func Test_doUpdateCollection(t *testing.T) { defer server.Close() type args struct { - auth authentication + auth *authentication sObjectName string records any batchSize int @@ -689,7 +689,7 @@ func Test_doUpdateCollection(t *testing.T) { { name: "successful_update_collection", args: args{ - auth: sfAuth, + auth: &sfAuth, sObjectName: "Account", records: []account{ { @@ -709,7 +709,7 @@ func Test_doUpdateCollection(t *testing.T) { { name: "bad_data", args: args{ - auth: sfAuth, + auth: &sfAuth, sObjectName: "Account", records: "1", batchSize: 200, @@ -750,7 +750,7 @@ func Test_doUpsertCollection(t *testing.T) { defer server.Close() type args struct { - auth authentication + auth *authentication sObjectName string fieldName string records any @@ -765,7 +765,7 @@ func Test_doUpsertCollection(t *testing.T) { { name: "successful_upsert_collection", args: args{ - auth: sfAuth, + auth: &sfAuth, sObjectName: "Account", fieldName: "ExternalId__c", records: []account{ @@ -786,7 +786,7 @@ func Test_doUpsertCollection(t *testing.T) { { name: "bad_data", args: args{ - auth: sfAuth, + auth: &sfAuth, sObjectName: "Account", fieldName: "ExternalId__c", records: "1", @@ -862,7 +862,7 @@ func Test_doDeleteCollection(t *testing.T) { defer sfErrorServer.Close() type args struct { - auth authentication + auth *authentication sObjectName string records any batchSize int @@ -876,7 +876,7 @@ func Test_doDeleteCollection(t *testing.T) { { name: "successful_delete_collection_single_batch", args: args{ - auth: sfAuth, + auth: &sfAuth, sObjectName: "Account", records: []account{ { @@ -894,7 +894,7 @@ func Test_doDeleteCollection(t *testing.T) { { name: "successful_delete_collection_multi_batch", args: args{ - auth: sfAuth, + auth: &sfAuth, sObjectName: "Account", records: []account{ { @@ -912,7 +912,7 @@ func Test_doDeleteCollection(t *testing.T) { { name: "bad_data", args: args{ - auth: sfAuth, + auth: &sfAuth, sObjectName: "Account", records: "1", batchSize: 200, @@ -923,7 +923,7 @@ func Test_doDeleteCollection(t *testing.T) { { name: "bad_request", args: args{ - auth: badReqSfAuth, + auth: &badReqSfAuth, sObjectName: "Account", records: []account{ { @@ -938,7 +938,7 @@ func Test_doDeleteCollection(t *testing.T) { { name: "salesforce_errors", args: args{ - auth: sfErrorSfAuth, + auth: &sfErrorSfAuth, sObjectName: "Account", records: []account{ { diff --git a/query.go b/query.go index aef0574..63b0c4d 100644 --- a/query.go +++ b/query.go @@ -17,7 +17,7 @@ type queryResponse struct { Records []map[string]any `json:"records"` } -func performQuery(auth authentication, query string, sObject any) error { +func performQuery(auth *authentication, query string, sObject any) error { query = url.QueryEscape(query) queryResp := &queryResponse{ Done: false, @@ -25,7 +25,11 @@ func performQuery(auth authentication, query string, sObject any) error { } for !queryResp.Done { - resp, err := doRequest(http.MethodGet, queryResp.NextRecordsUrl, jsonType, auth, "") + resp, err := doRequest(auth, requestPayload{ + method: http.MethodGet, + uri: queryResp.NextRecordsUrl, + content: jsonType, + }) if err != nil { return err } diff --git a/query_test.go b/query_test.go index 8117b77..c341c98 100644 --- a/query_test.go +++ b/query_test.go @@ -61,7 +61,7 @@ func Test_performQuery(t *testing.T) { defer badRespServer.Close() type args struct { - auth authentication + auth *authentication query string sObject []account } @@ -74,7 +74,7 @@ func Test_performQuery(t *testing.T) { { name: "query_account", args: args{ - auth: sfAuth, + auth: &sfAuth, query: "SELECT Id, Name FROM Account", sObject: []account{}, }, @@ -93,7 +93,7 @@ func Test_performQuery(t *testing.T) { { name: "http_error", args: args{ - auth: badSfAuth, + auth: &badSfAuth, query: "SELECT Id, Name FROM Account", sObject: []account{}, }, @@ -103,7 +103,7 @@ func Test_performQuery(t *testing.T) { { name: "bad_response", args: args{ - auth: badRespSfAuth, + auth: &badRespSfAuth, query: "SELECT Id FROM Account", sObject: []account{}, }, diff --git a/salesforce.go b/salesforce.go index 4bc57c2..e6fe025 100644 --- a/salesforce.go +++ b/salesforce.go @@ -1,6 +1,7 @@ package salesforce import ( + "encoding/json" "errors" "io" "net/http" @@ -19,6 +20,7 @@ type SalesforceErrorMessage struct { Message string `json:"message"` StatusCode string `json:"statusCode"` Fields []string `json:"fields"` + ErrorCode string `json:"errorCode"` } type SalesforceResult struct { @@ -32,33 +34,42 @@ type SalesforceResults struct { HasSalesforceErrors bool } +type requestPayload struct { + method string + uri string + content string + body string + retry bool +} + const ( - apiVersion = "v60.0" - jsonType = "application/json" - csvType = "text/csv" - batchSizeMax = 200 - bulkBatchSizeMax = 10000 + apiVersion = "v60.0" + jsonType = "application/json" + csvType = "text/csv" + batchSizeMax = 200 + bulkBatchSizeMax = 10000 + invalidSessionIdError = "INVALID_SESSION_ID" ) -func doRequest(method string, uri string, content string, auth authentication, body string) (*http.Response, error) { +func doRequest(auth *authentication, payload requestPayload) (*http.Response, error) { var reader *strings.Reader var req *http.Request var err error - endpoint := auth.InstanceUrl + "/services/data/" + apiVersion + uri + endpoint := auth.InstanceUrl + "/services/data/" + apiVersion + payload.uri - if body != "" { - reader = strings.NewReader(body) - req, err = http.NewRequest(method, endpoint, reader) + if payload.body != "" { + reader = strings.NewReader(payload.body) + req, err = http.NewRequest(payload.method, endpoint, reader) } else { - req, err = http.NewRequest(method, endpoint, nil) + req, err = http.NewRequest(payload.method, endpoint, nil) } if err != nil { return nil, err } req.Header.Set("User-Agent", "go-salesforce") - req.Header.Set("Content-Type", content) - req.Header.Set("Accept", content) + req.Header.Set("Content-Type", payload.content) + req.Header.Set("Accept", payload.content) req.Header.Set("Authorization", "Bearer "+auth.AccessToken) resp, err := http.DefaultClient.Do(req) @@ -66,10 +77,10 @@ func doRequest(method string, uri string, content string, auth authentication, b return resp, err } if resp.StatusCode < 200 || resp.StatusCode > 299 { - return resp, processSalesforceError(*resp) + resp, err = processSalesforceError(*resp, auth, payload) } - return resp, nil + return resp, err } func validateOfTypeSlice(data any) error { @@ -161,16 +172,31 @@ func validateBulk(sf Salesforce, records any, batchSize int, isFile bool) error return nil } -func processSalesforceError(resp http.Response) error { - var errorMessage string +func processSalesforceError(resp http.Response, auth *authentication, payload requestPayload) (*http.Response, error) { responseData, err := io.ReadAll(resp.Body) if err != nil { - return err + return &resp, err + } + var sfErrors []SalesforceErrorMessage + err = json.Unmarshal(responseData, &sfErrors) + if err != nil { + return &resp, err + } + for _, sfError := range sfErrors { + if sfError.ErrorCode == invalidSessionIdError && !payload.retry { // only attempt to refresh the session once + err = refreshSession(auth) + if err != nil { + return &resp, err + } + newResp, err := doRequest(auth, requestPayload{payload.method, payload.uri, payload.content, payload.body, true}) + if err != nil { + return &resp, err + } + return newResp, nil + } } - errorMessage = string(resp.Status) + ": " + string(responseData) - - return errors.New(errorMessage) + return &resp, errors.New(string(responseData)) } func Init(creds Creds) (*Salesforce, error) { @@ -205,6 +231,7 @@ func Init(creds Creds) (*Salesforce, error) { } else if auth == nil || auth.AccessToken == "" { return nil, errors.New("unknown authentication error") } + auth.creds = creds return &Salesforce{auth: auth}, nil } @@ -214,7 +241,12 @@ func (sf *Salesforce) DoRequest(method string, uri string, body []byte) (*http.R return nil, authErr } - resp, err := doRequest(method, uri, jsonType, *sf.auth, string(body)) + resp, err := doRequest(sf.auth, requestPayload{ + method: method, + uri: uri, + content: jsonType, + body: string(body), + }) if err != nil { return nil, err } @@ -228,7 +260,7 @@ func (sf *Salesforce) Query(query string, sObject any) error { return authErr } - queryErr := performQuery(*sf.auth, query, sObject) + queryErr := performQuery(sf.auth, query, sObject) if queryErr != nil { return queryErr } @@ -246,7 +278,7 @@ func (sf *Salesforce) QueryStruct(soqlStruct any, sObject any) error { if err != nil { return err } - queryErr := performQuery(*sf.auth, soqlQuery, sObject) + queryErr := performQuery(sf.auth, soqlQuery, sObject) if queryErr != nil { return queryErr } @@ -260,7 +292,7 @@ func (sf *Salesforce) InsertOne(sObjectName string, record any) (SalesforceResul return SalesforceResult{}, validationErr } - return doInsertOne(*sf.auth, sObjectName, record) + return doInsertOne(sf.auth, sObjectName, record) } func (sf *Salesforce) UpdateOne(sObjectName string, record any) error { @@ -269,7 +301,7 @@ func (sf *Salesforce) UpdateOne(sObjectName string, record any) error { return validationErr } - return doUpdateOne(*sf.auth, sObjectName, record) + return doUpdateOne(sf.auth, sObjectName, record) } func (sf *Salesforce) UpsertOne(sObjectName string, externalIdFieldName string, record any) (SalesforceResult, error) { @@ -278,7 +310,7 @@ func (sf *Salesforce) UpsertOne(sObjectName string, externalIdFieldName string, return SalesforceResult{}, validationErr } - return doUpsertOne(*sf.auth, sObjectName, externalIdFieldName, record) + return doUpsertOne(sf.auth, sObjectName, externalIdFieldName, record) } func (sf *Salesforce) DeleteOne(sObjectName string, record any) error { @@ -287,7 +319,7 @@ func (sf *Salesforce) DeleteOne(sObjectName string, record any) error { return validationErr } - return doDeleteOne(*sf.auth, sObjectName, record) + return doDeleteOne(sf.auth, sObjectName, record) } func (sf *Salesforce) InsertCollection(sObjectName string, records any, batchSize int) (SalesforceResults, error) { @@ -296,7 +328,7 @@ func (sf *Salesforce) InsertCollection(sObjectName string, records any, batchSiz return SalesforceResults{}, validationErr } - return doInsertCollection(*sf.auth, sObjectName, records, batchSize) + return doInsertCollection(sf.auth, sObjectName, records, batchSize) } func (sf *Salesforce) UpdateCollection(sObjectName string, records any, batchSize int) (SalesforceResults, error) { @@ -305,7 +337,7 @@ func (sf *Salesforce) UpdateCollection(sObjectName string, records any, batchSiz return SalesforceResults{}, validationErr } - return doUpdateCollection(*sf.auth, sObjectName, records, batchSize) + return doUpdateCollection(sf.auth, sObjectName, records, batchSize) } func (sf *Salesforce) UpsertCollection(sObjectName string, externalIdFieldName string, records any, batchSize int) (SalesforceResults, error) { @@ -314,7 +346,7 @@ func (sf *Salesforce) UpsertCollection(sObjectName string, externalIdFieldName s return SalesforceResults{}, validationErr } - return doUpsertCollection(*sf.auth, sObjectName, externalIdFieldName, records, batchSize) + return doUpsertCollection(sf.auth, sObjectName, externalIdFieldName, records, batchSize) } func (sf *Salesforce) DeleteCollection(sObjectName string, records any, batchSize int) (SalesforceResults, error) { @@ -323,7 +355,7 @@ func (sf *Salesforce) DeleteCollection(sObjectName string, records any, batchSiz return SalesforceResults{}, validationErr } - return doDeleteCollection(*sf.auth, sObjectName, records, batchSize) + return doDeleteCollection(sf.auth, sObjectName, records, batchSize) } func (sf *Salesforce) InsertComposite(sObjectName string, records any, batchSize int, allOrNone bool) (SalesforceResults, error) { @@ -332,7 +364,7 @@ func (sf *Salesforce) InsertComposite(sObjectName string, records any, batchSize return SalesforceResults{}, validationErr } - return doInsertComposite(*sf.auth, sObjectName, records, allOrNone, batchSize) + return doInsertComposite(sf.auth, sObjectName, records, allOrNone, batchSize) } func (sf *Salesforce) UpdateComposite(sObjectName string, records any, batchSize int, allOrNone bool) (SalesforceResults, error) { @@ -341,7 +373,7 @@ func (sf *Salesforce) UpdateComposite(sObjectName string, records any, batchSize return SalesforceResults{}, validationErr } - return doUpdateComposite(*sf.auth, sObjectName, records, allOrNone, batchSize) + return doUpdateComposite(sf.auth, sObjectName, records, allOrNone, batchSize) } func (sf *Salesforce) UpsertComposite(sObjectName string, externalIdFieldName string, records any, batchSize int, allOrNone bool) (SalesforceResults, error) { @@ -350,7 +382,7 @@ func (sf *Salesforce) UpsertComposite(sObjectName string, externalIdFieldName st return SalesforceResults{}, validationErr } - return doUpsertComposite(*sf.auth, sObjectName, externalIdFieldName, records, allOrNone, batchSize) + return doUpsertComposite(sf.auth, sObjectName, externalIdFieldName, records, allOrNone, batchSize) } func (sf *Salesforce) DeleteComposite(sObjectName string, records any, batchSize int, allOrNone bool) (SalesforceResults, error) { @@ -359,7 +391,7 @@ func (sf *Salesforce) DeleteComposite(sObjectName string, records any, batchSize return SalesforceResults{}, validationErr } - return doDeleteComposite(*sf.auth, sObjectName, records, allOrNone, batchSize) + return doDeleteComposite(sf.auth, sObjectName, records, allOrNone, batchSize) } func (sf *Salesforce) QueryBulkExport(query string, filePath string) error { @@ -367,7 +399,7 @@ func (sf *Salesforce) QueryBulkExport(query string, filePath string) error { if authErr != nil { return authErr } - queryErr := doQueryBulk(*sf.auth, filePath, query) + queryErr := doQueryBulk(sf.auth, filePath, query) if queryErr != nil { return queryErr } @@ -385,7 +417,7 @@ func (sf *Salesforce) QueryStructBulkExport(soqlStruct any, filePath string) err if err != nil { return err } - queryErr := doQueryBulk(*sf.auth, filePath, soqlQuery) + queryErr := doQueryBulk(sf.auth, filePath, soqlQuery) if queryErr != nil { return queryErr } @@ -399,7 +431,7 @@ func (sf *Salesforce) InsertBulk(sObjectName string, records any, batchSize int, return []string{}, validationErr } - jobIds, bulkErr := doBulkJob(*sf.auth, sObjectName, "", insertOperation, records, batchSize, waitForResults) + jobIds, bulkErr := doBulkJob(sf.auth, sObjectName, "", insertOperation, records, batchSize, waitForResults) if bulkErr != nil { return []string{}, bulkErr } @@ -413,7 +445,7 @@ func (sf *Salesforce) InsertBulkFile(sObjectName string, filePath string, batchS return []string{}, validationErr } - jobIds, bulkErr := doBulkJobWithFile(*sf.auth, sObjectName, "", insertOperation, filePath, batchSize, waitForResults) + jobIds, bulkErr := doBulkJobWithFile(sf.auth, sObjectName, "", insertOperation, filePath, batchSize, waitForResults) if bulkErr != nil { return []string{}, bulkErr } @@ -427,7 +459,7 @@ func (sf *Salesforce) UpdateBulk(sObjectName string, records any, batchSize int, return []string{}, validationErr } - jobIds, bulkErr := doBulkJob(*sf.auth, sObjectName, "", updateOperation, records, batchSize, waitForResults) + jobIds, bulkErr := doBulkJob(sf.auth, sObjectName, "", updateOperation, records, batchSize, waitForResults) if bulkErr != nil { return []string{}, bulkErr } @@ -441,7 +473,7 @@ func (sf *Salesforce) UpdateBulkFile(sObjectName string, filePath string, batchS return []string{}, validationErr } - jobIds, bulkErr := doBulkJobWithFile(*sf.auth, sObjectName, "", updateOperation, filePath, batchSize, waitForResults) + jobIds, bulkErr := doBulkJobWithFile(sf.auth, sObjectName, "", updateOperation, filePath, batchSize, waitForResults) if bulkErr != nil { return []string{}, bulkErr } @@ -455,7 +487,7 @@ func (sf *Salesforce) UpsertBulk(sObjectName string, externalIdFieldName string, return []string{}, validationErr } - jobIds, bulkErr := doBulkJob(*sf.auth, sObjectName, externalIdFieldName, upsertOperation, records, batchSize, waitForResults) + jobIds, bulkErr := doBulkJob(sf.auth, sObjectName, externalIdFieldName, upsertOperation, records, batchSize, waitForResults) if bulkErr != nil { return []string{}, bulkErr } @@ -469,7 +501,7 @@ func (sf *Salesforce) UpsertBulkFile(sObjectName string, externalIdFieldName str return []string{}, validationErr } - jobIds, bulkErr := doBulkJobWithFile(*sf.auth, sObjectName, externalIdFieldName, upsertOperation, filePath, batchSize, waitForResults) + jobIds, bulkErr := doBulkJobWithFile(sf.auth, sObjectName, externalIdFieldName, upsertOperation, filePath, batchSize, waitForResults) if bulkErr != nil { return []string{}, bulkErr } @@ -483,7 +515,7 @@ func (sf *Salesforce) DeleteBulk(sObjectName string, records any, batchSize int, return []string{}, validationErr } - jobIds, bulkErr := doBulkJob(*sf.auth, sObjectName, "", deleteOperation, records, batchSize, waitForResults) + jobIds, bulkErr := doBulkJob(sf.auth, sObjectName, "", deleteOperation, records, batchSize, waitForResults) if bulkErr != nil { return []string{}, bulkErr } @@ -497,7 +529,7 @@ func (sf *Salesforce) DeleteBulkFile(sObjectName string, filePath string, batchS return []string{}, validationErr } - jobIds, bulkErr := doBulkJobWithFile(*sf.auth, sObjectName, "", deleteOperation, filePath, batchSize, waitForResults) + jobIds, bulkErr := doBulkJobWithFile(sf.auth, sObjectName, "", deleteOperation, filePath, batchSize, waitForResults) if bulkErr != nil { return []string{}, bulkErr } @@ -511,13 +543,13 @@ func (sf *Salesforce) GetJobResults(bulkJobId string) (BulkJobResults, error) { return BulkJobResults{}, authErr } - job, err := getJobResults(*sf.auth, ingestJobType, bulkJobId) + job, err := getJobResults(sf.auth, ingestJobType, bulkJobId) if err != nil { return BulkJobResults{}, err } if job.State == jobStateJobComplete { - job, err = getJobRecordResults(*sf.auth, job) + job, err = getJobRecordResults(sf.auth, job) if err != nil { return job, err } diff --git a/salesforce_test.go b/salesforce_test.go index 691105b..769fbaf 100644 --- a/salesforce_test.go +++ b/salesforce_test.go @@ -6,6 +6,7 @@ import ( "net/http" "net/http/httptest" "reflect" + "strconv" "strings" "testing" @@ -41,11 +42,8 @@ func Test_doRequest(t *testing.T) { defer badServer.Close() type args struct { - method string - uri string - content string - auth authentication - body string + auth *authentication + payload requestPayload } tests := []struct { name string @@ -56,11 +54,13 @@ func Test_doRequest(t *testing.T) { { name: "make_generic_http_call_ok", args: args{ - method: http.MethodGet, - uri: "", - content: jsonType, - auth: sfAuth, - body: "", + auth: &sfAuth, + payload: requestPayload{ + method: http.MethodGet, + uri: "", + content: jsonType, + body: "", + }, }, want: http.StatusOK, wantErr: false, @@ -68,11 +68,13 @@ func Test_doRequest(t *testing.T) { { name: "make_generic_http_call_bad_request", args: args{ - method: http.MethodGet, - uri: "", - content: jsonType, - auth: badSfAuth, - body: "", + auth: &badSfAuth, + payload: requestPayload{ + method: http.MethodGet, + uri: "", + content: jsonType, + body: "", + }, }, want: http.StatusBadRequest, wantErr: true, @@ -80,7 +82,7 @@ func Test_doRequest(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := doRequest(tt.args.method, tt.args.uri, tt.args.content, tt.args.auth, tt.args.body) + got, err := doRequest(tt.args.auth, tt.args.payload) if (err != nil) != tt.wantErr { t.Errorf("doRequest() error = %v, wantErr %v", err, tt.wantErr) return @@ -253,51 +255,195 @@ func Test_validateBatchSizeWithinRange(t *testing.T) { } func Test_processSalesforceError(t *testing.T) { + body, _ := json.Marshal([]SalesforceErrorMessage{{ + Message: "error message", + StatusCode: strconv.Itoa(http.StatusInternalServerError), + Fields: []string{}, + ErrorCode: strconv.Itoa(http.StatusInternalServerError), + }}) + exampleResp := http.Response{ + Status: "500", + StatusCode: 500, + Body: io.NopCloser(strings.NewReader(string(body))), + } + + badServer, badSfAuth := setupTestServer(body, http.StatusInternalServerError) + defer badServer.Close() + + bodyInvalidSession, _ := json.Marshal([]SalesforceErrorMessage{{ + Message: "error message", + StatusCode: strconv.Itoa(http.StatusInternalServerError), + Fields: []string{}, + ErrorCode: invalidSessionIdError, + }}) + reqPayload := requestPayload{ + method: http.MethodGet, + uri: "", + content: jsonType, + body: "", + } + + serverRefreshed, sfAuthRefreshed := setupTestServer("", http.StatusOK) + defer serverRefreshed.Close() + serverInvalidSession, sfAuthInvalidSession := setupTestServer(sfAuthRefreshed, http.StatusOK) + defer serverInvalidSession.Close() + sfAuthInvalidSession.grantType = grantTypeClientCredentials + + serverRefreshFail, sfAuthRefreshFail := setupTestServer("", http.StatusBadRequest) + defer serverRefreshFail.Close() + sfAuthRefreshFail.grantType = grantTypeClientCredentials + + serverRetryFail := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if strings.Contains(r.RequestURI, "/oauth2/token") { + body, err := json.Marshal(badSfAuth) + if err != nil { + panic(err) + } + if _, err := w.Write(body); err != nil { + panic(err) + } + } else { + w.WriteHeader(http.StatusBadRequest) + } + })) + defer serverRetryFail.Close() + sfAuthRetryFail := authentication{ + InstanceUrl: serverRetryFail.URL, + AccessToken: "1234", + grantType: grantTypeClientCredentials, + } + type args struct { - resp http.Response + resp http.Response + auth *authentication + payload requestPayload } tests := []struct { name string args args - want string + want int wantErr bool }{ { name: "process_500_error", + args: args{ + resp: exampleResp, + auth: &badSfAuth, + payload: reqPayload, + }, + want: exampleResp.StatusCode, + wantErr: true, + }, + { + name: "process_invalid_session", args: args{ resp: http.Response{ - Status: "500", - StatusCode: 500, - Body: io.NopCloser(strings.NewReader("error message")), + Status: "400", + StatusCode: 400, + Body: io.NopCloser(strings.NewReader(string(bodyInvalidSession))), }, + auth: &sfAuthInvalidSession, + payload: reqPayload, }, - want: "500: error message", + want: http.StatusOK, + wantErr: false, + }, + { + name: "fail_to_refresh", + args: args{ + resp: http.Response{ + Status: "400", + StatusCode: 400, + Body: io.NopCloser(strings.NewReader(string(bodyInvalidSession))), + }, + auth: &sfAuthRefreshFail, + payload: reqPayload, + }, + want: 400, + wantErr: true, + }, + { + name: "fail_to_retry_request", + args: args{ + resp: http.Response{ + Status: "400", + StatusCode: 400, + Body: io.NopCloser(strings.NewReader(string(bodyInvalidSession))), + }, + auth: &sfAuthRetryFail, + payload: reqPayload, + }, + want: 400, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := processSalesforceError(tt.args.resp) - if err != nil != tt.wantErr { + got, err := processSalesforceError(tt.args.resp, tt.args.auth, tt.args.payload) + if (err != nil) != tt.wantErr { t.Errorf("processSalesforceError() error = %v, wantErr %v", err, tt.wantErr) + return } - if !reflect.DeepEqual(err.Error(), tt.want) { - t.Errorf("processSalesforceError() = %v, want %v", err.Error(), tt.want) + if !reflect.DeepEqual(got.StatusCode, tt.want) { + t.Errorf("processSalesforceError() = %v, want %v", got.StatusCode, tt.want) } }) } } func TestInit(t *testing.T) { - sfAuth := authentication{ + sfAuthUsernamePassword := authentication{ AccessToken: "1234", InstanceUrl: "example.com", Id: "123abc", IssuedAt: "01/01/1970", Signature: "signed", + grantType: grantTypeUsernamePassword, } - server, _ := setupTestServer(sfAuth, http.StatusOK) - defer server.Close() + serverUsernamePassword, _ := setupTestServer(sfAuthUsernamePassword, http.StatusOK) + defer serverUsernamePassword.Close() + credsUsernamePassword := Creds{ + Domain: serverUsernamePassword.URL, + Username: "u", + Password: "p", + SecurityToken: "t", + ConsumerKey: "key", + ConsumerSecret: "secret", + } + sfAuthUsernamePassword.creds = credsUsernamePassword + + sfAuthClientCredentials := authentication{ + AccessToken: "1234", + InstanceUrl: "example.com", + Id: "123abc", + IssuedAt: "01/01/1970", + Signature: "signed", + grantType: grantTypeClientCredentials, + } + serverClientCredentials, _ := setupTestServer(sfAuthClientCredentials, http.StatusOK) + defer serverClientCredentials.Close() + credsClientCredentials := Creds{ + Domain: serverClientCredentials.URL, + ConsumerKey: "key", + ConsumerSecret: "secret", + } + sfAuthClientCredentials.creds = credsClientCredentials + + sfAuthAccessToken := authentication{ + AccessToken: "1234", + InstanceUrl: "example.com", + Id: "123abc", + IssuedAt: "01/01/1970", + Signature: "signed", + grantType: grantTypeAccessToken, + } + serverAccessToken, _ := setupTestServer(sfAuthAccessToken, http.StatusOK) + defer serverAccessToken.Close() + credsAccessToken := Creds{ + Domain: serverAccessToken.URL, + AccessToken: "1234", + } + sfAuthAccessToken.creds = credsAccessToken type args struct { creds Creds @@ -315,34 +461,20 @@ func TestInit(t *testing.T) { wantErr: true, }, { - name: "authentication_username_password", - args: args{creds: Creds{ - Domain: server.URL, - Username: "u", - Password: "p", - SecurityToken: "t", - ConsumerKey: "key", - ConsumerSecret: "secret", - }}, - want: &Salesforce{auth: &sfAuth}, + name: "authentication_username_password", + args: args{creds: sfAuthUsernamePassword.creds}, + want: &Salesforce{auth: &sfAuthUsernamePassword}, wantErr: false, }, { - name: "authentication_client_credentials", - args: args{creds: Creds{ - Domain: server.URL, - ConsumerKey: "key", - ConsumerSecret: "secret", - }}, - want: &Salesforce{auth: &sfAuth}, + name: "authentication_client_credentials", + args: args{creds: credsClientCredentials}, + want: &Salesforce{auth: &sfAuthClientCredentials}, wantErr: false, }, { - name: "authentication_access_token", - args: args{creds: Creds{ - Domain: server.URL, - AccessToken: "1234", - }}, + name: "authentication_access_token", + args: args{creds: credsAccessToken}, wantErr: false, }, }