github.com/fastly/cli@v1.7.2-0.20240304164155-9d0f1d77c3bf/pkg/commands/aclentry/aclentry_test.go (about) 1 package aclentry_test 2 3 import ( 4 "bytes" 5 "io" 6 "net/http" 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 TestACLEntryCreate(t *testing.T) { 19 args := testutil.Args 20 scenarios := []testutil.TestScenario{ 21 { 22 Name: "validate missing --acl-id flag", 23 Args: args("acl-entry create --ip 127.0.0.1"), 24 WantError: "error parsing arguments: required flag --acl-id not provided", 25 }, 26 { 27 Name: "validate missing --ip flag", 28 Args: args("acl-entry create --acl-id 123"), 29 WantError: "error reading service: no service ID found", 30 }, 31 { 32 Name: "validate missing --service-id flag", 33 Args: args("acl-entry create --acl-id 123 --ip 127.0.0.1"), 34 WantError: "error reading service: no service ID found", 35 }, 36 { 37 Name: "validate CreateACLEntry API error", 38 API: mock.API{ 39 CreateACLEntryFn: func(i *fastly.CreateACLEntryInput) (*fastly.ACLEntry, error) { 40 return nil, testutil.Err 41 }, 42 }, 43 Args: args("acl-entry create --acl-id 123 --ip 127.0.0.1 --service-id 123"), 44 WantError: testutil.Err.Error(), 45 }, 46 { 47 Name: "validate CreateACLEntry API success", 48 API: mock.API{ 49 CreateACLEntryFn: func(i *fastly.CreateACLEntryInput) (*fastly.ACLEntry, error) { 50 return &fastly.ACLEntry{ 51 ACLID: fastly.ToPointer(i.ACLID), 52 EntryID: fastly.ToPointer("456"), 53 IP: i.IP, 54 ServiceID: fastly.ToPointer(i.ServiceID), 55 }, nil 56 }, 57 }, 58 Args: args("acl-entry create --acl-id 123 --ip 127.0.0.1 --service-id 123"), 59 WantOutput: "Created ACL entry '456' (ip: 127.0.0.1, negated: false, service: 123)", 60 }, 61 { 62 Name: "validate CreateACLEntry API success with negated IP", 63 API: mock.API{ 64 CreateACLEntryFn: func(i *fastly.CreateACLEntryInput) (*fastly.ACLEntry, error) { 65 return &fastly.ACLEntry{ 66 ACLID: fastly.ToPointer(i.ACLID), 67 EntryID: fastly.ToPointer("456"), 68 IP: i.IP, 69 ServiceID: fastly.ToPointer(i.ServiceID), 70 Negated: fastly.ToPointer(bool(fastly.ToValue(i.Negated))), 71 }, nil 72 }, 73 }, 74 Args: args("acl-entry create --acl-id 123 --ip 127.0.0.1 --negated --service-id 123"), 75 WantOutput: "Created ACL entry '456' (ip: 127.0.0.1, negated: true, service: 123)", 76 }, 77 } 78 79 for testcaseIdx := range scenarios { 80 testcase := &scenarios[testcaseIdx] 81 t.Run(testcase.Name, func(t *testing.T) { 82 var stdout bytes.Buffer 83 app.Init = func(_ []string, _ io.Reader) (*global.Data, error) { 84 opts := testutil.MockGlobalData(testcase.Args, &stdout) 85 opts.APIClientFactory = mock.APIClient(testcase.API) 86 return opts, nil 87 } 88 err := app.Run(testcase.Args, nil) 89 testutil.AssertErrorContains(t, err, testcase.WantError) 90 testutil.AssertStringContains(t, stdout.String(), testcase.WantOutput) 91 }) 92 } 93 } 94 95 func TestACLEntryDelete(t *testing.T) { 96 args := testutil.Args 97 scenarios := []testutil.TestScenario{ 98 { 99 Name: "validate missing --acl-id flag", 100 Args: args("acl-entry delete --id 456"), 101 WantError: "error parsing arguments: required flag --acl-id not provided", 102 }, 103 { 104 Name: "validate missing --id flag", 105 Args: args("acl-entry delete --acl-id 123"), 106 WantError: "error parsing arguments: required flag --id not provided", 107 }, 108 { 109 Name: "validate missing --service-id flag", 110 Args: args("acl-entry delete --acl-id 123 --id 456"), 111 WantError: "error reading service: no service ID found", 112 }, 113 { 114 Name: "validate DeleteACL API error", 115 API: mock.API{ 116 DeleteACLEntryFn: func(i *fastly.DeleteACLEntryInput) error { 117 return testutil.Err 118 }, 119 }, 120 Args: args("acl-entry delete --acl-id 123 --id 456 --service-id 123"), 121 WantError: testutil.Err.Error(), 122 }, 123 { 124 Name: "validate DeleteACL API success", 125 API: mock.API{ 126 DeleteACLEntryFn: func(i *fastly.DeleteACLEntryInput) error { 127 return nil 128 }, 129 }, 130 Args: args("acl-entry delete --acl-id 123 --id 456 --service-id 123"), 131 WantOutput: "Deleted ACL entry '456' (service: 123)", 132 }, 133 } 134 135 for testcaseIdx := range scenarios { 136 testcase := &scenarios[testcaseIdx] 137 t.Run(testcase.Name, func(t *testing.T) { 138 var stdout bytes.Buffer 139 app.Init = func(_ []string, _ io.Reader) (*global.Data, error) { 140 opts := testutil.MockGlobalData(testcase.Args, &stdout) 141 opts.APIClientFactory = mock.APIClient(testcase.API) 142 return opts, nil 143 } 144 err := app.Run(testcase.Args, nil) 145 testutil.AssertErrorContains(t, err, testcase.WantError) 146 testutil.AssertStringContains(t, stdout.String(), testcase.WantOutput) 147 }) 148 } 149 } 150 151 func TestACLEntryDescribe(t *testing.T) { 152 args := testutil.Args 153 scenarios := []testutil.TestScenario{ 154 { 155 Name: "validate missing --acl-id flag", 156 Args: args("acl-entry describe --id 456"), 157 WantError: "error parsing arguments: required flag --acl-id not provided", 158 }, 159 { 160 Name: "validate missing --id flag", 161 Args: args("acl-entry describe --acl-id 123"), 162 WantError: "error parsing arguments: required flag --id not provided", 163 }, 164 { 165 Name: "validate missing --service-id flag", 166 Args: args("acl-entry describe --acl-id 123 --id 456"), 167 WantError: "error reading service: no service ID found", 168 }, 169 { 170 Name: "validate GetACL API error", 171 API: mock.API{ 172 GetACLEntryFn: func(i *fastly.GetACLEntryInput) (*fastly.ACLEntry, error) { 173 return nil, testutil.Err 174 }, 175 }, 176 Args: args("acl-entry describe --acl-id 123 --id 456 --service-id 123"), 177 WantError: testutil.Err.Error(), 178 }, 179 { 180 Name: "validate GetACL API success", 181 API: mock.API{ 182 GetACLEntryFn: getACLEntry, 183 }, 184 Args: args("acl-entry describe --acl-id 123 --id 456 --service-id 123"), 185 WantOutput: "\nService ID: 123\nACL ID: 123\nID: 456\nIP: 127.0.0.1\nSubnet: 0\nNegated: false\nComment: \n\nCreated at: 2021-06-15 23:00:00 +0000 UTC\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\nDeleted at: 2021-06-15 23:00:00 +0000 UTC\n", 186 }, 187 } 188 189 for testcaseIdx := range scenarios { 190 testcase := &scenarios[testcaseIdx] 191 t.Run(testcase.Name, func(t *testing.T) { 192 var stdout bytes.Buffer 193 app.Init = func(_ []string, _ io.Reader) (*global.Data, error) { 194 opts := testutil.MockGlobalData(testcase.Args, &stdout) 195 opts.APIClientFactory = mock.APIClient(testcase.API) 196 return opts, nil 197 } 198 err := app.Run(testcase.Args, nil) 199 testutil.AssertErrorContains(t, err, testcase.WantError) 200 testutil.AssertStringContains(t, stdout.String(), testcase.WantOutput) 201 }) 202 } 203 } 204 205 func TestACLEntryList(t *testing.T) { 206 args := testutil.Args 207 scenarios := []testutil.TestScenario{ 208 { 209 Name: "validate missing --acl-id flag", 210 Args: args("acl-entry list"), 211 WantError: "error parsing arguments: required flag --acl-id not provided", 212 }, 213 { 214 Name: "validate missing --service-id flag", 215 Args: args("acl-entry list --acl-id 123"), 216 WantError: "error reading service: no service ID found", 217 }, 218 { 219 Name: "validate ListACLEntries API error (via GetNext() call)", 220 API: mock.API{ 221 GetACLEntriesFn: func(i *fastly.GetACLEntriesInput) *fastly.ListPaginator[fastly.ACLEntry] { 222 return fastly.NewPaginator[fastly.ACLEntry](mock.HTTPClient{ 223 Errors: []error{ 224 testutil.Err, 225 }, 226 Responses: []*http.Response{nil}, 227 }, fastly.ListOpts{}, "/example") 228 }, 229 }, 230 Args: args("acl-entry list --acl-id 123 --service-id 123"), 231 WantError: testutil.Err.Error(), 232 }, 233 { 234 Name: "validate ListACLEntries API success", 235 API: mock.API{ 236 GetACLEntriesFn: func(i *fastly.GetACLEntriesInput) *fastly.ListPaginator[fastly.ACLEntry] { 237 return fastly.NewPaginator[fastly.ACLEntry](mock.HTTPClient{ 238 Errors: []error{nil}, 239 Responses: []*http.Response{ 240 { 241 Body: io.NopCloser(strings.NewReader(`[ 242 { 243 "id": "456", 244 "service_id": "123", 245 "acl_id": "xyz", 246 "ip": "127.0.0.1", 247 "negated": 0, 248 "subnet": 0, 249 "comment": "", 250 "created_at": "2020-04-21T18:14:32+00:00", 251 "updated_at": "2020-04-21T18:14:32+00:00", 252 "deleted_at": null 253 }, 254 { 255 "id": "789", 256 "service_id": "123", 257 "acl_id": "xyz", 258 "ip": "127.0.0.2", 259 "negated": 1, 260 "subnet": 0, 261 "comment": "", 262 "created_at": "2020-04-21T18:14:32+00:00", 263 "updated_at": "2020-04-21T18:14:32+00:00", 264 "deleted_at": null 265 } 266 ]`)), 267 }, 268 }, 269 }, fastly.ListOpts{}, "/example") 270 }, 271 }, 272 Args: args("acl-entry list --acl-id 123 --service-id 123"), 273 WantOutput: listACLEntriesOutput, 274 }, 275 { 276 Name: "validate --verbose flag", 277 API: mock.API{ 278 GetACLEntriesFn: func(i *fastly.GetACLEntriesInput) *fastly.ListPaginator[fastly.ACLEntry] { 279 return fastly.NewPaginator[fastly.ACLEntry](mock.HTTPClient{ 280 Errors: []error{nil}, 281 Responses: []*http.Response{ 282 { 283 Body: io.NopCloser(strings.NewReader(`[ 284 { 285 "id": "456", 286 "service_id": "123", 287 "acl_id": "123", 288 "ip": "127.0.0.1", 289 "negated": 0, 290 "subnet": 0, 291 "comment": "foo", 292 "created_at": "2021-06-15T23:00:00Z", 293 "updated_at": "2021-06-15T23:00:00Z", 294 "deleted_at": "2021-06-15T23:00:00Z" 295 }, 296 { 297 "id": "789", 298 "service_id": "123", 299 "acl_id": "123", 300 "ip": "127.0.0.2", 301 "negated": 1, 302 "subnet": 0, 303 "comment": "bar", 304 "created_at": "2021-06-15T23:00:00Z", 305 "updated_at": "2021-06-15T23:00:00Z", 306 "deleted_at": "2021-06-15T23:00:00Z" 307 } 308 ]`)), 309 }, 310 }, 311 }, fastly.ListOpts{}, "/example") 312 }, 313 }, 314 Args: args("acl-entry list --acl-id 123 --per-page 1 --service-id 123 --verbose"), 315 WantOutput: listACLEntriesOutputVerbose, 316 }, 317 } 318 319 for testcaseIdx := range scenarios { 320 testcase := &scenarios[testcaseIdx] 321 t.Run(testcase.Name, func(t *testing.T) { 322 var stdout bytes.Buffer 323 app.Init = func(_ []string, _ io.Reader) (*global.Data, error) { 324 opts := testutil.MockGlobalData(testcase.Args, &stdout) 325 opts.APIClientFactory = mock.APIClient(testcase.API) 326 return opts, nil 327 } 328 err := app.Run(testcase.Args, nil) 329 testutil.AssertErrorContains(t, err, testcase.WantError) 330 testutil.AssertStringContains(t, stdout.String(), testcase.WantOutput) 331 }) 332 } 333 } 334 335 var listACLEntriesOutput = `SERVICE ID ID IP SUBNET NEGATED 336 123 456 127.0.0.1 0 false 337 123 789 127.0.0.2 0 true 338 ` 339 340 var listACLEntriesOutputVerbose = `Fastly API endpoint: https://api.fastly.com 341 Fastly API token provided via config file (profile: user) 342 343 Service ID (via --service-id): 123 344 345 ACL ID: 123 346 ID: 456 347 IP: 127.0.0.1 348 Subnet: 0 349 Negated: false 350 Comment: foo 351 352 Created at: 2021-06-15 23:00:00 +0000 UTC 353 Updated at: 2021-06-15 23:00:00 +0000 UTC 354 Deleted at: 2021-06-15 23:00:00 +0000 UTC 355 356 ACL ID: 123 357 ID: 789 358 IP: 127.0.0.2 359 Subnet: 0 360 Negated: true 361 Comment: bar 362 363 Created at: 2021-06-15 23:00:00 +0000 UTC 364 Updated at: 2021-06-15 23:00:00 +0000 UTC 365 Deleted at: 2021-06-15 23:00:00 +0000 UTC 366 367 ` 368 369 func TestACLEntryUpdate(t *testing.T) { 370 args := testutil.Args 371 scenarios := []testutil.TestScenario{ 372 { 373 Name: "validate missing --acl-id flag", 374 Args: args("acl-entry update"), 375 WantError: "error parsing arguments: required flag --acl-id not provided", 376 }, 377 { 378 Name: "validate missing --service-id flag", 379 Args: args("acl-entry update --acl-id 123"), 380 WantError: "error reading service: no service ID found", 381 }, 382 { 383 Name: "validate missing --id flag for single entry update", 384 Args: args("acl-entry update --acl-id 123 --service-id 123"), 385 WantError: "no ID found", 386 }, 387 { 388 Name: "validate UpdateACL API error", 389 API: mock.API{ 390 UpdateACLEntryFn: func(i *fastly.UpdateACLEntryInput) (*fastly.ACLEntry, error) { 391 return nil, testutil.Err 392 }, 393 }, 394 Args: args("acl-entry update --acl-id 123 --id 456 --service-id 123"), 395 WantError: testutil.Err.Error(), 396 }, 397 { 398 Name: "validate error from --file set with invalid json", 399 API: mock.API{ 400 BatchModifyACLEntriesFn: func(i *fastly.BatchModifyACLEntriesInput) error { 401 return nil 402 }, 403 }, 404 Args: args(`acl-entry update --acl-id 123 --file {"foo":"bar"} --id 456 --service-id 123`), 405 WantError: "missing 'entries'", 406 }, 407 { 408 Name: "validate error from --file set with zero json entries", 409 API: mock.API{ 410 BatchModifyACLEntriesFn: func(i *fastly.BatchModifyACLEntriesInput) error { 411 return nil 412 }, 413 }, 414 Args: args(`acl-entry update --acl-id 123 --file {"entries":[]} --id 456 --service-id 123`), 415 WantError: "missing 'entries'", 416 }, 417 { 418 Name: "validate success with --file", 419 API: mock.API{ 420 BatchModifyACLEntriesFn: func(i *fastly.BatchModifyACLEntriesInput) error { 421 return nil 422 }, 423 }, 424 Args: args("acl-entry update --acl-id 123 --file testdata/batch.json --id 456 --service-id 123"), 425 WantOutput: "Updated 3 ACL entries (service: 123)", 426 }, 427 // NOTE: When specifying JSON inline be sure not to have any spaces, and don't 428 // try to side-step it by wrapping in single quotes as the CLI parser will 429 // get confused (it will consider the single quotes as being part of the 430 // string it has parsed, e.g. "'{...}'" which means a json.Unmarshal error). 431 { 432 Name: "validate success with --file as inline json", 433 API: mock.API{ 434 BatchModifyACLEntriesFn: func(i *fastly.BatchModifyACLEntriesInput) error { 435 return nil 436 }, 437 }, 438 Args: args(`acl-entry update --acl-id 123 --file {"entries":[{"op":"create","ip":"127.0.0.1","subnet":8},{"op":"update"},{"op":"upsert"}]} --id 456 --service-id 123`), 439 WantOutput: "Updated 3 ACL entries (service: 123)", 440 }, 441 } 442 443 for testcaseIdx := range scenarios { 444 testcase := &scenarios[testcaseIdx] 445 t.Run(testcase.Name, func(t *testing.T) { 446 var stdout bytes.Buffer 447 app.Init = func(_ []string, _ io.Reader) (*global.Data, error) { 448 opts := testutil.MockGlobalData(testcase.Args, &stdout) 449 opts.APIClientFactory = mock.APIClient(testcase.API) 450 return opts, nil 451 } 452 err := app.Run(testcase.Args, nil) 453 testutil.AssertErrorContains(t, err, testcase.WantError) 454 testutil.AssertStringContains(t, stdout.String(), testcase.WantOutput) 455 }) 456 } 457 } 458 459 func getACLEntry(i *fastly.GetACLEntryInput) (*fastly.ACLEntry, error) { 460 t := testutil.Date 461 462 return &fastly.ACLEntry{ 463 ACLID: fastly.ToPointer(i.ACLID), 464 EntryID: fastly.ToPointer(i.EntryID), 465 IP: fastly.ToPointer("127.0.0.1"), 466 ServiceID: fastly.ToPointer(i.ServiceID), 467 468 CreatedAt: &t, 469 DeletedAt: &t, 470 UpdatedAt: &t, 471 }, nil 472 }