Repository: amp-labs/connectors
Pull request #63 (2024-02-06 23:29:28)

[ENG-643] Resurrect SF tests.

codesee-maps[bot]: <details open>
<summary><b>👇 <a href="https://app.codesee.io/r/reviews?pr=63&src=https%3A%2F%2Fgithub.com%2Famp-labs%2Fconnectors">Click on the image</a> for a new way to code review</b>
</summary>

####

[![Review these changes using an interactive CodeSee Map](https://s3.us-east-2.amazonaws.com/maps.codesee.io/images/github/amp-labs/connectors/63/ea5884e1/3d1ad47f111dd1772414a28b0ad51462f7d6570e.svg)](https://app.codesee.io/r/reviews?pr=63&src=https%3A%2F%2Fgithub.com%2Famp-labs%2Fconnectors)

#### Legend
<img src="https://app.codesee.io/Legend_v3.png" alt="CodeSee Map legend" width="220" height="156">
</details>
jlimatampersand: Current dependencies on/for this PR:
* `main`
  * **PR #63** <a href="https://app.graphite.dev/github/pr/amp-labs/connectors/63?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>  👈

This [stack of pull requests](https://stacking.dev/?utm_source=stack-comment) is managed by [Graphite](https://app.graphite.dev/github/pr/amp-labs/connectors/63?utm_source=stack-comment).

Patch:
diff --git a/go.mod b/go.mod
index 273c7d87..7dc7663b 100644
--- a/go.mod
+++ b/go.mod
@@ -5,6 +5,7 @@ go 1.21
 require (
 	github.com/brianvoe/gofakeit/v6 v6.28.0
 	github.com/go-playground/validator v9.31.0+incompatible
+	github.com/joho/godotenv v1.5.1
 	github.com/spyzhov/ajson v0.9.0
 	github.com/stretchr/testify v1.8.4
 	github.com/subchen/go-xmldom v1.1.2
diff --git a/go.sum b/go.sum
index 4afc3fad..5509275e 100644
--- a/go.sum
+++ b/go.sum
@@ -18,6 +18,8 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
+github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
 github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
 github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=


Test patch:
diff --git a/test/.gitignore b/test/.gitignore
deleted file mode 100644
index 9b50cdec..00000000
--- a/test/.gitignore
+++ /dev/null
@@ -1,1 +0,0 @@
-salesforce
diff --git a/test/salesforce/read-write.go b/test/salesforce/read-write.go
new file mode 100644
index 00000000..a84eedb5
--- /dev/null
+++ b/test/salesforce/read-write.go
@@ -0,0 +1,186 @@
+package main
+
+import (
+	"context"
+	"encoding/json"
+	"flag"
+	"fmt"
+	"log/slog"
+	"net/http"
+	"os"
+	"time"
+
+	"github.com/amp-labs/connectors"
+	"github.com/amp-labs/connectors/salesforce"
+	"github.com/joho/godotenv"
+	"golang.org/x/oauth2"
+)
+
+// Set the appropriate environment variables in a .env file, then run:
+// go run test/salesforce.go
+
+const TimeoutSeconds = 30
+
+func main() {
+	os.Exit(mainFn())
+}
+
+func mainFn() int { //nolint:funlen
+	if err := godotenv.Load(); err != nil {
+		slog.Error("Error loading .env file", "error", err)
+
+		return 1
+	}
+
+	salesforceSubdomain := os.Getenv("SALESFORCE_SUBDOMAIN")
+	clientId := os.Getenv("SALESFORCE_CLIENT_ID")
+	clientSecret := os.Getenv("SALESFORCE_CLIENT_SECRET")
+	accessToken := os.Getenv("SALESFORCE_ACCESS_TOKEN")
+	refreshToken := os.Getenv("SALESFORCE_REFRESH_TOKEN")
+
+	subdomain := flag.String("subdomain", salesforceSubdomain, "Salesforce subdomain")
+	flag.Parse()
+
+	handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
+		Level: slog.LevelDebug,
+	})
+	logger := slog.New(handler)
+	slog.SetDefault(logger)
+
+	cfg := &oauth2.Config{
+		ClientID:     clientId,
+		ClientSecret: clientSecret,
+		Endpoint: oauth2.Endpoint{
+			AuthURL:   fmt.Sprintf("https://%s.my.salesforce.com/services/oauth2/authorize", *subdomain),
+			TokenURL:  fmt.Sprintf("https://%s.my.salesforce.com/services/oauth2/token", *subdomain),
+			AuthStyle: oauth2.AuthStyleInParams,
+		},
+	}
+
+	tok := &oauth2.Token{
+		AccessToken:  accessToken,
+		RefreshToken: refreshToken,
+		TokenType:    "bearer",
+		Expiry:       time.Now().Add(-1 * time.Hour), // just pretend it's expired already, whatever, it'll fetch a new one.
+	}
+
+	ctx := context.Background()
+
+	// Create a new Salesforce connector, with a token provider that uses the sfdx CLI to fetch an access token.
+	sfc, err := connectors.Salesforce(
+		salesforce.WithClient(ctx, http.DefaultClient, cfg, tok),
+		salesforce.WithSubdomain(*subdomain))
+	if err != nil {
+		slog.Error("Error creating Salesforce connector", "error", err)
+
+		return 1
+	}
+
+	defer func() {
+		_ = sfc.Close()
+	}()
+
+	if err := testConnector(ctx, sfc); err != nil {
+		slog.Error("Error testing", "connector", sfc, "error", err)
+
+		return 1
+	}
+
+	return 0
+}
+
+func testConnector(ctx context.Context, conn connectors.Connector) error {
+	// Create a context with a timeout
+	ctx, done := context.WithTimeout(ctx, TimeoutSeconds*time.Second)
+	defer done()
+
+	// Read some data from Salesforce
+	res, err := conn.Read(ctx, connectors.ReadParams{
+		ObjectName: "Account",
+		Fields:     []string{"Id", "Name", "BillingCity", "IsDeleted"},
+	})
+	if err != nil {
+		return fmt.Errorf("error reading from Salesforce: %w", err)
+	}
+
+	// Print the results
+	jsonStr, err := json.MarshalIndent(res, "", "  ")
+	if err != nil {
+		return fmt.Errorf("error marshalling JSON: %w", err)
+	}
+
+	_, _ = os.Stdout.Write(jsonStr)
+	_, _ = os.Stdout.WriteString("\n")
+
+	// IMPORTANT: every time this test is run, it will create a new Account
+	// in SFDC instance. Will need to delete those out at later date.
+	writtenRecordId, err := testSalesforceValidCreate(ctx, conn)
+	if err != nil {
+		return fmt.Errorf("error creating record in Salesforce: %w", err)
+	}
+	// IMPORTANT: will fail if specific recordId does not already exist in instance
+	if err := testSalesforceValidUpdate(ctx, conn, writtenRecordId); err != nil {
+		return fmt.Errorf("error updating record in Salesforce: %w", err)
+	}
+
+	return nil
+}
+
+const accountNumber = 123
+
+// testSalesforceValidCreate will create a valid record in Salesforce.
+func testSalesforceValidCreate(ctx context.Context, conn connectors.Connector) (string, error) {
+	writeRes, err := conn.Write(ctx, connectors.WriteParams{
+		ObjectName: "Account",
+		RecordData: map[string]interface{}{
+			"Name":          "TEST ACCOUNT - [TO DELETE]",
+			"AccountNumber": accountNumber,
+		},
+	})
+	if err != nil {
+		return "", fmt.Errorf("error writing to Salesforce: %w", err)
+	}
+
+	// Print the results
+	jsonStr, err := json.MarshalIndent(writeRes, "", "  ")
+	if err != nil {
+		return "", fmt.Errorf("error marshalling JSON: %w", err)
+	}
+
+	_, _ = os.Stdout.Write(jsonStr)
+	_, _ = os.Stdout.WriteString("\n")
+
+	return writeRes.RecordId, nil
+}
+
+const accountNumber2 = 456
+
+// testSalesforceValidUpdate will update existing record in Salesforce.
+func testSalesforceValidUpdate(ctx context.Context, conn connectors.Connector, writtenRecordId string) error {
+	writeRes, err := conn.Write(ctx, connectors.WriteParams{
+		ObjectName: "Account",
+		RecordData: map[string]interface{}{
+			"Name":          "OKADA TEST ACCOUNT",
+			"AccountNumber": accountNumber2,
+		},
+		RecordId: writtenRecordId,
+	})
+	if err != nil {
+		return fmt.Errorf("error writing to Salesforce: %w", err)
+	}
+
+	if !writeRes.Success {
+		return fmt.Errorf("write to %s failed when it should have succeeded", writtenRecordId) //nolint:goerr113
+	}
+
+	// Print the results
+	jsonStr, err := json.MarshalIndent(writeRes, "", "  ")
+	if err != nil {
+		return fmt.Errorf("error marshalling JSON: %w", err)
+	}
+
+	_, _ = os.Stdout.Write(jsonStr)
+	_, _ = os.Stdout.WriteString("\n")
+
+	return nil
+}