github.com/SaurabhDubey-Groww/go-cloud@v0.0.0-20221124105541-b26c29285fd8/internal/testing/setup/setup.go (about)

     1  // Copyright 2019 The Go Cloud Development Kit Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     https://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package setup // import "gocloud.dev/internal/testing/setup"
    16  
    17  import (
    18  	"context"
    19  	"flag"
    20  	"io/ioutil"
    21  	"net/http"
    22  	"os"
    23  	"path/filepath"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/aws/aws-sdk-go/aws"
    28  	awscreds "github.com/aws/aws-sdk-go/aws/credentials"
    29  	"github.com/aws/aws-sdk-go/aws/session"
    30  
    31  	awsv2 "github.com/aws/aws-sdk-go-v2/aws"
    32  	awsv2config "github.com/aws/aws-sdk-go-v2/config"
    33  	awsv2creds "github.com/aws/aws-sdk-go-v2/credentials"
    34  
    35  	"gocloud.dev/gcp"
    36  	"gocloud.dev/internal/useragent"
    37  
    38  	"github.com/google/go-replayers/grpcreplay"
    39  	"github.com/google/go-replayers/httpreplay"
    40  	hrgoog "github.com/google/go-replayers/httpreplay/google"
    41  	"golang.org/x/oauth2/google"
    42  	"google.golang.org/api/option"
    43  	"google.golang.org/grpc"
    44  	grpccreds "google.golang.org/grpc/credentials"
    45  	"google.golang.org/grpc/credentials/oauth"
    46  )
    47  
    48  // Record is true iff the tests are being run in "record" mode.
    49  var Record = flag.Bool("record", false, "whether to run tests against cloud resources and record the interactions")
    50  
    51  // FakeGCPCredentials gets fake GCP credentials.
    52  func FakeGCPCredentials(ctx context.Context) (*google.Credentials, error) {
    53  	return google.CredentialsFromJSON(ctx, []byte(`{"type": "service_account", "project_id": "my-project-id"}`))
    54  }
    55  
    56  func awsSession(region string, client *http.Client) (*session.Session, error) {
    57  	// Provide fake creds if running in replay mode.
    58  	var creds *awscreds.Credentials
    59  	if !*Record {
    60  		creds = awscreds.NewStaticCredentials("FAKE_ID", "FAKE_SECRET", "FAKE_TOKEN")
    61  	}
    62  	return session.NewSession(&aws.Config{
    63  		HTTPClient:  client,
    64  		Region:      aws.String(region),
    65  		Credentials: creds,
    66  		MaxRetries:  aws.Int(0),
    67  	})
    68  }
    69  
    70  func awsV2Config(ctx context.Context, region string, client *http.Client) (awsv2.Config, error) {
    71  	// Provide fake creds if running in replay mode.
    72  	var creds awsv2.CredentialsProvider
    73  	if !*Record {
    74  		creds = awsv2creds.NewStaticCredentialsProvider("FAKE_KEY", "FAKE_SECRET", "FAKE_SESSION")
    75  	}
    76  	return awsv2config.LoadDefaultConfig(
    77  		ctx,
    78  		awsv2config.WithHTTPClient(client),
    79  		awsv2config.WithRegion(region),
    80  		awsv2config.WithCredentialsProvider(creds),
    81  		awsv2config.WithRetryer(func() awsv2.Retryer { return awsv2.NopRetryer{} }),
    82  	)
    83  }
    84  
    85  // NewRecordReplayClient creates a new http.Client for tests. This client's
    86  // activity is being either recorded to files (when *Record is set) or replayed
    87  // from files. rf is a modifier function that will be invoked with the address
    88  // of the httpreplay.Recorder object used to obtain the client; this function
    89  // can mutate the recorder to add service-specific header filters, for example.
    90  // An initState is returned for tests that need a state to have deterministic
    91  // results, for example, a seed to generate random sequences.
    92  func NewRecordReplayClient(ctx context.Context, t *testing.T, rf func(r *httpreplay.Recorder)) (c *http.Client, cleanup func(), initState int64) {
    93  	httpreplay.DebugHeaders()
    94  	path := filepath.Join("testdata", t.Name()+".replay")
    95  	if *Record {
    96  		t.Logf("Recording into golden file %s", path)
    97  		if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
    98  			t.Fatal(err)
    99  		}
   100  		state := time.Now()
   101  		b, _ := state.MarshalBinary()
   102  		rec, err := httpreplay.NewRecorder(path, b)
   103  		if err != nil {
   104  			t.Fatal(err)
   105  		}
   106  		rf(rec)
   107  		cleanup = func() {
   108  			if err := rec.Close(); err != nil {
   109  				t.Fatal(err)
   110  			}
   111  		}
   112  
   113  		return rec.Client(), cleanup, state.UnixNano()
   114  	}
   115  	t.Logf("Replaying from golden file %s", path)
   116  	rep, err := httpreplay.NewReplayer(path)
   117  	if err != nil {
   118  		t.Fatal(err)
   119  	}
   120  	recState := new(time.Time)
   121  	if err := recState.UnmarshalBinary(rep.Initial()); err != nil {
   122  		t.Fatal(err)
   123  	}
   124  	return rep.Client(), func() { rep.Close() }, recState.UnixNano()
   125  }
   126  
   127  // NewAWSSession creates a new session for testing against AWS.
   128  // If the test is in --record mode, the test will call out to AWS, and the
   129  // results are recorded in a replay file.
   130  // Otherwise, the session reads a replay file and runs the test as a replay,
   131  // which never makes an outgoing HTTP call and uses fake credentials.
   132  // An initState is returned for tests that need a state to have deterministic
   133  // results, for example, a seed to generate random sequences.
   134  func NewAWSSession(ctx context.Context, t *testing.T, region string) (sess *session.Session,
   135  	rt http.RoundTripper, cleanup func(), initState int64) {
   136  	client, cleanup, state := NewRecordReplayClient(ctx, t, func(r *httpreplay.Recorder) {
   137  		r.RemoveQueryParams("X-Amz-Credential", "X-Amz-Signature", "X-Amz-Security-Token")
   138  		r.RemoveRequestHeaders("Authorization", "Duration", "X-Amz-Security-Token")
   139  		r.ClearHeaders("X-Amz-Date")
   140  		r.ClearQueryParams("X-Amz-Date")
   141  		r.ClearHeaders("User-Agent") // AWS includes the Go version
   142  	})
   143  	sess, err := awsSession(region, client)
   144  	if err != nil {
   145  		t.Fatal(err)
   146  	}
   147  	return sess, client.Transport, cleanup, state
   148  }
   149  
   150  // NewAWSv2Config creates a new aws.Config for testing against AWS.
   151  // If the test is in --record mode, the test will call out to AWS, and the
   152  // results are recorded in a replay file.
   153  // Otherwise, the session reads a replay file and runs the test as a replay,
   154  // which never makes an outgoing HTTP call and uses fake credentials.
   155  // An initState is returned for tests that need a state to have deterministic
   156  // results, for example, a seed to generate random sequences.
   157  func NewAWSv2Config(ctx context.Context, t *testing.T, region string) (cfg awsv2.Config, rt http.RoundTripper, cleanup func(), initState int64) {
   158  	client, cleanup, state := NewRecordReplayClient(ctx, t, func(r *httpreplay.Recorder) {
   159  		r.RemoveQueryParams("X-Amz-Credential", "X-Amz-Signature", "X-Amz-Security-Token")
   160  		r.RemoveRequestHeaders("Authorization", "Duration", "X-Amz-Security-Token")
   161  		r.ClearHeaders("Amz-Sdk-Invocation-Id")
   162  		r.ClearHeaders("X-Amz-Date")
   163  		r.ClearQueryParams("X-Amz-Date")
   164  		r.ClearHeaders("User-Agent") // AWS includes the Go version
   165  	})
   166  	cfg, err := awsV2Config(ctx, region, client)
   167  	if err != nil {
   168  		t.Fatal(err)
   169  	}
   170  	return cfg, client.Transport, cleanup, state
   171  }
   172  
   173  // NewGCPClient creates a new HTTPClient for testing against GCP.
   174  // NewGCPClient creates a new HTTPClient for testing against GCP.
   175  // If the test is in --record mode, the client will call out to GCP, and the
   176  // results are recorded in a replay file.
   177  // Otherwise, the session reads a replay file and runs the test as a replay,
   178  // which never makes an outgoing HTTP call and uses fake credentials.
   179  func NewGCPClient(ctx context.Context, t *testing.T) (client *gcp.HTTPClient, rt http.RoundTripper, done func()) {
   180  	c, cleanup, _ := NewRecordReplayClient(ctx, t, func(r *httpreplay.Recorder) {
   181  		r.ClearQueryParams("Expires")
   182  		r.ClearQueryParams("Signature")
   183  		r.ClearHeaders("Expires")
   184  		r.ClearHeaders("Signature")
   185  	})
   186  	transport := c.Transport
   187  	if *Record {
   188  		creds, err := gcp.DefaultCredentials(ctx)
   189  		if err != nil {
   190  			t.Fatalf("failed to get default credentials: %v", err)
   191  		}
   192  		c, err = hrgoog.RecordClient(ctx, c, option.WithTokenSource(gcp.CredentialsTokenSource(creds)))
   193  		if err != nil {
   194  			t.Fatal(err)
   195  		}
   196  	}
   197  	return &gcp.HTTPClient{Client: *c}, transport, cleanup
   198  }
   199  
   200  // NewGCPgRPCConn creates a new connection for testing against GCP via gRPC.
   201  // If the test is in --record mode, the client will call out to GCP, and the
   202  // results are recorded in a replay file.
   203  // Otherwise, the session reads a replay file and runs the test as a replay,
   204  // which never makes an outgoing RPC and uses fake credentials.
   205  func NewGCPgRPCConn(ctx context.Context, t *testing.T, endPoint, api string) (*grpc.ClientConn, func()) {
   206  	filename := t.Name() + ".replay"
   207  	if *Record {
   208  		opts, done := newGCPRecordDialOptions(t, filename)
   209  		opts = append(opts, useragent.GRPCDialOption(api))
   210  		// Add credentials for real RPCs.
   211  		creds, err := gcp.DefaultCredentials(ctx)
   212  		if err != nil {
   213  			t.Fatal(err)
   214  		}
   215  		opts = append(opts, grpc.WithTransportCredentials(grpccreds.NewClientTLSFromCert(nil, "")))
   216  		opts = append(opts, grpc.WithPerRPCCredentials(oauth.TokenSource{TokenSource: gcp.CredentialsTokenSource(creds)}))
   217  		conn, err := grpc.DialContext(ctx, endPoint, opts...)
   218  		if err != nil {
   219  			t.Fatal(err)
   220  		}
   221  		return conn, done
   222  	}
   223  	rep, done := newGCPReplayer(t, filename)
   224  	conn, err := rep.Connection()
   225  	if err != nil {
   226  		t.Fatal(err)
   227  	}
   228  	return conn, done
   229  }
   230  
   231  // NewAzureTestBlobClient creates a new connection for testing against Azure Blob.
   232  func NewAzureTestBlobClient(ctx context.Context, t *testing.T) (*http.Client, func()) {
   233  	client, cleanup, _ := NewRecordReplayClient(ctx, t, func(r *httpreplay.Recorder) {
   234  		r.RemoveQueryParams("se", "sig", "st")
   235  		r.RemoveQueryParams("X-Ms-Date")
   236  		r.ClearQueryParams("blockid")
   237  		r.ClearHeaders("X-Ms-Date")
   238  		r.ClearHeaders("X-Ms-Version")
   239  		r.ClearHeaders("User-Agent") // includes the full Go version
   240  		// Yes, it's true, Azure does not appear to be internally
   241  		// consistent about casing for BLock(l|L)ist.
   242  		r.ScrubBody("<Block(l|L)ist><Latest>.*</Latest></Block(l|L)ist>")
   243  	})
   244  	return client, cleanup
   245  }
   246  
   247  // NewAzureKeyVaultTestClient creates a *http.Client for Azure KeyVault test
   248  // recordings.
   249  func NewAzureKeyVaultTestClient(ctx context.Context, t *testing.T) (*http.Client, func()) {
   250  	client, cleanup, _ := NewRecordReplayClient(ctx, t, func(r *httpreplay.Recorder) {
   251  		r.RemoveQueryParams("se", "sig")
   252  		r.RemoveQueryParams("X-Ms-Date")
   253  		r.ClearHeaders("X-Ms-Date")
   254  		r.ClearHeaders("User-Agent") // includes the full Go version
   255  	})
   256  	return client, cleanup
   257  }
   258  
   259  // FakeGCPDefaultCredentials sets up the environment with fake GCP credentials.
   260  // It returns a cleanup function.
   261  func FakeGCPDefaultCredentials(t *testing.T) func() {
   262  	const envVar = "GOOGLE_APPLICATION_CREDENTIALS"
   263  	jsonCred := []byte(`{"client_id": "foo.apps.googleusercontent.com", "client_secret": "bar", "refresh_token": "baz", "type": "authorized_user"}`)
   264  	f, err := ioutil.TempFile("", "fake-gcp-creds")
   265  	if err != nil {
   266  		t.Fatal(err)
   267  	}
   268  	if err := ioutil.WriteFile(f.Name(), jsonCred, 0666); err != nil {
   269  		t.Fatal(err)
   270  	}
   271  	oldEnvVal := os.Getenv(envVar)
   272  	os.Setenv(envVar, f.Name())
   273  	return func() {
   274  		os.Remove(f.Name())
   275  		os.Setenv(envVar, oldEnvVal)
   276  	}
   277  }
   278  
   279  // newGCPRecordDialOptions return grpc.DialOptions that are to be appended to a
   280  // GRPC dial request. These options allow a recorder to intercept RPCs and save
   281  // RPCs to the file at filename, or read the RPCs from the file and return them.
   282  func newGCPRecordDialOptions(t *testing.T, filename string) (opts []grpc.DialOption, done func()) {
   283  	path := filepath.Join("testdata", filename)
   284  	os.MkdirAll(filepath.Dir(path), os.ModePerm)
   285  	t.Logf("Recording into golden file %s", path)
   286  	r, err := grpcreplay.NewRecorder(path, nil)
   287  	if err != nil {
   288  		t.Fatal(err)
   289  	}
   290  	opts = r.DialOptions()
   291  	done = func() {
   292  		if err := r.Close(); err != nil {
   293  			t.Errorf("unable to close recorder: %v", err)
   294  		}
   295  	}
   296  	return opts, done
   297  }
   298  
   299  // newGCPReplayer returns a Replayer for GCP gRPC connections, as well as a function
   300  // to call when done with the Replayer.
   301  func newGCPReplayer(t *testing.T, filename string) (*grpcreplay.Replayer, func()) {
   302  	path := filepath.Join("testdata", filename)
   303  	t.Logf("Replaying from golden file %s", path)
   304  	r, err := grpcreplay.NewReplayer(path, nil)
   305  	if err != nil {
   306  		t.Fatal(err)
   307  	}
   308  	done := func() {
   309  		if err := r.Close(); err != nil {
   310  			t.Errorf("unable to close recorder: %v", err)
   311  		}
   312  	}
   313  	return r, done
   314  }
   315  
   316  // HasDockerTestEnvironment returns true when either:
   317  // 1) Not on Github Actions.
   318  // 2) On Github's Linux environment, where Docker is available.
   319  func HasDockerTestEnvironment() bool {
   320  	s := os.Getenv("RUNNER_OS")
   321  	return s == "" || s == "Linux"
   322  }