package gcs import ( "fmt" "net/http" "sort" "testing" "time" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/stretchr/testify/assert" "github.com/trufflesecurity/trufflehog/v3/pkg/common" "github.com/trufflesecurity/trufflehog/v3/pkg/context" ) const ( publicBucket = "public-trufflehog-test-bucket" testProjectID = "trufflehog-testing" testAPIKey = "somekeys" testBucket = "test-bkt-th" testBucket2 = "test-bkt-th2" testBucket3 = "test-bkt-th3" testBucket4 = "test-bkt-th4" perfTestBucketGlob = "perf-test-bkt-th*" bucket1 = "bucket1" bucket2 = "bucket2" object1 = "object1" object2 = "object2" ) func TestNewGcsManager(t *testing.T) { ctx := context.Background() testCases := []struct { name string projID string opts []gcsManagerOption want *gcsManager wantErr bool }{ { name: "new gcs manager, no options", projID: testProjectID, want: &gcsManager{projectID: testProjectID, concurrency: defaultConcurrency, maxObjectSize: defaultMaxObjectSize}, }, { name: "new gcs manager, no project id", projID: "", wantErr: true, }, { name: "new gcs manager, without auth", projID: "", opts: []gcsManagerOption{withoutAuthentication()}, want: &gcsManager{ concurrency: defaultConcurrency, maxObjectSize: defaultMaxObjectSize, withoutAuth: true, }, }, { name: "new gcs manager, with api key", projID: testProjectID, opts: []gcsManagerOption{withAPIKey(ctx, testAPIKey)}, want: &gcsManager{projectID: testProjectID, concurrency: defaultConcurrency, maxObjectSize: defaultMaxObjectSize}, }, { name: "new gcs manager, with json service account", projID: testProjectID, opts: []gcsManagerOption{withJSONServiceAccount(ctx, []byte(`{ "type": "service_account", "project_id": "test-project"}`, ))}, want: &gcsManager{projectID: testProjectID, concurrency: defaultConcurrency, maxObjectSize: defaultMaxObjectSize}, }, { name: "new gcs manager, with default ADC account", projID: testProjectID, opts: []gcsManagerOption{withDefaultADC(ctx)}, want: &gcsManager{projectID: testProjectID, concurrency: defaultConcurrency, maxObjectSize: defaultMaxObjectSize}, }, { name: "new gcs manager, with bucket offsets", projID: testProjectID, opts: []gcsManagerOption{ withDefaultADC(ctx), withBucketOffsets(map[string]offsetInfo{ testBucket: {lastProcessedObject: "moar1.txt", isBucketProcessed: false}, testBucket2: {lastProcessedObject: "", isBucketProcessed: true}}), }, want: &gcsManager{ projectID: testProjectID, hasEnumerated: true, concurrency: defaultConcurrency, maxObjectSize: defaultMaxObjectSize, buckets: map[string]bucket{ testBucket: {name: testBucket, startOffset: "moar.txt", shouldInclude: true}, testBucket2: {name: testBucket2, startOffset: "", shouldInclude: false}, }, }, }, { name: "new gcs manager, with include buckets", projID: testProjectID, opts: []gcsManagerOption{ withDefaultADC(ctx), withIncludeBuckets([]string{bucket1, bucket2}), }, want: &gcsManager{ projectID: testProjectID, includeBuckets: map[string]struct{}{bucket1: {}, bucket2: {}}, concurrency: defaultConcurrency, maxObjectSize: defaultMaxObjectSize, }, }, { name: "new gcs manager, with include buckets and api key", projID: testProjectID, opts: []gcsManagerOption{withIncludeBuckets([]string{bucket1, bucket2}), withAPIKey(ctx, testAPIKey)}, want: &gcsManager{ projectID: testProjectID, includeBuckets: map[string]struct{}{bucket1: {}, bucket2: {}}, concurrency: defaultConcurrency, maxObjectSize: defaultMaxObjectSize, }, }, { name: "new gcs manager, with exclude buckets", projID: testProjectID, opts: []gcsManagerOption{withExcludeBuckets([]string{bucket1, bucket2}), withAPIKey(ctx, testAPIKey)}, want: &gcsManager{ projectID: testProjectID, excludeBuckets: map[string]struct{}{bucket1: {}, bucket2: {}}, concurrency: defaultConcurrency, maxObjectSize: defaultMaxObjectSize, }, }, { name: "new gcs manager, with exclude buckets and api key", projID: testProjectID, opts: []gcsManagerOption{withExcludeBuckets([]string{bucket1, bucket2}), withAPIKey(ctx, testAPIKey)}, want: &gcsManager{ projectID: testProjectID, excludeBuckets: map[string]struct{}{bucket1: {}, bucket2: {}}, concurrency: defaultConcurrency, maxObjectSize: defaultMaxObjectSize, }, }, { name: "new gcs manager, with include and exclude buckets", projID: testProjectID, opts: []gcsManagerOption{ withDefaultADC(ctx), withIncludeBuckets([]string{bucket1, bucket2}), withExcludeBuckets([]string{"bucket3", "bucket4"}), }, want: &gcsManager{ projectID: testProjectID, includeBuckets: map[string]struct{}{bucket1: {}, bucket2: {}}, concurrency: defaultConcurrency, maxObjectSize: defaultMaxObjectSize, }, }, { name: "new gcs manager, with include and exclude buckets and api key", projID: testProjectID, opts: []gcsManagerOption{ withDefaultADC(ctx), withIncludeBuckets([]string{bucket1, bucket2}), withExcludeBuckets([]string{"bucket3", "bucket4"}), withAPIKey(ctx, testAPIKey), }, want: &gcsManager{ projectID: testProjectID, includeBuckets: map[string]struct{}{bucket1: {}, bucket2: {}}, concurrency: defaultConcurrency, maxObjectSize: defaultMaxObjectSize, }, }, { name: "new gcs manager, with include objects, no bucket", projID: testProjectID, opts: []gcsManagerOption{withDefaultADC(ctx), withIncludeObjects([]string{"object1", "object2"})}, want: &gcsManager{ projectID: testProjectID, includeObjects: map[string]struct{}{object1: {}, object2: {}}, concurrency: defaultConcurrency, maxObjectSize: defaultMaxObjectSize, }, }, { name: "new gcs manager, with include objects, include buckets", projID: testProjectID, opts: []gcsManagerOption{ withDefaultADC(ctx), withIncludeObjects([]string{"object1", "object2"}), withIncludeBuckets([]string{bucket1, bucket2}), }, want: &gcsManager{ projectID: testProjectID, includeObjects: map[string]struct{}{object1: {}, object2: {}}, includeBuckets: map[string]struct{}{bucket1: {}, bucket2: {}}, concurrency: defaultConcurrency, maxObjectSize: defaultMaxObjectSize, }, }, { name: "new gcs manager, with include objects and api key, include buckets", projID: testProjectID, opts: []gcsManagerOption{ withIncludeObjects([]string{"object1", "object2"}), withIncludeBuckets([]string{bucket1, bucket2}), withAPIKey(ctx, testAPIKey), }, want: &gcsManager{ projectID: testProjectID, includeObjects: map[string]struct{}{object1: {}, object2: {}}, includeBuckets: map[string]struct{}{bucket1: {}, bucket2: {}}, concurrency: defaultConcurrency, maxObjectSize: defaultMaxObjectSize, }, }, { name: "new gcs manager, with exclude objects", projID: testProjectID, opts: []gcsManagerOption{withDefaultADC(ctx), withExcludeObjects([]string{"object1", "object2"})}, want: &gcsManager{ projectID: testProjectID, excludeObjects: map[string]struct{}{object1: {}, object2: {}}, concurrency: defaultConcurrency, maxObjectSize: defaultMaxObjectSize, }, }, { name: "new gcs manager, with exclude objects and api key", projID: testProjectID, opts: []gcsManagerOption{withExcludeObjects([]string{"object1", "object2"}), withAPIKey(ctx, testAPIKey)}, want: &gcsManager{ projectID: testProjectID, excludeObjects: map[string]struct{}{object1: {}, object2: {}}, concurrency: defaultConcurrency, maxObjectSize: defaultMaxObjectSize, }, }, { name: "new gcs manager, with include and exclude objects", projID: testProjectID, opts: []gcsManagerOption{ withDefaultADC(ctx), withIncludeObjects([]string{"object1", "object2"}), withExcludeObjects([]string{"object3", "object4"}), }, want: &gcsManager{ projectID: testProjectID, includeObjects: map[string]struct{}{object1: {}, object2: {}}, concurrency: defaultConcurrency, maxObjectSize: defaultMaxObjectSize, }, }, { name: "new gcs manager, with concurrency", projID: testProjectID, opts: []gcsManagerOption{withDefaultADC(ctx), withConcurrency(10)}, want: &gcsManager{ projectID: testProjectID, concurrency: 10, maxObjectSize: defaultMaxObjectSize, }, }, { name: "new gcs manager, default concurrency", projID: testProjectID, opts: []gcsManagerOption{withDefaultADC(ctx)}, want: &gcsManager{ projectID: testProjectID, concurrency: defaultConcurrency, maxObjectSize: defaultMaxObjectSize, }, }, { name: "new gcs manager, with negative concurrency", projID: testProjectID, opts: []gcsManagerOption{withDefaultADC(ctx), withConcurrency(-1)}, want: &gcsManager{ projectID: testProjectID, concurrency: defaultConcurrency, maxObjectSize: defaultMaxObjectSize, }, }, { name: "new gcs manager, with valid max object size", projID: testProjectID, opts: []gcsManagerOption{withDefaultADC(ctx), withMaxObjectSize(10)}, want: &gcsManager{ projectID: testProjectID, concurrency: defaultConcurrency, maxObjectSize: 10, }, }, { name: "new gcs manager, with negative max object size", projID: testProjectID, opts: []gcsManagerOption{withDefaultADC(ctx), withMaxObjectSize(-1)}, want: &gcsManager{ projectID: testProjectID, concurrency: defaultConcurrency, maxObjectSize: defaultMaxObjectSize, }, }, { name: "new gcs manager, max object size above limit", projID: testProjectID, opts: []gcsManagerOption{withDefaultADC(ctx), withMaxObjectSize(int64(2 * common.GB))}, want: &gcsManager{ projectID: testProjectID, concurrency: defaultConcurrency, maxObjectSize: defaultMaxObjectSize, }, }, { name: "new gcs manager, with max object size 0", projID: testProjectID, opts: []gcsManagerOption{withDefaultADC(ctx), withMaxObjectSize(0)}, want: &gcsManager{ projectID: testProjectID, concurrency: defaultConcurrency, maxObjectSize: defaultMaxObjectSize, }, }, { name: "new gcs manager, with HTTP client", projID: testProjectID, opts: []gcsManagerOption{withHTTPClient(ctx, &http.Client{})}, want: &gcsManager{projectID: testProjectID, concurrency: defaultConcurrency, maxObjectSize: defaultMaxObjectSize}, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { got, err := newGCSManager(tc.projID, tc.opts...) if (err != nil) != tc.wantErr { t.Errorf("newGCSManager() error = %v, wantErr %v", err, tc.wantErr) return } if err != nil { return } // The client should never be nil. if got.client == nil { t.Errorf("newGCSManager() client should not be nil") } diff := cmp.Diff(got, tc.want, cmp.AllowUnexported(gcsManager{}, bucket{}), cmpopts.IgnoreFields(gcsManager{}, "client", "workerPool", "buckets"), ) if diff != "" { t.Errorf("newGCSManager(%v, %v) got: %v, want: %v, diff: %v", tc.projID, tc.opts, got, tc.want, diff) } }) } } func TestGCSManagerStats(t *testing.T) { ctx := context.Background() testCases := []struct { name string projectID string opts []gcsManagerOption wantStats *attributes }{ { name: "enumerate, all buckets, with objects", projectID: testProjectID, opts: []gcsManagerOption{withDefaultADC(ctx), withExcludeBuckets([]string{perfTestBucketGlob, publicBucket})}, wantStats: &attributes{ numBuckets: 4, numObjects: 5, bucketObjects: map[string]uint64{testBucket: 2, testBucket2: 1, testBucket3: 1, testBucket4: 1}, }, }, { name: "list objects, with exclude objects", projectID: testProjectID, opts: []gcsManagerOption{ withDefaultADC(ctx), withExcludeObjects([]string{"aws1.txt", "moar2.txt"}), withExcludeBuckets([]string{perfTestBucketGlob, publicBucket}), }, wantStats: &attributes{ numBuckets: 4, numObjects: 3, bucketObjects: map[string]uint64{testBucket: 0, testBucket2: 1, testBucket3: 1, testBucket4: 1}, }, }, { name: "list objects, with include objects", projectID: testProjectID, opts: []gcsManagerOption{ withDefaultADC(ctx), withIncludeObjects([]string{"aws1.txt"}), withExcludeBuckets([]string{perfTestBucketGlob, publicBucket}), }, wantStats: &attributes{ numBuckets: 4, numObjects: 1, bucketObjects: map[string]uint64{testBucket: 1, testBucket2: 0, testBucket3: 0, testBucket4: 0}, }, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { gm, err := newGCSManager(tc.projectID, tc.opts...) if err != nil { t.Fatalf("newGCSManager() error = %v", err) } got, err := gm.Attributes(ctx) if err != nil { t.Errorf("Attributes() error = %v", err) } if diff := cmp.Diff(got, tc.wantStats, cmp.AllowUnexported(attributes{}), cmpopts.IgnoreFields(attributes{}, "mu")); diff != "" { t.Errorf("Attributes() got: %v, want: %v, diff: %v", got, tc.wantStats, diff) } }) } } func TestGCSManagerStats_Time(t *testing.T) { ctx := context.Background() opts := []gcsManagerOption{withDefaultADC(ctx)} gm, err := newGCSManager(testProjectID, opts...) if err != nil { t.Fatalf("newGCSManager() error = %v", err) } start := time.Now() var stats *attributes stats, _ = gm.Attributes(ctx) end := time.Since(start).Seconds() fmt.Printf("Time taken to get %d objects: %f seconds\n", stats.numObjects, end) } func TestGCSManagerListObjects(t *testing.T) { ctx := context.Background() testCases := []struct { name string projectID string opts []gcsManagerOption want []object wantNumBkt uint32 wantNumObj uint64 wantErr bool }{ { name: "list objects, all buckets, no objects", projectID: "other", opts: []gcsManagerOption{withDefaultADC(ctx)}, want: []object{}, wantErr: true, }, { name: "list objects, all buckets, with objects", projectID: testProjectID, opts: []gcsManagerOption{withDefaultADC(ctx), withExcludeBuckets([]string{perfTestBucketGlob, publicBucket})}, want: []object{ { name: "aws1.txt", bucket: testBucket, contentType: "text/plain", size: 150, link: "https://storage.googleapis.com/download/storage/v1/b/test-bkt-th/o/aws1.txt?generation=1677870994890594&alt=media", acl: []string{}, md5: "7a4df45f839cd594ac236b19524cec92", }, { name: "moar2.txt", bucket: testBucket, contentType: "text/plain", size: 12, link: "https://storage.googleapis.com/download/storage/v1/b/test-bkt-th/o/moar2.txt?generation=1677871000378542&alt=media", acl: []string{}, md5: "e59ff97941044f85df5297e1c302d260", }, { name: "aws3.txt", bucket: testBucket2, contentType: "text/plain", size: 150, link: "https://storage.googleapis.com/download/storage/v1/b/test-bkt-th2/o/aws3.txt?generation=1677871022489611&alt=media", acl: []string{}, md5: "26eb03378a0c8e99ee0cecf9155ace81", }, { name: "moar.txt", bucket: testBucket3, contentType: "text/plain", size: 6, link: "https://storage.googleapis.com/download/storage/v1/b/test-bkt-th3/o/moar.txt?generation=1677871042896804&alt=media", acl: []string{}, md5: "09f7e02f1290be211da707a266f153b3", }, { name: "AMAZON_FASHION_5.json", bucket: testBucket4, contentType: "application/json", size: 1413469, link: "https://storage.googleapis.com/download/storage/v1/b/test-bkt-th4/o/AMAZON_FASHION_5.json?generation=1677871063457469&alt=media", acl: []string{}, md5: "5a1d8f483aa34695256379050d42a2b7", }, }, wantNumBkt: 4, wantNumObj: 5, }, { name: "list objects, include buckets, with objects", projectID: testProjectID, opts: []gcsManagerOption{withDefaultADC(ctx), withIncludeBuckets([]string{testBucket})}, want: []object{ { name: "aws1.txt", bucket: testBucket, contentType: "text/plain", size: 150, link: "https://storage.googleapis.com/download/storage/v1/b/test-bkt-th/o/aws1.txt?generation=1677870994890594&alt=media", acl: []string{}, md5: "7a4df45f839cd594ac236b19524cec92", }, { name: "moar2.txt", bucket: testBucket, contentType: "text/plain", size: 12, link: "https://storage.googleapis.com/download/storage/v1/b/test-bkt-th/o/moar2.txt?generation=1677871000378542&alt=media", acl: []string{}, md5: "e59ff97941044f85df5297e1c302d260", }, }, wantNumBkt: 1, wantNumObj: 2, }, { name: "list objects, exclude buckets, with objects", projectID: testProjectID, opts: []gcsManagerOption{withDefaultADC(ctx), withExcludeBuckets([]string{testBucket, testBucket2, perfTestBucketGlob, publicBucket})}, want: []object{ { name: "moar.txt", bucket: testBucket3, contentType: "text/plain", size: 6, link: "https://storage.googleapis.com/download/storage/v1/b/test-bkt-th3/o/moar.txt?generation=1677871042896804&alt=media", acl: []string{}, md5: "09f7e02f1290be211da707a266f153b3", }, { name: "AMAZON_FASHION_5.json", bucket: testBucket4, contentType: "application/json", size: 1413469, link: "https://storage.googleapis.com/download/storage/v1/b/test-bkt-th4/o/AMAZON_FASHION_5.json?generation=1677871063457469&alt=media", acl: []string{}, md5: "5a1d8f483aa34695256379050d42a2b7", }, }, wantNumBkt: 2, wantNumObj: 2, }, { name: "list objects, with exclude objects", projectID: testProjectID, opts: []gcsManagerOption{ withDefaultADC(ctx), withExcludeObjects([]string{"aws1.txt", "moar2.txt"}), withExcludeBuckets([]string{perfTestBucketGlob, publicBucket}), }, want: []object{ { name: "aws3.txt", bucket: testBucket2, contentType: "text/plain", size: 150, link: "https://storage.googleapis.com/download/storage/v1/b/test-bkt-th2/o/aws3.txt?generation=1677871022489611&alt=media", acl: []string{}, md5: "26eb03378a0c8e99ee0cecf9155ace81", }, { name: "moar.txt", bucket: testBucket3, contentType: "text/plain", size: 6, link: "https://storage.googleapis.com/download/storage/v1/b/test-bkt-th3/o/moar.txt?generation=1677871042896804&alt=media", acl: []string{}, md5: "09f7e02f1290be211da707a266f153b3", }, { name: "AMAZON_FASHION_5.json", bucket: testBucket4, contentType: "application/json", size: 1413469, link: "https://storage.googleapis.com/download/storage/v1/b/test-bkt-th4/o/AMAZON_FASHION_5.json?generation=1677871063457469&alt=media", acl: []string{}, md5: "5a1d8f483aa34695256379050d42a2b7", }, }, wantNumBkt: 4, // We still list objects in all buckets. wantNumObj: 3, }, { name: "list objects, with include objects, include bucket", projectID: testProjectID, opts: []gcsManagerOption{ withDefaultADC(ctx), withIncludeObjects([]string{"aws1.txt"}), withIncludeBuckets([]string{testBucket}), }, want: []object{ { name: "aws1.txt", bucket: testBucket, contentType: "text/plain", size: 150, link: "https://storage.googleapis.com/download/storage/v1/b/test-bkt-th/o/aws1.txt?generation=1677870994890594&alt=media", acl: []string{}, md5: "7a4df45f839cd594ac236b19524cec92", }, }, wantNumBkt: 1, wantNumObj: 1, }, { name: "list objects, with include objects", projectID: testProjectID, opts: []gcsManagerOption{ withDefaultADC(ctx), withIncludeObjects([]string{"aws1.txt"}), withExcludeBuckets([]string{perfTestBucketGlob, publicBucket}), }, want: []object{ { name: "aws1.txt", bucket: testBucket, contentType: "text/plain", size: 150, link: "https://storage.googleapis.com/download/storage/v1/b/test-bkt-th/o/aws1.txt?generation=1677870994890594&alt=media", acl: []string{}, md5: "7a4df45f839cd594ac236b19524cec92", }, }, wantNumBkt: 4, wantNumObj: 1, }, { name: "list objects, with include bucket, exclude object", projectID: testProjectID, opts: []gcsManagerOption{ withDefaultADC(ctx), withIncludeBuckets([]string{testBucket}), withExcludeObjects([]string{"aws1.txt"}), withExcludeBuckets([]string{perfTestBucketGlob, publicBucket}), }, want: []object{ { name: "moar2.txt", bucket: testBucket, contentType: "text/plain", size: 12, link: "https://storage.googleapis.com/download/storage/v1/b/test-bkt-th/o/moar2.txt?generation=1677871000378542&alt=media", acl: []string{}, md5: "e59ff97941044f85df5297e1c302d260", }, }, wantNumBkt: 1, wantNumObj: 1, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { mgr, err := newGCSManager(tc.projectID, tc.opts...) assert.Nil(t, err) got, err := mgr.ListObjects(ctx) if (err != nil) != tc.wantErr { t.Errorf("GCSManager.ListObjects() error = %v, wantErr %v", err, tc.wantErr) return } // If we expect an error, we're done. // Short-circuit the rest of the test. if err != nil { return } doneCh := make(chan struct{}) defer close(doneCh) go func() { defer func() { doneCh <- struct{}{} }() res := make([]object, 0, len(tc.want)) for obj := range got { res = append(res, obj.(object)) } if len(res) != len(tc.want) { t.Errorf("gcsManager.ListObjects() got: %v, want: %v", res, tc.want) } // Test the bucket and object counts. assert.Equal(t, tc.wantNumBkt, mgr.numBuckets) assert.Equal(t, tc.wantNumObj, mgr.numObjects) // Sort the objects by name to make the test deterministic. // This is necessary because we list bucket objects concurrently. sort.Slice(res, func(i, j int) bool { return res[i].name < res[j].name }) sort.Slice(tc.want, func(i, j int) bool { return tc.want[i].name < tc.want[j].name }) // Test the objects are equal. if diff := cmp.Diff(res, tc.want, cmp.AllowUnexported(object{}), cmpopts.IgnoreFields(object{}, "Reader", "createdAt", "updatedAt")); diff != "" { t.Errorf("gcsManager.ListObjects() mismatch (-want +got):\n%s", diff) } }() <-doneCh }) } } func TestGCSManagerListObjects_Resuming(t *testing.T) { ctx := context.Background() testCases := []struct { name string projectID string opts []gcsManagerOption want []object wantNumBkt uint32 wantNumObj uint64 wantErr bool }{ { name: "list objects, testBucket, resume from moar2.txt", projectID: testProjectID, opts: []gcsManagerOption{ withDefaultADC(ctx), withIncludeBuckets([]string{testBucket}), withBucketOffsets(map[string]offsetInfo{testBucket: {lastProcessedObject: "moar2.txt"}}), }, want: []object{ { name: "moar2.txt", bucket: testBucket, contentType: "text/plain", size: 12, link: "https://storage.googleapis.com/download/storage/v1/b/test-bkt-th/o/moar2.txt?generation=1677871000378542&alt=media", acl: []string{}, md5: "e59ff97941044f85df5297e1c302d260", }, }, wantNumBkt: 1, wantNumObj: 1, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { mgr, err := newGCSManager(tc.projectID, tc.opts...) assert.Nil(t, err) got, err := mgr.ListObjects(ctx) if (err != nil) != tc.wantErr { t.Errorf("GCSManager.ListObjects() error = %v, wantErr %v", err, tc.wantErr) return } // If we expect an error, we're done. // Short-circuit the rest of the test. if err != nil { return } doneCh := make(chan struct{}) defer close(doneCh) go func() { defer func() { doneCh <- struct{}{} }() res := make([]object, 0, len(tc.want)) for obj := range got { res = append(res, obj.(object)) } if len(res) != len(tc.want) { t.Errorf("gcsManager.ListObjects() got: %v, want: %v", res, tc.want) } // Test the bucket and object counts. assert.Equal(t, tc.wantNumBkt, mgr.numBuckets) assert.Equal(t, tc.wantNumObj, mgr.numObjects) // Sort the objects by name to make the test deterministic. // This is necessary because we list bucket objects concurrently. sort.Slice(res, func(i, j int) bool { return res[i].name < res[j].name }) sort.Slice(tc.want, func(i, j int) bool { return tc.want[i].name < tc.want[j].name }) // Test the objects are equal. if diff := cmp.Diff(res, tc.want, cmp.AllowUnexported(object{}), cmpopts.IgnoreFields(object{}, "Reader", "createdAt", "updatedAt")); diff != "" { t.Errorf("gcsManager.ListObjects() mismatch (-want +got):\n%s", diff) } }() <-doneCh }) } } func Test_isObjectTypeValid(t *testing.T) { testCases := []struct { name string objName string want bool }{ { name: "valid object type, text", objName: "aws1.txt", want: true, }, { name: "valid object type, zip", objName: "aws1.zip", want: true, }, { name: "invalid object type", objName: "video.mp4", want: false, }, } ctx := context.Background() for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { got := isObjectTypeValid(ctx, tc.objName) assert.Equal(t, tc.want, got) }) } } func Test_isObjectSizeValid(t *testing.T) { testCases := []struct { name string objSize int64 want bool }{ { name: "valid object size, 150 bytes", objSize: 150, want: true, }, { name: "valid object size, 1 MB", objSize: int64(1 * common.MB), want: true, }, { name: "valid object size, 49 MB", objSize: int64(49 * common.MB), want: true, }, { name: "invalid object size, 1 GB", objSize: int64(1 * common.GB), want: false, }, { name: "invalid object size, 2 GB", objSize: int64(2 * common.GB), want: false, }, { name: "invalid object size, empty object", objSize: 0, want: false, }, { name: "invalid object size, negative object size", objSize: -1, want: false, }, } ctx := context.Background() for _, tc := range testCases { g := &gcsManager{ maxObjectSize: maxObjectSizeLimit, } t.Run(tc.name, func(t *testing.T) { got := g.isObjectSizeValid(ctx, tc.objSize) assert.Equal(t, tc.want, got) }) } }