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 }