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")