// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 package loopback import ( "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/storagebuckets" plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" "google.golang.org/grpc/codes" ) // Method is used to determine if an error should be returned to a specific storage plugin method. type Method uint8 const ( // Any will return an error for any of the method defined in the storage plugin. Any Method = iota // Will return an error for the OnCreateStorageBucket method OnCreateStorageBucket // Will return an error for the OnUpdateStorageBucket method OnUpdateStorageBucket // Will return an error for the OnDeleteStorageBucket method OnDeleteStorageBucket // Will return an error for the ValidatePermissions method ValidatePermissions // Will return an error for the HeadObject method HeadObject // Will return an error for the GetObject method GetObject // Will return an error for the PutObject method PutObject // Will return an error for the DeleteObjects method DeleteObjects ) const ( defaultStreamChunkSize = 8 ) // PluginMockError is used to mock an error when interacting with an external object store. type PluginMockError struct { BucketName string BucketPrefix string ObjectKey string ErrMsg string ErrCode codes.Code ErrMethod Method } // match compares the given values from the parameters to the values provided in the mocked error. // The bucket and key parameter values should be provided from the plugin request. The method // value should be based on the plugin method that is calling this function. // // When match returns false, the mocked error should not be used for the plugin response. // When match returns true, the mocked error should be used for the plugin response. func (e PluginMockError) match(bucket *storagebuckets.StorageBucket, key string, method Method) bool { // if the mocked error object key does not match the request's object key, return false. // the object key comparison is ignored when the given key is empty because the following // plugin methods do not provide key values: onCreateStorageBucket, onUpdateStorageBucket, // onDeleteStorageBucket if key != "" && e.ObjectKey != key { return false } // if the mocked error bucket name does not match the request's bucket name, return false. if e.BucketName != bucket.BucketName { return false } // if the request has a bucket prefix and it does not match the mocked error bucket prefix, return false. if bucket.BucketPrefix != "" && e.BucketPrefix != bucket.BucketPrefix { return false } // if the mocked error method is set to Any, return true. This means that the mocked error response // will be utilized by all the plugin methods. if e.ErrMethod == Any { return true } // if the mocked error method does match the given method, return false. if e.ErrMethod != method { return false } // all checks comparison checks passed, return true. return true } // PluginMockPutObjectResponse is used to mock a response when calling putObject. type PluginMockPutObjectResponse struct { BucketName string BucketPrefix string ObjectKey string Response *plgpb.PutObjectResponse } // match compares the given values from the parameters to the values provided in the mocked response. // The bucket and key parameter values should be provided from the plugin request. // // When match returns false, the mocked response should not be used for the plugin response. // When match returns true, the mocked response should be used for the plugin response. func (r PluginMockPutObjectResponse) match(bucket *storagebuckets.StorageBucket, key string) bool { // if the mocked response object key does not match the request's object key, return false. if r.ObjectKey != key { return false } // if the mocked response bucket name does not match the request's bucket name, return false. if r.BucketName != bucket.BucketName { return false } // if the request has a bucket prefix and it does not match the mocked response bucket prefix, return false. if bucket.BucketPrefix != "" && r.BucketPrefix != bucket.BucketPrefix { return false } // all checks comparison checks passed, return true. return true } type TestOption func(*TestOptions) error type TestOptions struct { withMockBuckets map[BucketName]Bucket withMockError []PluginMockError withMockPutObjectResponse []PluginMockPutObjectResponse withChunkSize int } // getTestOpts - iterate the inbound Options and return a struct func getTestOpts(opt ...TestOption) (TestOptions, error) { opts := getDefaultTestOptions() for _, o := range opt { if err := o(&opts); err != nil { return opts, err } } return opts, nil } func getDefaultTestOptions() TestOptions { return TestOptions{ withChunkSize: 8, } } // WithMockBuckets provides an option to create mocked external object store buckets. func WithMockBuckets(buckets map[BucketName]Bucket) TestOption { return func(o *TestOptions) error { o.withMockBuckets = buckets return nil } } // WithMockError provides an option to mock an error when interacting with an external object store. func WithMockError(err PluginMockError) TestOption { return func(o *TestOptions) error { o.withMockError = append(o.withMockError, err) return nil } } func WithMockPutObjectResponse(response PluginMockPutObjectResponse) TestOption { return func(o *TestOptions) error { o.withMockPutObjectResponse = append(o.withMockPutObjectResponse, response) return nil } } // WithChunkSize provides an option to set the chunkSize used for grpc streams. func WithChunkSize(size int) TestOption { return func(o *TestOptions) error { o.withChunkSize = size return nil } }