github.com/fastly/cli@v1.7.2-0.20240304164155-9d0f1d77c3bf/pkg/commands/logging/openstack/openstack_integration_test.go (about) 1 package openstack_test 2 3 import ( 4 "bytes" 5 "errors" 6 "io" 7 "strings" 8 "testing" 9 10 "github.com/fastly/go-fastly/v9/fastly" 11 12 "github.com/fastly/cli/pkg/app" 13 "github.com/fastly/cli/pkg/global" 14 "github.com/fastly/cli/pkg/mock" 15 "github.com/fastly/cli/pkg/testutil" 16 ) 17 18 func TestOpenstackCreate(t *testing.T) { 19 args := testutil.Args 20 scenarios := []struct { 21 args []string 22 api mock.API 23 wantError string 24 wantOutput string 25 }{ 26 { 27 args: args("logging openstack create --service-id 123 --version 1 --name log --bucket log --access-key foo --user user --url https://example.com --autoclone"), 28 api: mock.API{ 29 ListVersionsFn: testutil.ListVersions, 30 CloneVersionFn: testutil.CloneVersionResult(4), 31 CreateOpenstackFn: createOpenstackOK, 32 }, 33 wantOutput: "Created OpenStack logging endpoint log (service 123 version 4)", 34 }, 35 { 36 args: args("logging openstack create --service-id 123 --version 1 --name log --bucket log --access-key foo --user user --url https://example.com --autoclone"), 37 api: mock.API{ 38 ListVersionsFn: testutil.ListVersions, 39 CloneVersionFn: testutil.CloneVersionResult(4), 40 CreateOpenstackFn: createOpenstackError, 41 }, 42 wantError: errTest.Error(), 43 }, 44 { 45 args: args("logging openstack create --service-id 123 --version 1 --name log --bucket log --access-key foo --user user --url https://example.com --compression-codec zstd --gzip-level 9 --autoclone"), 46 api: mock.API{ 47 ListVersionsFn: testutil.ListVersions, 48 CloneVersionFn: testutil.CloneVersionResult(4), 49 }, 50 wantError: "error parsing arguments: the --compression-codec flag is mutually exclusive with the --gzip-level flag", 51 }, 52 } 53 for testcaseIdx := range scenarios { 54 testcase := &scenarios[testcaseIdx] 55 t.Run(strings.Join(testcase.args, " "), func(t *testing.T) { 56 var stdout bytes.Buffer 57 app.Init = func(_ []string, _ io.Reader) (*global.Data, error) { 58 opts := testutil.MockGlobalData(testcase.args, &stdout) 59 opts.APIClientFactory = mock.APIClient(testcase.api) 60 return opts, nil 61 } 62 err := app.Run(testcase.args, nil) 63 testutil.AssertErrorContains(t, err, testcase.wantError) 64 testutil.AssertStringContains(t, stdout.String(), testcase.wantOutput) 65 }) 66 } 67 } 68 69 func TestOpenstackList(t *testing.T) { 70 args := testutil.Args 71 scenarios := []struct { 72 args []string 73 api mock.API 74 wantError string 75 wantOutput string 76 }{ 77 { 78 args: args("logging openstack list --service-id 123 --version 1"), 79 api: mock.API{ 80 ListVersionsFn: testutil.ListVersions, 81 ListOpenstacksFn: listOpenstacksOK, 82 }, 83 wantOutput: listOpenstacksShortOutput, 84 }, 85 { 86 args: args("logging openstack list --service-id 123 --version 1 --verbose"), 87 api: mock.API{ 88 ListVersionsFn: testutil.ListVersions, 89 ListOpenstacksFn: listOpenstacksOK, 90 }, 91 wantOutput: listOpenstacksVerboseOutput, 92 }, 93 { 94 args: args("logging openstack list --service-id 123 --version 1 -v"), 95 api: mock.API{ 96 ListVersionsFn: testutil.ListVersions, 97 ListOpenstacksFn: listOpenstacksOK, 98 }, 99 wantOutput: listOpenstacksVerboseOutput, 100 }, 101 { 102 args: args("logging openstack --verbose list --service-id 123 --version 1"), 103 api: mock.API{ 104 ListVersionsFn: testutil.ListVersions, 105 ListOpenstacksFn: listOpenstacksOK, 106 }, 107 wantOutput: listOpenstacksVerboseOutput, 108 }, 109 { 110 args: args("logging -v openstack list --service-id 123 --version 1"), 111 api: mock.API{ 112 ListVersionsFn: testutil.ListVersions, 113 ListOpenstacksFn: listOpenstacksOK, 114 }, 115 wantOutput: listOpenstacksVerboseOutput, 116 }, 117 { 118 args: args("logging openstack list --service-id 123 --version 1"), 119 api: mock.API{ 120 ListVersionsFn: testutil.ListVersions, 121 ListOpenstacksFn: listOpenstacksError, 122 }, 123 wantError: errTest.Error(), 124 }, 125 } 126 for testcaseIdx := range scenarios { 127 testcase := &scenarios[testcaseIdx] 128 t.Run(strings.Join(testcase.args, " "), func(t *testing.T) { 129 var stdout bytes.Buffer 130 app.Init = func(_ []string, _ io.Reader) (*global.Data, error) { 131 opts := testutil.MockGlobalData(testcase.args, &stdout) 132 opts.APIClientFactory = mock.APIClient(testcase.api) 133 return opts, nil 134 } 135 err := app.Run(testcase.args, nil) 136 testutil.AssertErrorContains(t, err, testcase.wantError) 137 testutil.AssertString(t, testcase.wantOutput, stdout.String()) 138 }) 139 } 140 } 141 142 func TestOpenstackDescribe(t *testing.T) { 143 args := testutil.Args 144 scenarios := []struct { 145 args []string 146 api mock.API 147 wantError string 148 wantOutput string 149 }{ 150 { 151 args: args("logging openstack describe --service-id 123 --version 1"), 152 wantError: "error parsing arguments: required flag --name not provided", 153 }, 154 { 155 args: args("logging openstack describe --service-id 123 --version 1 --name logs"), 156 api: mock.API{ 157 ListVersionsFn: testutil.ListVersions, 158 GetOpenstackFn: getOpenstackError, 159 }, 160 wantError: errTest.Error(), 161 }, 162 { 163 args: args("logging openstack describe --service-id 123 --version 1 --name logs"), 164 api: mock.API{ 165 ListVersionsFn: testutil.ListVersions, 166 GetOpenstackFn: getOpenstackOK, 167 }, 168 wantOutput: describeOpenstackOutput, 169 }, 170 } 171 for testcaseIdx := range scenarios { 172 testcase := &scenarios[testcaseIdx] 173 t.Run(strings.Join(testcase.args, " "), func(t *testing.T) { 174 var stdout bytes.Buffer 175 app.Init = func(_ []string, _ io.Reader) (*global.Data, error) { 176 opts := testutil.MockGlobalData(testcase.args, &stdout) 177 opts.APIClientFactory = mock.APIClient(testcase.api) 178 return opts, nil 179 } 180 err := app.Run(testcase.args, nil) 181 testutil.AssertErrorContains(t, err, testcase.wantError) 182 testutil.AssertString(t, testcase.wantOutput, stdout.String()) 183 }) 184 } 185 } 186 187 func TestOpenstackUpdate(t *testing.T) { 188 args := testutil.Args 189 scenarios := []struct { 190 args []string 191 api mock.API 192 wantError string 193 wantOutput string 194 }{ 195 { 196 args: args("logging openstack update --service-id 123 --version 1 --new-name log"), 197 wantError: "error parsing arguments: required flag --name not provided", 198 }, 199 { 200 args: args("logging openstack update --service-id 123 --version 1 --name logs --new-name log --autoclone"), 201 api: mock.API{ 202 ListVersionsFn: testutil.ListVersions, 203 CloneVersionFn: testutil.CloneVersionResult(4), 204 UpdateOpenstackFn: updateOpenstackError, 205 }, 206 wantError: errTest.Error(), 207 }, 208 { 209 args: args("logging openstack update --service-id 123 --version 1 --name logs --new-name log --autoclone"), 210 api: mock.API{ 211 ListVersionsFn: testutil.ListVersions, 212 CloneVersionFn: testutil.CloneVersionResult(4), 213 UpdateOpenstackFn: updateOpenstackOK, 214 }, 215 wantOutput: "Updated OpenStack logging endpoint log (service 123 version 4)", 216 }, 217 } 218 for testcaseIdx := range scenarios { 219 testcase := &scenarios[testcaseIdx] 220 t.Run(strings.Join(testcase.args, " "), func(t *testing.T) { 221 var stdout bytes.Buffer 222 app.Init = func(_ []string, _ io.Reader) (*global.Data, error) { 223 opts := testutil.MockGlobalData(testcase.args, &stdout) 224 opts.APIClientFactory = mock.APIClient(testcase.api) 225 return opts, nil 226 } 227 err := app.Run(testcase.args, nil) 228 testutil.AssertErrorContains(t, err, testcase.wantError) 229 testutil.AssertStringContains(t, stdout.String(), testcase.wantOutput) 230 }) 231 } 232 } 233 234 func TestOpenstackDelete(t *testing.T) { 235 args := testutil.Args 236 scenarios := []struct { 237 args []string 238 api mock.API 239 wantError string 240 wantOutput string 241 }{ 242 { 243 args: args("logging openstack delete --service-id 123 --version 1"), 244 wantError: "error parsing arguments: required flag --name not provided", 245 }, 246 { 247 args: args("logging openstack delete --service-id 123 --version 1 --name logs --autoclone"), 248 api: mock.API{ 249 ListVersionsFn: testutil.ListVersions, 250 CloneVersionFn: testutil.CloneVersionResult(4), 251 DeleteOpenstackFn: deleteOpenstackError, 252 }, 253 wantError: errTest.Error(), 254 }, 255 { 256 args: args("logging openstack delete --service-id 123 --version 1 --name logs --autoclone"), 257 api: mock.API{ 258 ListVersionsFn: testutil.ListVersions, 259 CloneVersionFn: testutil.CloneVersionResult(4), 260 DeleteOpenstackFn: deleteOpenstackOK, 261 }, 262 wantOutput: "Deleted OpenStack logging endpoint logs (service 123 version 4)", 263 }, 264 } 265 for testcaseIdx := range scenarios { 266 testcase := &scenarios[testcaseIdx] 267 t.Run(strings.Join(testcase.args, " "), func(t *testing.T) { 268 var stdout bytes.Buffer 269 app.Init = func(_ []string, _ io.Reader) (*global.Data, error) { 270 opts := testutil.MockGlobalData(testcase.args, &stdout) 271 opts.APIClientFactory = mock.APIClient(testcase.api) 272 return opts, nil 273 } 274 err := app.Run(testcase.args, nil) 275 testutil.AssertErrorContains(t, err, testcase.wantError) 276 testutil.AssertStringContains(t, stdout.String(), testcase.wantOutput) 277 }) 278 } 279 } 280 281 var errTest = errors.New("fixture error") 282 283 func createOpenstackOK(i *fastly.CreateOpenstackInput) (*fastly.Openstack, error) { 284 s := fastly.Openstack{ 285 ServiceID: fastly.ToPointer(i.ServiceID), 286 ServiceVersion: fastly.ToPointer(i.ServiceVersion), 287 } 288 289 if i.Name != nil { 290 s.Name = i.Name 291 } 292 293 return &s, nil 294 } 295 296 func createOpenstackError(_ *fastly.CreateOpenstackInput) (*fastly.Openstack, error) { 297 return nil, errTest 298 } 299 300 func listOpenstacksOK(i *fastly.ListOpenstackInput) ([]*fastly.Openstack, error) { 301 return []*fastly.Openstack{ 302 { 303 ServiceID: fastly.ToPointer(i.ServiceID), 304 ServiceVersion: fastly.ToPointer(i.ServiceVersion), 305 Name: fastly.ToPointer("logs"), 306 BucketName: fastly.ToPointer("my-logs"), 307 AccessKey: fastly.ToPointer("1234"), 308 User: fastly.ToPointer("user"), 309 URL: fastly.ToPointer("https://example.com"), 310 Path: fastly.ToPointer("logs/"), 311 Period: fastly.ToPointer(3600), 312 GzipLevel: fastly.ToPointer(0), 313 Format: fastly.ToPointer(`%h %l %u %t "%r" %>s %b`), 314 FormatVersion: fastly.ToPointer(2), 315 ResponseCondition: fastly.ToPointer("Prevent default logging"), 316 MessageType: fastly.ToPointer("classic"), 317 TimestampFormat: fastly.ToPointer("%Y-%m-%dT%H:%M:%S.000"), 318 Placement: fastly.ToPointer("none"), 319 PublicKey: fastly.ToPointer(pgpPublicKey()), 320 CompressionCodec: fastly.ToPointer("zstd"), 321 }, 322 { 323 ServiceID: fastly.ToPointer(i.ServiceID), 324 ServiceVersion: fastly.ToPointer(i.ServiceVersion), 325 Name: fastly.ToPointer("analytics"), 326 BucketName: fastly.ToPointer("analytics"), 327 AccessKey: fastly.ToPointer("1234"), 328 User: fastly.ToPointer("user2"), 329 URL: fastly.ToPointer("https://two.example.com"), 330 Path: fastly.ToPointer("logs/"), 331 Period: fastly.ToPointer(86400), 332 GzipLevel: fastly.ToPointer(0), 333 Format: fastly.ToPointer(`%h %l %u %t "%r" %>s %b`), 334 FormatVersion: fastly.ToPointer(2), 335 MessageType: fastly.ToPointer("classic"), 336 ResponseCondition: fastly.ToPointer("Prevent default logging"), 337 TimestampFormat: fastly.ToPointer("%Y-%m-%dT%H:%M:%S.000"), 338 Placement: fastly.ToPointer("none"), 339 PublicKey: fastly.ToPointer(pgpPublicKey()), 340 CompressionCodec: fastly.ToPointer("zstd"), 341 }, 342 }, nil 343 } 344 345 func listOpenstacksError(_ *fastly.ListOpenstackInput) ([]*fastly.Openstack, error) { 346 return nil, errTest 347 } 348 349 var listOpenstacksShortOutput = strings.TrimSpace(` 350 SERVICE VERSION NAME 351 123 1 logs 352 123 1 analytics 353 `) + "\n" 354 355 var listOpenstacksVerboseOutput = strings.TrimSpace(` 356 Fastly API endpoint: https://api.fastly.com 357 Fastly API token provided via config file (profile: user) 358 359 Service ID (via --service-id): 123 360 361 Version: 1 362 Openstack 1/2 363 Service ID: 123 364 Version: 1 365 Name: logs 366 Bucket: my-logs 367 Access key: 1234 368 User: user 369 URL: https://example.com 370 Path: logs/ 371 Period: 3600 372 GZip level: 0 373 Format: %h %l %u %t "%r" %>s %b 374 Format version: 2 375 Response condition: Prevent default logging 376 Message type: classic 377 Timestamp format: %Y-%m-%dT%H:%M:%S.000 378 Placement: none 379 Public key: `+pgpPublicKey()+` 380 Compression codec: zstd 381 Openstack 2/2 382 Service ID: 123 383 Version: 1 384 Name: analytics 385 Bucket: analytics 386 Access key: 1234 387 User: user2 388 URL: https://two.example.com 389 Path: logs/ 390 Period: 86400 391 GZip level: 0 392 Format: %h %l %u %t "%r" %>s %b 393 Format version: 2 394 Response condition: Prevent default logging 395 Message type: classic 396 Timestamp format: %Y-%m-%dT%H:%M:%S.000 397 Placement: none 398 Public key: `+pgpPublicKey()+` 399 Compression codec: zstd 400 `) + "\n\n" 401 402 func getOpenstackOK(i *fastly.GetOpenstackInput) (*fastly.Openstack, error) { 403 return &fastly.Openstack{ 404 ServiceID: fastly.ToPointer(i.ServiceID), 405 ServiceVersion: fastly.ToPointer(i.ServiceVersion), 406 Name: fastly.ToPointer("logs"), 407 BucketName: fastly.ToPointer("my-logs"), 408 AccessKey: fastly.ToPointer("1234"), 409 User: fastly.ToPointer("user"), 410 URL: fastly.ToPointer("https://example.com"), 411 Path: fastly.ToPointer("logs/"), 412 Period: fastly.ToPointer(3600), 413 GzipLevel: fastly.ToPointer(0), 414 Format: fastly.ToPointer(`%h %l %u %t "%r" %>s %b`), 415 FormatVersion: fastly.ToPointer(2), 416 ResponseCondition: fastly.ToPointer("Prevent default logging"), 417 MessageType: fastly.ToPointer("classic"), 418 TimestampFormat: fastly.ToPointer("%Y-%m-%dT%H:%M:%S.000"), 419 Placement: fastly.ToPointer("none"), 420 PublicKey: fastly.ToPointer(pgpPublicKey()), 421 CompressionCodec: fastly.ToPointer("zstd"), 422 }, nil 423 } 424 425 func getOpenstackError(_ *fastly.GetOpenstackInput) (*fastly.Openstack, error) { 426 return nil, errTest 427 } 428 429 var describeOpenstackOutput = "\n" + strings.TrimSpace(` 430 Access key: 1234 431 Bucket: my-logs 432 Compression codec: zstd 433 Format: %h %l %u %t "%r" %>s %b 434 Format version: 2 435 GZip level: 0 436 Message type: classic 437 Name: logs 438 Path: logs/ 439 Period: 3600 440 Placement: none 441 Public key: `+pgpPublicKey()+` 442 Response condition: Prevent default logging 443 Service ID: 123 444 Timestamp format: %Y-%m-%dT%H:%M:%S.000 445 URL: https://example.com 446 User: user 447 Version: 1 448 `) + "\n" 449 450 func updateOpenstackOK(i *fastly.UpdateOpenstackInput) (*fastly.Openstack, error) { 451 return &fastly.Openstack{ 452 ServiceID: fastly.ToPointer(i.ServiceID), 453 ServiceVersion: fastly.ToPointer(i.ServiceVersion), 454 Name: fastly.ToPointer("log"), 455 BucketName: fastly.ToPointer("my-logs"), 456 AccessKey: fastly.ToPointer("1234"), 457 User: fastly.ToPointer("userupdate"), 458 URL: fastly.ToPointer("https://update.example.com"), 459 Path: fastly.ToPointer("logs/"), 460 Period: fastly.ToPointer(3600), 461 Format: fastly.ToPointer(`%h %l %u %t "%r" %>s %b`), 462 FormatVersion: fastly.ToPointer(2), 463 ResponseCondition: fastly.ToPointer("Prevent default logging"), 464 MessageType: fastly.ToPointer("classic"), 465 TimestampFormat: fastly.ToPointer("%Y-%m-%dT%H:%M:%S.000"), 466 Placement: fastly.ToPointer("none"), 467 PublicKey: fastly.ToPointer(pgpPublicKey()), 468 CompressionCodec: fastly.ToPointer("zstd"), 469 }, nil 470 } 471 472 func updateOpenstackError(_ *fastly.UpdateOpenstackInput) (*fastly.Openstack, error) { 473 return nil, errTest 474 } 475 476 func deleteOpenstackOK(_ *fastly.DeleteOpenstackInput) error { 477 return nil 478 } 479 480 func deleteOpenstackError(_ *fastly.DeleteOpenstackInput) error { 481 return errTest 482 } 483 484 // pgpPublicKey returns a PEM encoded PGP public key suitable for testing. 485 func pgpPublicKey() string { 486 return strings.TrimSpace(`-----BEGIN PGP PUBLIC KEY BLOCK----- 487 mQENBFyUD8sBCACyFnB39AuuTygseek+eA4fo0cgwva6/FSjnWq7riouQee8GgQ/ 488 ibXTRyv4iVlwI12GswvMTIy7zNvs1R54i0qvsLr+IZ4GVGJqs6ZJnvQcqe3xPoR4 489 8AnBfw90o32r/LuHf6QCJXi+AEu35koNlNAvLJ2B+KACaNB7N0EeWmqpV/1V2k9p 490 lDYk+th7LcCuaFNGqKS/PrMnnMqR6VDLCjHhNx4KR79b0Twm/2qp6an3hyNRu8Gn 491 dwxpf1/BUu3JWf+LqkN4Y3mbOmSUL3MaJNvyQguUzTfS0P0uGuBDHrJCVkMZCzDB 492 89ag55jCPHyGeHBTd02gHMWzsg3WMBWvCsrzABEBAAG0JXRlcnJhZm9ybSAodGVz 493 dCkgPHRlc3RAdGVycmFmb3JtLmNvbT6JAU4EEwEIADgWIQSHYyc6Kj9l6HzQsau6 494 vFFc9jxV/wUCXJQPywIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRC6vFFc 495 9jxV/815CAClb32OxV7wG01yF97TzlyTl8TnvjMtoG29Mw4nSyg+mjM3b8N7iXm9 496 OLX59fbDAWtBSldSZE22RXd3CvlFOG/EnKBXSjBtEqfyxYSnyOPkMPBYWGL/ApkX 497 SvPYJ4LKdvipYToKFh3y9kk2gk1DcDBDyaaHvR+3rv1u3aoy7/s2EltAfDS3ZQIq 498 7/cWTLJml/lleeB/Y6rPj8xqeCYhE5ahw9gsV/Mdqatl24V9Tks30iijx0Hhw+Gx 499 kATUikMGr2GDVqoIRga5kXI7CzYff4rkc0Twn47fMHHHe/KY9M2yVnMHUXmAZwbG 500 M1cMI/NH1DjevCKdGBLcRJlhuLPKF/anuQENBFyUD8sBCADIpd7r7GuPd6n/Ikxe 501 u6h7umV6IIPoAm88xCYpTbSZiaK30Svh6Ywra9jfE2KlU9o6Y/art8ip0VJ3m07L 502 4RSfSpnzqgSwdjSq5hNour2Fo/BzYhK7yaz2AzVSbe33R0+RYhb4b/6N+bKbjwGF 503 ftCsqVFMH+PyvYkLbvxyQrHlA9woAZaNThI1ztO5rGSnGUR8xt84eup28WIFKg0K 504 UEGUcTzz+8QGAwAra+0ewPXo/AkO+8BvZjDidP417u6gpBHOJ9qYIcO9FxHeqFyu 505 YrjlrxowEgXn5wO8xuNz6Vu1vhHGDHGDsRbZF8pv1d5O+0F1G7ttZ2GRRgVBZPwi 506 kiyRABEBAAGJATYEGAEIACAWIQSHYyc6Kj9l6HzQsau6vFFc9jxV/wUCXJQPywIb 507 DAAKCRC6vFFc9jxV/9YOCACe8qmOSnKQpQfW+PqYOqo3dt7JyweTs3FkD6NT8Zml 508 dYy/vkstbTjPpX6aTvUZjkb46BVi7AOneVHpD5GBqvRsZ9iVgDYHaehmLCdKiG5L 509 3Tp90NN+QY5WDbsGmsyk6+6ZMYejb4qYfweQeduOj27aavCJdLkCYMoRKfcFYI8c 510 FaNmEfKKy/r1PO20NXEG6t9t05K/frHy6ZG8bCNYdpagfFVot47r9JaQqWlTNtIR 511 5+zkkSq/eG9BEtRij3a6cTdQbktdBzx2KBeI0PYc1vlZR0LpuFKZqY9vlE6vTGLR 512 wMfrTEOvx0NxUM3rpaCgEmuWbB1G1Hu371oyr4srrr+N 513 =28dr 514 -----END PGP PUBLIC KEY BLOCK----- 515 `) 516 }