github.com/fastly/cli@v1.7.2-0.20240304164155-9d0f1d77c3bf/pkg/commands/dictionaryentry/dictionaryitem_test.go (about)

     1  package dictionaryentry_test
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"io"
     7  	"net/http"
     8  	"os"
     9  	"strings"
    10  	"testing"
    11  
    12  	"github.com/fastly/go-fastly/v9/fastly"
    13  
    14  	"github.com/fastly/cli/pkg/app"
    15  	"github.com/fastly/cli/pkg/global"
    16  	"github.com/fastly/cli/pkg/mock"
    17  	"github.com/fastly/cli/pkg/testutil"
    18  )
    19  
    20  func TestDictionaryItemDescribe(t *testing.T) {
    21  	args := testutil.Args
    22  	scenarios := []struct {
    23  		args       []string
    24  		api        mock.API
    25  		wantError  string
    26  		wantOutput string
    27  	}{
    28  		{
    29  			args:      args("dictionary-entry describe --service-id 123 --key foo"),
    30  			api:       mock.API{GetDictionaryItemFn: describeDictionaryItemOK},
    31  			wantError: "error parsing arguments: required flag --dictionary-id not provided",
    32  		},
    33  		{
    34  			args:      args("dictionary-entry describe --service-id 123 --dictionary-id 456"),
    35  			api:       mock.API{GetDictionaryItemFn: describeDictionaryItemOK},
    36  			wantError: "error parsing arguments: required flag --key not provided",
    37  		},
    38  		{
    39  			args:       args("dictionary-entry describe --service-id 123 --dictionary-id 456 --key foo"),
    40  			api:        mock.API{GetDictionaryItemFn: describeDictionaryItemOK},
    41  			wantOutput: describeDictionaryItemOutput,
    42  		},
    43  		{
    44  			args:       args("dictionary-entry describe --service-id 123 --dictionary-id 456 --key foo-deleted"),
    45  			api:        mock.API{GetDictionaryItemFn: describeDictionaryItemOKDeleted},
    46  			wantOutput: describeDictionaryItemOutputDeleted,
    47  		},
    48  	}
    49  	for testcaseIdx := range scenarios {
    50  		testcase := &scenarios[testcaseIdx]
    51  		t.Run(strings.Join(testcase.args, " "), func(t *testing.T) {
    52  			var stdout bytes.Buffer
    53  			app.Init = func(_ []string, _ io.Reader) (*global.Data, error) {
    54  				opts := testutil.MockGlobalData(testcase.args, &stdout)
    55  				opts.APIClientFactory = mock.APIClient(testcase.api)
    56  				return opts, nil
    57  			}
    58  			err := app.Run(testcase.args, nil)
    59  			testutil.AssertErrorContains(t, err, testcase.wantError)
    60  			testutil.AssertString(t, testcase.wantOutput, stdout.String())
    61  		})
    62  	}
    63  }
    64  
    65  func TestDictionaryItemsList(t *testing.T) {
    66  	args := testutil.Args
    67  	scenarios := []struct {
    68  		args       []string
    69  		api        mock.API
    70  		wantError  string
    71  		wantOutput string
    72  	}{
    73  		{
    74  			args:      args("dictionary-entry list --service-id 123"),
    75  			wantError: "error parsing arguments: required flag --dictionary-id not provided",
    76  		},
    77  		{
    78  			args:      args("dictionary-entry list --dictionary-id 456"),
    79  			wantError: "error reading service: no service ID found",
    80  		},
    81  		{
    82  			api: mock.API{
    83  				GetDictionaryItemsFn: func(i *fastly.GetDictionaryItemsInput) *fastly.ListPaginator[fastly.DictionaryItem] {
    84  					return fastly.NewPaginator[fastly.DictionaryItem](mock.HTTPClient{
    85  						Errors: []error{
    86  							testutil.Err,
    87  						},
    88  						Responses: []*http.Response{nil},
    89  					}, fastly.ListOpts{}, "/example")
    90  				},
    91  			},
    92  			args:      args("dictionary-entry list --service-id 123 --dictionary-id 456"),
    93  			wantError: testutil.Err.Error(),
    94  		},
    95  		{
    96  			api: mock.API{
    97  				GetDictionaryItemsFn: func(i *fastly.GetDictionaryItemsInput) *fastly.ListPaginator[fastly.DictionaryItem] {
    98  					return fastly.NewPaginator[fastly.DictionaryItem](mock.HTTPClient{
    99  						Errors: []error{nil},
   100  						Responses: []*http.Response{
   101  							{
   102  								Body: io.NopCloser(strings.NewReader(`[
   103                    {
   104                      "dictionary_id": "123",
   105                      "item_key": "foo",
   106                      "item_value": "bar",
   107                      "created_at": "2021-06-15T23:00:00Z",
   108                      "updated_at": "2021-06-15T23:00:00Z"
   109                    },
   110                    {
   111                      "dictionary_id": "456",
   112                      "item_key": "baz",
   113                      "item_value": "qux",
   114                      "created_at": "2021-06-15T23:00:00Z",
   115                      "updated_at": "2021-06-15T23:00:00Z",
   116                      "deleted_at": "2021-06-15T23:00:00Z"
   117                    }
   118                  ]`)),
   119  							},
   120  						},
   121  					}, fastly.ListOpts{}, "/example")
   122  				},
   123  			},
   124  			args:       args("dictionary-entry list --service-id 123 --dictionary-id 456 --per-page 1"),
   125  			wantOutput: listDictionaryItemsOutput,
   126  		},
   127  	}
   128  	for testcaseIdx := range scenarios {
   129  		testcase := &scenarios[testcaseIdx]
   130  		t.Run(strings.Join(testcase.args, " "), func(t *testing.T) {
   131  			var stdout bytes.Buffer
   132  			app.Init = func(_ []string, _ io.Reader) (*global.Data, error) {
   133  				opts := testutil.MockGlobalData(testcase.args, &stdout)
   134  				opts.APIClientFactory = mock.APIClient(testcase.api)
   135  				return opts, nil
   136  			}
   137  			err := app.Run(testcase.args, nil)
   138  			testutil.AssertErrorContains(t, err, testcase.wantError)
   139  			testutil.AssertString(t, testcase.wantOutput, stdout.String())
   140  		})
   141  	}
   142  }
   143  
   144  func TestDictionaryItemCreate(t *testing.T) {
   145  	args := testutil.Args
   146  	scenarios := []struct {
   147  		args       []string
   148  		api        mock.API
   149  		wantError  string
   150  		wantOutput string
   151  	}{
   152  		{
   153  			args:      args("dictionary-entry create --service-id 123"),
   154  			api:       mock.API{CreateDictionaryItemFn: createDictionaryItemOK},
   155  			wantError: "error parsing arguments: required flag ",
   156  		},
   157  		{
   158  			args:      args("dictionary-entry create --service-id 123 --dictionary-id 456"),
   159  			api:       mock.API{CreateDictionaryItemFn: createDictionaryItemOK},
   160  			wantError: "error parsing arguments: required flag ",
   161  		},
   162  		{
   163  			args:       args("dictionary-entry create --service-id 123 --dictionary-id 456 --key foo --value bar"),
   164  			api:        mock.API{CreateDictionaryItemFn: createDictionaryItemOK},
   165  			wantOutput: "SUCCESS: Created dictionary item foo (service 123, dictionary 456)\n",
   166  		},
   167  	}
   168  	for testcaseIdx := range scenarios {
   169  		testcase := &scenarios[testcaseIdx]
   170  		t.Run(strings.Join(testcase.args, " "), func(t *testing.T) {
   171  			var stdout bytes.Buffer
   172  			app.Init = func(_ []string, _ io.Reader) (*global.Data, error) {
   173  				opts := testutil.MockGlobalData(testcase.args, &stdout)
   174  				opts.APIClientFactory = mock.APIClient(testcase.api)
   175  				return opts, nil
   176  			}
   177  			err := app.Run(testcase.args, nil)
   178  			testutil.AssertErrorContains(t, err, testcase.wantError)
   179  			testutil.AssertString(t, testcase.wantOutput, stdout.String())
   180  		})
   181  	}
   182  }
   183  
   184  func TestDictionaryItemUpdate(t *testing.T) {
   185  	args := testutil.Args
   186  	scenarios := []struct {
   187  		args       []string
   188  		api        mock.API
   189  		fileData   string
   190  		wantError  string
   191  		wantOutput string
   192  	}{
   193  		{
   194  			args:      args("dictionary-entry update --service-id 123"),
   195  			api:       mock.API{UpdateDictionaryItemFn: updateDictionaryItemOK},
   196  			wantError: "error parsing arguments: required flag --dictionary-id not provided",
   197  		},
   198  		{
   199  			args:      args("dictionary-entry update --service-id 123 --dictionary-id 456"),
   200  			api:       mock.API{UpdateDictionaryItemFn: updateDictionaryItemOK},
   201  			wantError: "an empty value is not allowed for either the '--key' or '--value' flags",
   202  		},
   203  		{
   204  			args:       args("dictionary-entry update --service-id 123 --dictionary-id 456 --key foo --value bar"),
   205  			api:        mock.API{UpdateDictionaryItemFn: updateDictionaryItemOK},
   206  			wantOutput: updateDictionaryItemOutput,
   207  		},
   208  		{
   209  			args:      args("dictionary-entry update --service-id 123 --dictionary-id 456 --file filePath"),
   210  			fileData:  `{invalid": "json"}`,
   211  			wantError: "invalid character 'i' looking for beginning of object key string",
   212  		},
   213  		// NOTE: We don't specify the full error value in the wantError field
   214  		// because this would cause an error on different OS'. For example, Unix
   215  		// systems report 'no such file or directory', while Windows will report
   216  		// 'The system cannot find the file specified'.
   217  		{
   218  			args:      args("dictionary-entry update --service-id 123 --dictionary-id 456 --file missingPath"),
   219  			wantError: "open missingPath:",
   220  		},
   221  		{
   222  			args:      args("dictionary-entry update --service-id 123 --dictionary-id 456 --file filePath"),
   223  			fileData:  dictionaryItemBatchModifyInputOK,
   224  			api:       mock.API{BatchModifyDictionaryItemsFn: batchModifyDictionaryItemsError},
   225  			wantError: errTest.Error(),
   226  		},
   227  		{
   228  			args:       args("dictionary-entry update --service-id 123 --dictionary-id 456 --file filePath"),
   229  			fileData:   dictionaryItemBatchModifyInputOK,
   230  			api:        mock.API{BatchModifyDictionaryItemsFn: batchModifyDictionaryItemsOK},
   231  			wantOutput: "SUCCESS: Made 4 modifications of Dictionary 456 on service 123\n",
   232  		},
   233  	}
   234  	for testcaseIdx := range scenarios {
   235  		testcase := &scenarios[testcaseIdx]
   236  		t.Run(strings.Join(testcase.args, " "), func(t *testing.T) {
   237  			var filePath string
   238  			if testcase.fileData != "" {
   239  				filePath = testutil.MakeTempFile(t, testcase.fileData)
   240  				defer os.RemoveAll(filePath)
   241  			}
   242  
   243  			// Insert temp file path into args when "filePath" is present as placeholder
   244  			for i, v := range testcase.args {
   245  				if v == "filePath" {
   246  					testcase.args[i] = filePath
   247  				}
   248  			}
   249  
   250  			var stdout bytes.Buffer
   251  			app.Init = func(_ []string, _ io.Reader) (*global.Data, error) {
   252  				opts := testutil.MockGlobalData(testcase.args, &stdout)
   253  				opts.APIClientFactory = mock.APIClient(testcase.api)
   254  				return opts, nil
   255  			}
   256  			err := app.Run(testcase.args, nil)
   257  			testutil.AssertErrorContains(t, err, testcase.wantError)
   258  			testutil.AssertString(t, testcase.wantOutput, stdout.String())
   259  		})
   260  	}
   261  }
   262  
   263  func TestDictionaryItemDelete(t *testing.T) {
   264  	args := testutil.Args
   265  	scenarios := []struct {
   266  		args       []string
   267  		api        mock.API
   268  		wantError  string
   269  		wantOutput string
   270  	}{
   271  		{
   272  			args:      args("dictionary-entry delete --service-id 123"),
   273  			api:       mock.API{DeleteDictionaryItemFn: deleteDictionaryItemOK},
   274  			wantError: "error parsing arguments: required flag ",
   275  		},
   276  		{
   277  			args:      args("dictionary-entry delete --service-id 123 --dictionary-id 456"),
   278  			api:       mock.API{DeleteDictionaryItemFn: deleteDictionaryItemOK},
   279  			wantError: "error parsing arguments: required flag ",
   280  		},
   281  		{
   282  			args:       args("dictionary-entry delete --service-id 123 --dictionary-id 456 --key foo"),
   283  			api:        mock.API{DeleteDictionaryItemFn: deleteDictionaryItemOK},
   284  			wantOutput: "SUCCESS: Deleted dictionary item foo (service 123, dictionary 456)\n",
   285  		},
   286  	}
   287  	for testcaseIdx := range scenarios {
   288  		testcase := &scenarios[testcaseIdx]
   289  		t.Run(strings.Join(testcase.args, " "), func(t *testing.T) {
   290  			var stdout bytes.Buffer
   291  			app.Init = func(_ []string, _ io.Reader) (*global.Data, error) {
   292  				opts := testutil.MockGlobalData(testcase.args, &stdout)
   293  				opts.APIClientFactory = mock.APIClient(testcase.api)
   294  				return opts, nil
   295  			}
   296  			err := app.Run(testcase.args, nil)
   297  			testutil.AssertErrorContains(t, err, testcase.wantError)
   298  			testutil.AssertString(t, testcase.wantOutput, stdout.String())
   299  		})
   300  	}
   301  }
   302  
   303  func describeDictionaryItemOK(i *fastly.GetDictionaryItemInput) (*fastly.DictionaryItem, error) {
   304  	return &fastly.DictionaryItem{
   305  		ServiceID:    fastly.ToPointer(i.ServiceID),
   306  		DictionaryID: fastly.ToPointer(i.DictionaryID),
   307  		ItemKey:      fastly.ToPointer(i.ItemKey),
   308  		ItemValue:    fastly.ToPointer("bar"),
   309  		CreatedAt:    testutil.MustParseTimeRFC3339("2001-02-03T04:05:06Z"),
   310  		UpdatedAt:    testutil.MustParseTimeRFC3339("2001-02-03T04:05:07Z"),
   311  	}, nil
   312  }
   313  
   314  var describeDictionaryItemOutput = "\n" + `Service ID: 123
   315  Dictionary ID: 456
   316  Item Key: foo
   317  Item Value: bar
   318  Created (UTC): 2001-02-03 04:05
   319  Last edited (UTC): 2001-02-03 04:05
   320  `
   321  
   322  var updateDictionaryItemOutput = `SUCCESS: Updated dictionary item (service 123)
   323  
   324  Dictionary ID: 456
   325  Item Key: foo
   326  Item Value: bar
   327  Created (UTC): 2001-02-03 04:05
   328  Last edited (UTC): 2001-02-03 04:05
   329  `
   330  
   331  func describeDictionaryItemOKDeleted(i *fastly.GetDictionaryItemInput) (*fastly.DictionaryItem, error) {
   332  	return &fastly.DictionaryItem{
   333  		ServiceID:    fastly.ToPointer(i.ServiceID),
   334  		DictionaryID: fastly.ToPointer(i.DictionaryID),
   335  		ItemKey:      fastly.ToPointer(i.ItemKey),
   336  		ItemValue:    fastly.ToPointer("bar"),
   337  		CreatedAt:    testutil.MustParseTimeRFC3339("2001-02-03T04:05:06Z"),
   338  		UpdatedAt:    testutil.MustParseTimeRFC3339("2001-02-03T04:05:07Z"),
   339  		DeletedAt:    testutil.MustParseTimeRFC3339("2001-02-03T04:06:08Z"),
   340  	}, nil
   341  }
   342  
   343  var describeDictionaryItemOutputDeleted = "\n" + strings.TrimSpace(`
   344  Service ID: 123
   345  Dictionary ID: 456
   346  Item Key: foo-deleted
   347  Item Value: bar
   348  Created (UTC): 2001-02-03 04:05
   349  Last edited (UTC): 2001-02-03 04:05
   350  Deleted (UTC): 2001-02-03 04:06
   351  `) + "\n"
   352  
   353  var listDictionaryItemsOutput = "\n" + strings.TrimSpace(`
   354  Service ID: 123
   355  Item: 1/2
   356  	Dictionary ID: 123
   357  	Item Key: foo
   358  	Item Value: bar
   359  	Created (UTC): 2021-06-15 23:00
   360  	Last edited (UTC): 2021-06-15 23:00
   361  
   362  Item: 2/2
   363  	Dictionary ID: 456
   364  	Item Key: baz
   365  	Item Value: qux
   366  	Created (UTC): 2021-06-15 23:00
   367  	Last edited (UTC): 2021-06-15 23:00
   368  	Deleted (UTC): 2021-06-15 23:00
   369  `) + "\n\n"
   370  
   371  func createDictionaryItemOK(i *fastly.CreateDictionaryItemInput) (*fastly.DictionaryItem, error) {
   372  	return &fastly.DictionaryItem{
   373  		ServiceID:    fastly.ToPointer(i.ServiceID),
   374  		DictionaryID: fastly.ToPointer(i.DictionaryID),
   375  		ItemKey:      i.ItemKey,
   376  		ItemValue:    i.ItemValue,
   377  		CreatedAt:    testutil.MustParseTimeRFC3339("2001-02-03T04:05:06Z"),
   378  		UpdatedAt:    testutil.MustParseTimeRFC3339("2001-02-03T04:05:07Z"),
   379  	}, nil
   380  }
   381  
   382  func updateDictionaryItemOK(i *fastly.UpdateDictionaryItemInput) (*fastly.DictionaryItem, error) {
   383  	return &fastly.DictionaryItem{
   384  		ServiceID:    fastly.ToPointer(i.ServiceID),
   385  		DictionaryID: fastly.ToPointer(i.DictionaryID),
   386  		ItemKey:      fastly.ToPointer(i.ItemKey),
   387  		ItemValue:    fastly.ToPointer(i.ItemValue),
   388  		CreatedAt:    testutil.MustParseTimeRFC3339("2001-02-03T04:05:06Z"),
   389  		UpdatedAt:    testutil.MustParseTimeRFC3339("2001-02-03T04:05:07Z"),
   390  	}, nil
   391  }
   392  
   393  func deleteDictionaryItemOK(_ *fastly.DeleteDictionaryItemInput) error {
   394  	return nil
   395  }
   396  
   397  var dictionaryItemBatchModifyInputOK = `
   398  {
   399  	"items": [
   400  		{
   401  		  "op": "create",
   402  		  "item_key": "some_key",
   403  		  "item_value": "new_value"
   404  		},
   405  		{
   406  		  "op": "update",
   407  		  "item_key": "some_key",
   408  		  "item_value": "new_value"
   409  		},
   410  		{
   411  		  "op": "upsert",
   412  		  "item_key": "some_key",
   413  		  "item_value": "new_value"
   414  		},
   415  		{
   416  		  "op": "delete",
   417  		  "item_key": "some_key"
   418  		}
   419  	]
   420  }`
   421  
   422  func batchModifyDictionaryItemsOK(_ *fastly.BatchModifyDictionaryItemsInput) error {
   423  	return nil
   424  }
   425  
   426  func batchModifyDictionaryItemsError(_ *fastly.BatchModifyDictionaryItemsInput) error {
   427  	return errTest
   428  }
   429  
   430  var errTest = errors.New("an expected error occurred")