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  }