github.com/fastly/cli@v1.7.2-0.20240304164155-9d0f1d77c3bf/pkg/commands/logging/s3/s3_test.go (about)

     1  package s3_test
     2  
     3  import (
     4  	"bytes"
     5  	"testing"
     6  
     7  	"github.com/fastly/go-fastly/v9/fastly"
     8  
     9  	"github.com/fastly/cli/pkg/argparser"
    10  	"github.com/fastly/cli/pkg/commands/logging/s3"
    11  	"github.com/fastly/cli/pkg/config"
    12  	"github.com/fastly/cli/pkg/errors"
    13  	"github.com/fastly/cli/pkg/global"
    14  	"github.com/fastly/cli/pkg/manifest"
    15  	"github.com/fastly/cli/pkg/mock"
    16  	"github.com/fastly/cli/pkg/testutil"
    17  )
    18  
    19  func TestCreateS3Input(t *testing.T) {
    20  	red := fastly.S3RedundancyStandard
    21  	sse := fastly.S3ServerSideEncryptionAES
    22  	for _, testcase := range []struct {
    23  		name      string
    24  		cmd       *s3.CreateCommand
    25  		want      *fastly.CreateS3Input
    26  		wantError string
    27  	}{
    28  		{
    29  			name: "required values set flag serviceID using access credentials",
    30  			cmd:  createCommandRequired(),
    31  			want: &fastly.CreateS3Input{
    32  				ServiceID:      "123",
    33  				ServiceVersion: 4,
    34  				Name:           fastly.ToPointer("log"),
    35  				BucketName:     fastly.ToPointer("bucket"),
    36  				AccessKey:      fastly.ToPointer("access"),
    37  				SecretKey:      fastly.ToPointer("secret"),
    38  			},
    39  		},
    40  		{
    41  			name: "required values set flag serviceID using IAM role",
    42  			cmd:  createCommandRequiredIAMRole(),
    43  			want: &fastly.CreateS3Input{
    44  				ServiceID:      "123",
    45  				ServiceVersion: 4,
    46  				Name:           fastly.ToPointer("log"),
    47  				BucketName:     fastly.ToPointer("bucket"),
    48  				IAMRole:        fastly.ToPointer("arn:aws:iam::123456789012:role/S3Access"),
    49  			},
    50  		},
    51  		{
    52  			name: "all values set flag serviceID",
    53  			cmd:  createCommandAll(),
    54  			want: &fastly.CreateS3Input{
    55  				ServiceID:                    "123",
    56  				ServiceVersion:               4,
    57  				Name:                         fastly.ToPointer("logs"),
    58  				BucketName:                   fastly.ToPointer("bucket"),
    59  				Domain:                       fastly.ToPointer("domain"),
    60  				AccessKey:                    fastly.ToPointer("access"),
    61  				SecretKey:                    fastly.ToPointer("secret"),
    62  				Path:                         fastly.ToPointer("path"),
    63  				Period:                       fastly.ToPointer(3600),
    64  				Format:                       fastly.ToPointer(`%h %l %u %t "%r" %>s %b`),
    65  				MessageType:                  fastly.ToPointer("classic"),
    66  				FormatVersion:                fastly.ToPointer(2),
    67  				ResponseCondition:            fastly.ToPointer("Prevent default logging"),
    68  				TimestampFormat:              fastly.ToPointer("%Y-%m-%dT%H:%M:%S.000"),
    69  				Redundancy:                   &red,
    70  				Placement:                    fastly.ToPointer("none"),
    71  				PublicKey:                    fastly.ToPointer(pgpPublicKey()),
    72  				ServerSideEncryptionKMSKeyID: fastly.ToPointer("kmskey"),
    73  				ServerSideEncryption:         &sse,
    74  				CompressionCodec:             fastly.ToPointer("zstd"),
    75  			},
    76  		},
    77  		{
    78  			name:      "error missing serviceID",
    79  			cmd:       createCommandMissingServiceID(),
    80  			want:      nil,
    81  			wantError: errors.ErrNoServiceID.Error(),
    82  		},
    83  	} {
    84  		t.Run(testcase.name, func(t *testing.T) {
    85  			var bs []byte
    86  			out := bytes.NewBuffer(bs)
    87  			verboseMode := true
    88  
    89  			serviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{
    90  				AutoCloneFlag:      testcase.cmd.AutoClone,
    91  				APIClient:          testcase.cmd.Globals.APIClient,
    92  				Manifest:           testcase.cmd.Manifest,
    93  				Out:                out,
    94  				ServiceVersionFlag: testcase.cmd.ServiceVersion,
    95  				VerboseMode:        verboseMode,
    96  			})
    97  
    98  			switch {
    99  			case err != nil && testcase.wantError == "":
   100  				t.Fatalf("unexpected error getting service details: %v", err)
   101  				return
   102  			case err != nil && testcase.wantError != "":
   103  				testutil.AssertErrorContains(t, err, testcase.wantError)
   104  				return
   105  			case err == nil && testcase.wantError != "":
   106  				t.Fatalf("expected error, have nil (service details: %s, %d)", serviceID, serviceVersion.Number)
   107  			case err == nil && testcase.wantError == "":
   108  				have, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))
   109  				testutil.AssertErrorContains(t, err, testcase.wantError)
   110  				testutil.AssertEqual(t, testcase.want, have)
   111  			}
   112  		})
   113  	}
   114  }
   115  
   116  func TestUpdateS3Input(t *testing.T) {
   117  	scenarios := []struct {
   118  		name      string
   119  		cmd       *s3.UpdateCommand
   120  		api       mock.API
   121  		want      *fastly.UpdateS3Input
   122  		wantError string
   123  	}{
   124  		{
   125  			name: "no updates",
   126  			cmd:  updateCommandNoUpdates(),
   127  			api: mock.API{
   128  				ListVersionsFn: testutil.ListVersions,
   129  				CloneVersionFn: testutil.CloneVersionResult(4),
   130  				GetS3Fn:        getS3OK,
   131  			},
   132  			want: &fastly.UpdateS3Input{
   133  				ServiceID:      "123",
   134  				ServiceVersion: 4,
   135  				Name:           "log",
   136  			},
   137  		},
   138  		{
   139  			name: "all values set flag serviceID",
   140  			cmd:  updateCommandAll(),
   141  			api: mock.API{
   142  				ListVersionsFn: testutil.ListVersions,
   143  				CloneVersionFn: testutil.CloneVersionResult(4),
   144  				GetS3Fn:        getS3OK,
   145  			},
   146  			want: &fastly.UpdateS3Input{
   147  				ServiceID:                    "123",
   148  				ServiceVersion:               4,
   149  				Name:                         "log",
   150  				NewName:                      fastly.ToPointer("new1"),
   151  				BucketName:                   fastly.ToPointer("new2"),
   152  				AccessKey:                    fastly.ToPointer("new3"),
   153  				SecretKey:                    fastly.ToPointer("new4"),
   154  				IAMRole:                      fastly.ToPointer(""),
   155  				Domain:                       fastly.ToPointer("new5"),
   156  				Path:                         fastly.ToPointer("new6"),
   157  				Period:                       fastly.ToPointer(3601),
   158  				GzipLevel:                    fastly.ToPointer(0),
   159  				Format:                       fastly.ToPointer("new7"),
   160  				FormatVersion:                fastly.ToPointer(3),
   161  				MessageType:                  fastly.ToPointer("new8"),
   162  				ResponseCondition:            fastly.ToPointer("new9"),
   163  				TimestampFormat:              fastly.ToPointer("new10"),
   164  				Placement:                    fastly.ToPointer("new11"),
   165  				Redundancy:                   fastly.ToPointer(fastly.S3RedundancyReduced),
   166  				ServerSideEncryption:         fastly.ToPointer(fastly.S3ServerSideEncryptionKMS),
   167  				ServerSideEncryptionKMSKeyID: fastly.ToPointer("new12"),
   168  				PublicKey:                    fastly.ToPointer("new13"),
   169  				CompressionCodec:             fastly.ToPointer("new14"),
   170  			},
   171  		},
   172  		{
   173  			name:      "error missing serviceID",
   174  			cmd:       updateCommandMissingServiceID(),
   175  			want:      nil,
   176  			wantError: errors.ErrNoServiceID.Error(),
   177  		},
   178  	}
   179  	for testcaseIdx := range scenarios {
   180  		testcase := &scenarios[testcaseIdx]
   181  		t.Run(testcase.name, func(t *testing.T) {
   182  			testcase.cmd.Globals.APIClient = testcase.api
   183  
   184  			var bs []byte
   185  			out := bytes.NewBuffer(bs)
   186  			verboseMode := true
   187  
   188  			serviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{
   189  				AutoCloneFlag:      testcase.cmd.AutoClone,
   190  				APIClient:          testcase.api,
   191  				Manifest:           testcase.cmd.Manifest,
   192  				Out:                out,
   193  				ServiceVersionFlag: testcase.cmd.ServiceVersion,
   194  				VerboseMode:        verboseMode,
   195  			})
   196  
   197  			switch {
   198  			case err != nil && testcase.wantError == "":
   199  				t.Fatalf("unexpected error getting service details: %v", err)
   200  				return
   201  			case err != nil && testcase.wantError != "":
   202  				testutil.AssertErrorContains(t, err, testcase.wantError)
   203  				return
   204  			case err == nil && testcase.wantError != "":
   205  				t.Fatalf("expected error, have nil (service details: %s, %d)", serviceID, serviceVersion.Number)
   206  			case err == nil && testcase.wantError == "":
   207  				have, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))
   208  				testutil.AssertErrorContains(t, err, testcase.wantError)
   209  				testutil.AssertEqual(t, testcase.want, have)
   210  			}
   211  		})
   212  	}
   213  }
   214  
   215  func createCommandRequired() *s3.CreateCommand {
   216  	var b bytes.Buffer
   217  
   218  	g := global.Data{
   219  		Config: config.File{},
   220  		Env:    config.Environment{},
   221  		Output: &b,
   222  	}
   223  	g.APIClient, _ = mock.APIClient(mock.API{
   224  		ListVersionsFn: testutil.ListVersions,
   225  		CloneVersionFn: testutil.CloneVersionResult(4),
   226  	})("token", "endpoint", false)
   227  
   228  	return &s3.CreateCommand{
   229  		Base: argparser.Base{
   230  			Globals: &g,
   231  		},
   232  		Manifest: manifest.Data{
   233  			Flag: manifest.Flag{
   234  				ServiceID: "123",
   235  			},
   236  		},
   237  		ServiceVersion: argparser.OptionalServiceVersion{
   238  			OptionalString: argparser.OptionalString{Value: "1"},
   239  		},
   240  		AutoClone: argparser.OptionalAutoClone{
   241  			OptionalBool: argparser.OptionalBool{
   242  				Optional: argparser.Optional{
   243  					WasSet: true,
   244  				},
   245  				Value: true,
   246  			},
   247  		},
   248  		EndpointName: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: "log"},
   249  		BucketName:   argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: "bucket"},
   250  		AccessKey:    argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: "access"},
   251  		SecretKey:    argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: "secret"},
   252  	}
   253  }
   254  
   255  func createCommandRequiredIAMRole() *s3.CreateCommand {
   256  	var b bytes.Buffer
   257  
   258  	g := global.Data{
   259  		Config: config.File{},
   260  		Env:    config.Environment{},
   261  		Output: &b,
   262  	}
   263  	g.APIClient, _ = mock.APIClient(mock.API{
   264  		ListVersionsFn: testutil.ListVersions,
   265  		CloneVersionFn: testutil.CloneVersionResult(4),
   266  	})("token", "endpoint", false)
   267  
   268  	return &s3.CreateCommand{
   269  		Base: argparser.Base{
   270  			Globals: &g,
   271  		},
   272  		Manifest: manifest.Data{
   273  			Flag: manifest.Flag{
   274  				ServiceID: "123",
   275  			},
   276  		},
   277  		ServiceVersion: argparser.OptionalServiceVersion{
   278  			OptionalString: argparser.OptionalString{Value: "1"},
   279  		},
   280  		AutoClone: argparser.OptionalAutoClone{
   281  			OptionalBool: argparser.OptionalBool{
   282  				Optional: argparser.Optional{
   283  					WasSet: true,
   284  				},
   285  				Value: true,
   286  			},
   287  		},
   288  		EndpointName: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: "log"},
   289  		BucketName:   argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: "bucket"},
   290  		IAMRole:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: "arn:aws:iam::123456789012:role/S3Access"},
   291  	}
   292  }
   293  
   294  func createCommandAll() *s3.CreateCommand {
   295  	var b bytes.Buffer
   296  
   297  	g := global.Data{
   298  		Config: config.File{},
   299  		Env:    config.Environment{},
   300  		Output: &b,
   301  	}
   302  	g.APIClient, _ = mock.APIClient(mock.API{
   303  		ListVersionsFn: testutil.ListVersions,
   304  		CloneVersionFn: testutil.CloneVersionResult(4),
   305  	})("token", "endpoint", false)
   306  
   307  	return &s3.CreateCommand{
   308  		Base: argparser.Base{
   309  			Globals: &g,
   310  		},
   311  		Manifest: manifest.Data{
   312  			Flag: manifest.Flag{
   313  				ServiceID: "123",
   314  			},
   315  		},
   316  		ServiceVersion: argparser.OptionalServiceVersion{
   317  			OptionalString: argparser.OptionalString{Value: "1"},
   318  		},
   319  		AutoClone: argparser.OptionalAutoClone{
   320  			OptionalBool: argparser.OptionalBool{
   321  				Optional: argparser.Optional{
   322  					WasSet: true,
   323  				},
   324  				Value: true,
   325  			},
   326  		},
   327  		EndpointName:                 argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: "logs"},
   328  		BucketName:                   argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: "bucket"},
   329  		AccessKey:                    argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: "access"},
   330  		SecretKey:                    argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: "secret"},
   331  		Domain:                       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: "domain"},
   332  		Path:                         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: "path"},
   333  		Period:                       argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3600},
   334  		Format:                       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: `%h %l %u %t "%r" %>s %b`},
   335  		FormatVersion:                argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 2},
   336  		MessageType:                  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: "classic"},
   337  		ResponseCondition:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: "Prevent default logging"},
   338  		TimestampFormat:              argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: "%Y-%m-%dT%H:%M:%S.000"},
   339  		Placement:                    argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: "none"},
   340  		PublicKey:                    argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: pgpPublicKey()},
   341  		Redundancy:                   argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: string(fastly.S3RedundancyStandard)},
   342  		ServerSideEncryption:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: string(fastly.S3ServerSideEncryptionAES)},
   343  		ServerSideEncryptionKMSKeyID: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: "kmskey"},
   344  		CompressionCodec:             argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: "zstd"},
   345  	}
   346  }
   347  
   348  func createCommandMissingServiceID() *s3.CreateCommand {
   349  	res := createCommandAll()
   350  	res.Manifest = manifest.Data{}
   351  	return res
   352  }
   353  
   354  func updateCommandNoUpdates() *s3.UpdateCommand {
   355  	var b bytes.Buffer
   356  
   357  	g := global.Data{
   358  		Config: config.File{},
   359  		Env:    config.Environment{},
   360  		Output: &b,
   361  	}
   362  
   363  	return &s3.UpdateCommand{
   364  		Base: argparser.Base{
   365  			Globals: &g,
   366  		},
   367  		Manifest: manifest.Data{
   368  			Flag: manifest.Flag{
   369  				ServiceID: "123",
   370  			},
   371  		},
   372  		EndpointName: "log",
   373  		ServiceVersion: argparser.OptionalServiceVersion{
   374  			OptionalString: argparser.OptionalString{Value: "1"},
   375  		},
   376  		AutoClone: argparser.OptionalAutoClone{
   377  			OptionalBool: argparser.OptionalBool{
   378  				Optional: argparser.Optional{
   379  					WasSet: true,
   380  				},
   381  				Value: true,
   382  			},
   383  		},
   384  	}
   385  }
   386  
   387  func updateCommandAll() *s3.UpdateCommand {
   388  	var b bytes.Buffer
   389  
   390  	g := global.Data{
   391  		Config: config.File{},
   392  		Env:    config.Environment{},
   393  		Output: &b,
   394  	}
   395  
   396  	return &s3.UpdateCommand{
   397  		Base: argparser.Base{
   398  			Globals: &g,
   399  		},
   400  		Manifest: manifest.Data{
   401  			Flag: manifest.Flag{
   402  				ServiceID: "123",
   403  			},
   404  		},
   405  		EndpointName: "log",
   406  		ServiceVersion: argparser.OptionalServiceVersion{
   407  			OptionalString: argparser.OptionalString{Value: "1"},
   408  		},
   409  		AutoClone: argparser.OptionalAutoClone{
   410  			OptionalBool: argparser.OptionalBool{
   411  				Optional: argparser.Optional{
   412  					WasSet: true,
   413  				},
   414  				Value: true,
   415  			},
   416  		},
   417  		NewName:                      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: "new1"},
   418  		BucketName:                   argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: "new2"},
   419  		AccessKey:                    argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: "new3"},
   420  		SecretKey:                    argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: "new4"},
   421  		IAMRole:                      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: ""},
   422  		Domain:                       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: "new5"},
   423  		Path:                         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: "new6"},
   424  		Period:                       argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3601},
   425  		GzipLevel:                    argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 0},
   426  		Format:                       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: "new7"},
   427  		FormatVersion:                argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3},
   428  		MessageType:                  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: "new8"},
   429  		ResponseCondition:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: "new9"},
   430  		TimestampFormat:              argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: "new10"},
   431  		Placement:                    argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: "new11"},
   432  		Redundancy:                   argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: string(fastly.S3RedundancyReduced)},
   433  		ServerSideEncryption:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: string(fastly.S3ServerSideEncryptionKMS)},
   434  		ServerSideEncryptionKMSKeyID: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: "new12"},
   435  		PublicKey:                    argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: "new13"},
   436  		CompressionCodec:             argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: "new14"},
   437  	}
   438  }
   439  
   440  func updateCommandMissingServiceID() *s3.UpdateCommand {
   441  	res := updateCommandAll()
   442  	res.Manifest = manifest.Data{}
   443  	return res
   444  }
   445  
   446  func TestValidateRedundancy(t *testing.T) {
   447  	for _, testcase := range []struct {
   448  		value     string
   449  		want      fastly.S3Redundancy
   450  		wantError string
   451  	}{
   452  		{value: "standard", want: fastly.S3RedundancyStandard},
   453  		{value: "standard_ia", want: fastly.S3RedundancyStandardIA},
   454  		{value: "onezone_ia", want: fastly.S3RedundancyOneZoneIA},
   455  		{value: "glacier", want: fastly.S3RedundancyGlacierFlexibleRetrieval},
   456  		{value: "glacier_ir", want: fastly.S3RedundancyGlacierInstantRetrieval},
   457  		{value: "deep_archive", want: fastly.S3RedundancyGlacierDeepArchive},
   458  		{value: "reduced_redundancy", want: fastly.S3RedundancyReduced},
   459  		{value: "bad_value", wantError: "unknown redundancy"},
   460  	} {
   461  		t.Run(testcase.value, func(t *testing.T) {
   462  			have, err := s3.ValidateRedundancy(testcase.value)
   463  
   464  			switch {
   465  			case err != nil && testcase.wantError == "":
   466  				t.Fatalf("unexpected error ValidateRedundancy: %v", err)
   467  				return
   468  			case err != nil && testcase.wantError != "":
   469  				testutil.AssertErrorContains(t, err, testcase.wantError)
   470  				return
   471  			case err == nil && testcase.wantError != "":
   472  				t.Fatalf("expected error, have nil (redundancy: %s)", testcase.value)
   473  			case err == nil && testcase.wantError == "":
   474  				testutil.AssertEqual(t, testcase.want, have)
   475  			}
   476  		})
   477  	}
   478  }