diff --git a/CHANGELOG.md b/CHANGELOG.md index 85b319c25..f05349680 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,10 +16,11 @@ - feat(service/vcl): moved the `vcl` command under the `service` command, with an unlisted and deprecated alias of `vcl` ([#1616](https://github.com/fastly/cli/pull/1616)) - feat(service/healthcheck): moved the `healthcheck` command under the `service` command, with an unlisted and deprecated alias of `healthcheck` ([#1619](https://github.com/fastly/cli/pull/1619)) - feat(service/backend): moved the `backend` command under the `service` command, with an unlisted and deprecated alias of `backend` ([#1621](https://github.com/fastly/cli/pull/1621)) -- feat(service/backend): moved the `acl` and `aclentry` commands under the `service` command, with a unlisted and deprecated aliases of `acl` and `aclentry` ([#1621](https://github.com/fastly/cli/pull/1624)) +- feat(service/acl): moved the `acl` and `aclentry` commands under the `service` command, with a unlisted and deprecated aliases of `acl` and `aclentry` ([#1621](https://github.com/fastly/cli/pull/1624)) - feat(version): If the latest version is at least one major version higher than the current version, provide links to the release notes for the major version(s) so the user can review them before upgrading. ([#1623](https://github.com/fastly/cli/pull/1623)) - feat(service/imageoptimizerdefaults): moved the `imageoptimizerdefaults` commands under the `service` command, with an unlisted and deprecated alias of `imageoptimizerdefaults` ([#1627](https://github.com/fastly/cli/pull/1627)) - feat(service/alert): moved the `alerts` command to the `service alert` command, with an unlisted and deprecated alias of `alerts` ([#1616](https://github.com/fastly/cli/pull/1626)) +- feat(service/dictionaryentry): moved the `dictionary-entry` commands under the `service` command, with an unlisted and deprecated alias of `dictionary-entry` ([#1628](https://github.com/fastly/cli/pull/1628)) ### Bug fixes: diff --git a/pkg/app/run_test.go b/pkg/app/run_test.go index 9efce9240..4408dbe16 100644 --- a/pkg/app/run_test.go +++ b/pkg/app/run_test.go @@ -67,7 +67,6 @@ config-store config-store-entry dashboard dictionary -dictionary-entry domain install ip-list diff --git a/pkg/commands/alias/dictionaryentry/create.go b/pkg/commands/alias/dictionaryentry/create.go new file mode 100644 index 000000000..97ad644a5 --- /dev/null +++ b/pkg/commands/alias/dictionaryentry/create.go @@ -0,0 +1,29 @@ +package dictionaryentry + +import ( + "io" + + servicedictionaryentry "github.com/fastly/cli/pkg/commands/service/dictionaryentry" + + "github.com/fastly/cli/pkg/argparser" + "github.com/fastly/cli/pkg/global" + "github.com/fastly/cli/pkg/text" +) + +// CreateCommand wraps the CreateCommand from the servicedictionaryentry package. +type CreateCommand struct { + *servicedictionaryentry.CreateCommand +} + +// NewCreateCommand returns a usable command registered under the parent. +func NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand { + c := CreateCommand{servicedictionaryentry.NewCreateCommand(parent, g)} + c.CmdClause.Hidden() + return &c +} + +// Exec implements the command interface. +func (c *CreateCommand) Exec(in io.Reader, out io.Writer) error { + text.Deprecated(out, "Use the 'service dictionary-entry create' command instead.") + return c.CreateCommand.Exec(in, out) +} diff --git a/pkg/commands/alias/dictionaryentry/delete.go b/pkg/commands/alias/dictionaryentry/delete.go new file mode 100644 index 000000000..dc34c1266 --- /dev/null +++ b/pkg/commands/alias/dictionaryentry/delete.go @@ -0,0 +1,29 @@ +package dictionaryentry + +import ( + "io" + + servicedictionaryentry "github.com/fastly/cli/pkg/commands/service/dictionaryentry" + + "github.com/fastly/cli/pkg/argparser" + "github.com/fastly/cli/pkg/global" + "github.com/fastly/cli/pkg/text" +) + +// DeleteCommand wraps the DeleteCommand from the servicedictionaryentry package. +type DeleteCommand struct { + *servicedictionaryentry.DeleteCommand +} + +// NewDeleteCommand returns a usable command registered under the parent. +func NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand { + c := DeleteCommand{servicedictionaryentry.NewDeleteCommand(parent, g)} + c.CmdClause.Hidden() + return &c +} + +// Exec implements the command interface. +func (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error { + text.Deprecated(out, "Use the 'service dictionary-entry delete' command instead.") + return c.DeleteCommand.Exec(in, out) +} diff --git a/pkg/commands/alias/dictionaryentry/describe.go b/pkg/commands/alias/dictionaryentry/describe.go new file mode 100644 index 000000000..53c82c9ec --- /dev/null +++ b/pkg/commands/alias/dictionaryentry/describe.go @@ -0,0 +1,29 @@ +package dictionaryentry + +import ( + "io" + + servicedictionaryentry "github.com/fastly/cli/pkg/commands/service/dictionaryentry" + + "github.com/fastly/cli/pkg/argparser" + "github.com/fastly/cli/pkg/global" + "github.com/fastly/cli/pkg/text" +) + +// DescribeCommand wraps the DescribeCommand from the servicedictionaryentry package. +type DescribeCommand struct { + *servicedictionaryentry.DescribeCommand +} + +// NewDescribeCommand returns a usable command registered under the parent. +func NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand { + c := DescribeCommand{servicedictionaryentry.NewDescribeCommand(parent, g)} + c.CmdClause.Hidden() + return &c +} + +// Exec implements the command interface. +func (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error { + text.Deprecated(out, "Use the 'service dictionary-entry describe' command instead.") + return c.DescribeCommand.Exec(in, out) +} diff --git a/pkg/commands/alias/dictionaryentry/doc.go b/pkg/commands/alias/dictionaryentry/doc.go new file mode 100644 index 000000000..c468a3d86 --- /dev/null +++ b/pkg/commands/alias/dictionaryentry/doc.go @@ -0,0 +1,2 @@ +// Package dictionaryentry contains the 'dictionary-entry' alias for the 'service dictionary-entry' command. +package dictionaryentry diff --git a/pkg/commands/alias/dictionaryentry/list.go b/pkg/commands/alias/dictionaryentry/list.go new file mode 100644 index 000000000..582ad1262 --- /dev/null +++ b/pkg/commands/alias/dictionaryentry/list.go @@ -0,0 +1,29 @@ +package dictionaryentry + +import ( + "io" + + servicedictionaryentry "github.com/fastly/cli/pkg/commands/service/dictionaryentry" + + "github.com/fastly/cli/pkg/argparser" + "github.com/fastly/cli/pkg/global" + "github.com/fastly/cli/pkg/text" +) + +// ListCommand wraps the ListCommand from the servicedictionaryentry package. +type ListCommand struct { + *servicedictionaryentry.ListCommand +} + +// NewListCommand returns a usable command registered under the parent. +func NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand { + c := ListCommand{servicedictionaryentry.NewListCommand(parent, g)} + c.CmdClause.Hidden() + return &c +} + +// Exec implements the command interface. +func (c *ListCommand) Exec(in io.Reader, out io.Writer) error { + text.Deprecated(out, "Use the 'service dictionary-entry list' command instead.") + return c.ListCommand.Exec(in, out) +} diff --git a/pkg/commands/alias/dictionaryentry/root.go b/pkg/commands/alias/dictionaryentry/root.go new file mode 100644 index 000000000..b71b10c38 --- /dev/null +++ b/pkg/commands/alias/dictionaryentry/root.go @@ -0,0 +1,31 @@ +package dictionaryentry + +import ( + "io" + + "github.com/fastly/cli/pkg/argparser" + "github.com/fastly/cli/pkg/global" +) + +// RootCommand is the parent command for all subcommands in this package. +// It should be installed under the primary root command. +type RootCommand struct { + argparser.Base + // no flags +} + +// CommandName is the string to be used to invoke this command. +const CommandName = "dictionary-entry" + +// NewRootCommand returns a new command registered in the parent. +func NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand { + var c RootCommand + c.Globals = g + c.CmdClause = parent.Command(CommandName, "Manipulate Fastly edge dictionary items").Hidden() + return &c +} + +// Exec implements the command interface. +func (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error { + panic("unreachable") +} diff --git a/pkg/commands/alias/dictionaryentry/update.go b/pkg/commands/alias/dictionaryentry/update.go new file mode 100644 index 000000000..1c7ff06d3 --- /dev/null +++ b/pkg/commands/alias/dictionaryentry/update.go @@ -0,0 +1,29 @@ +package dictionaryentry + +import ( + "io" + + servicedictionaryentry "github.com/fastly/cli/pkg/commands/service/dictionaryentry" + + "github.com/fastly/cli/pkg/argparser" + "github.com/fastly/cli/pkg/global" + "github.com/fastly/cli/pkg/text" +) + +// UpdateCommand wraps the UpdateCommand from the servicedictionaryentry package. +type UpdateCommand struct { + *servicedictionaryentry.UpdateCommand +} + +// NewUpdateCommand returns a usable command registered under the parent. +func NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand { + c := UpdateCommand{servicedictionaryentry.NewUpdateCommand(parent, g)} + c.CmdClause.Hidden() + return &c +} + +// Exec implements the command interface. +func (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error { + text.Deprecated(out, "Use the 'service dictionary-entry update' command instead.") + return c.UpdateCommand.Exec(in, out) +} diff --git a/pkg/commands/commands.go b/pkg/commands/commands.go index 209b422ba..79d3c9551 100644 --- a/pkg/commands/commands.go +++ b/pkg/commands/commands.go @@ -8,6 +8,7 @@ import ( aliasaclentry "github.com/fastly/cli/pkg/commands/alias/aclentry" aliasalerts "github.com/fastly/cli/pkg/commands/alias/alerts" aliasbackend "github.com/fastly/cli/pkg/commands/alias/backend" + aliasdictionaryentry "github.com/fastly/cli/pkg/commands/alias/dictionaryentry" aliashealthcheck "github.com/fastly/cli/pkg/commands/alias/healthcheck" aliasimageoptimizerdefaults "github.com/fastly/cli/pkg/commands/alias/imageoptimizerdefaults" aliaspurge "github.com/fastly/cli/pkg/commands/alias/purge" @@ -25,7 +26,6 @@ import ( "github.com/fastly/cli/pkg/commands/dashboard" dashboardItem "github.com/fastly/cli/pkg/commands/dashboard/item" "github.com/fastly/cli/pkg/commands/dictionary" - "github.com/fastly/cli/pkg/commands/dictionaryentry" "github.com/fastly/cli/pkg/commands/domain" "github.com/fastly/cli/pkg/commands/install" "github.com/fastly/cli/pkg/commands/ip" @@ -102,6 +102,7 @@ import ( serviceaclentry "github.com/fastly/cli/pkg/commands/service/aclentry" servicealert "github.com/fastly/cli/pkg/commands/service/alert" servicebackend "github.com/fastly/cli/pkg/commands/service/backend" + servicedictionaryentry "github.com/fastly/cli/pkg/commands/service/dictionaryentry" servicedomain "github.com/fastly/cli/pkg/commands/service/domain" servicehealthcheck "github.com/fastly/cli/pkg/commands/service/healthcheck" serviceimageoptimizerdefaults "github.com/fastly/cli/pkg/commands/service/imageoptimizerdefaults" @@ -200,12 +201,6 @@ func Define( // nolint:revive // function-length dictionaryCreate := dictionary.NewCreateCommand(dictionaryCmdRoot.CmdClause, data) dictionaryDelete := dictionary.NewDeleteCommand(dictionaryCmdRoot.CmdClause, data) dictionaryDescribe := dictionary.NewDescribeCommand(dictionaryCmdRoot.CmdClause, data) - dictionaryEntryCmdRoot := dictionaryentry.NewRootCommand(app, data) - dictionaryEntryCreate := dictionaryentry.NewCreateCommand(dictionaryEntryCmdRoot.CmdClause, data) - dictionaryEntryDelete := dictionaryentry.NewDeleteCommand(dictionaryEntryCmdRoot.CmdClause, data) - dictionaryEntryDescribe := dictionaryentry.NewDescribeCommand(dictionaryEntryCmdRoot.CmdClause, data) - dictionaryEntryList := dictionaryentry.NewListCommand(dictionaryEntryCmdRoot.CmdClause, data) - dictionaryEntryUpdate := dictionaryentry.NewUpdateCommand(dictionaryEntryCmdRoot.CmdClause, data) dictionaryList := dictionary.NewListCommand(dictionaryCmdRoot.CmdClause, data) dictionaryUpdate := dictionary.NewUpdateCommand(dictionaryCmdRoot.CmdClause, data) domainCmdRoot := domain.NewRootCommand(app, data) @@ -655,6 +650,12 @@ func Define( // nolint:revive // function-length servicedomainList := servicedomain.NewListCommand(servicedomainCmdRoot.CmdClause, data) servicedomainUpdate := servicedomain.NewUpdateCommand(servicedomainCmdRoot.CmdClause, data) servicedomainValidate := servicedomain.NewValidateCommand(servicedomainCmdRoot.CmdClause, data) + servicedictionaryentryCmdRoot := servicedictionaryentry.NewRootCommand(serviceCmdRoot.CmdClause, data) + servicedictionaryentryCreate := servicedictionaryentry.NewCreateCommand(servicedictionaryentryCmdRoot.CmdClause, data) + servicedictionaryentryDelete := servicedictionaryentry.NewDeleteCommand(servicedictionaryentryCmdRoot.CmdClause, data) + servicedictionaryentryDescribe := servicedictionaryentry.NewDescribeCommand(servicedictionaryentryCmdRoot.CmdClause, data) + servicedictionaryentryList := servicedictionaryentry.NewListCommand(servicedictionaryentryCmdRoot.CmdClause, data) + servicedictionaryentryUpdate := servicedictionaryentry.NewUpdateCommand(servicedictionaryentryCmdRoot.CmdClause, data) servicebackendCmdRoot := servicebackend.NewRootCommand(serviceCmdRoot.CmdClause, data) servicebackendCreate := servicebackend.NewCreateCommand(servicebackendCmdRoot.CmdClause, data) servicebackendDelete := servicebackend.NewDeleteCommand(servicebackendCmdRoot.CmdClause, data) @@ -731,6 +732,12 @@ func Define( // nolint:revive // function-length aliasBackendDescribe := aliasbackend.NewDescribeCommand(aliasBackendRoot.CmdClause, data) aliasBackendList := aliasbackend.NewListCommand(aliasBackendRoot.CmdClause, data) aliasBackendUpdate := aliasbackend.NewUpdateCommand(aliasBackendRoot.CmdClause, data) + aliasDictionaryEntryRoot := aliasdictionaryentry.NewRootCommand(app, data) + aliasDictionaryEntryCreate := aliasdictionaryentry.NewCreateCommand(aliasDictionaryEntryRoot.CmdClause, data) + aliasDictionaryEntryDelete := aliasdictionaryentry.NewDeleteCommand(aliasDictionaryEntryRoot.CmdClause, data) + aliasDictionaryEntryDescribe := aliasdictionaryentry.NewDescribeCommand(aliasDictionaryEntryRoot.CmdClause, data) + aliasDictionaryEntryList := aliasdictionaryentry.NewListCommand(aliasDictionaryEntryRoot.CmdClause, data) + aliasDictionaryEntryUpdate := aliasdictionaryentry.NewUpdateCommand(aliasDictionaryEntryRoot.CmdClause, data) aliasHealthcheckRoot := aliashealthcheck.NewRootCommand(app, data) aliasHealthcheckCreate := aliashealthcheck.NewCreateCommand(aliasHealthcheckRoot.CmdClause, data) aliasHealthcheckDelete := aliashealthcheck.NewDeleteCommand(aliasHealthcheckRoot.CmdClause, data) @@ -845,12 +852,6 @@ func Define( // nolint:revive // function-length dictionaryCreate, dictionaryDelete, dictionaryDescribe, - dictionaryEntryCmdRoot, - dictionaryEntryCreate, - dictionaryEntryDelete, - dictionaryEntryDescribe, - dictionaryEntryList, - dictionaryEntryUpdate, dictionaryList, dictionaryUpdate, domainCmdRoot, @@ -1284,6 +1285,12 @@ func Define( // nolint:revive // function-length servicedomainList, servicedomainUpdate, servicedomainValidate, + servicedictionaryentryCmdRoot, + servicedictionaryentryCreate, + servicedictionaryentryDelete, + servicedictionaryentryDescribe, + servicedictionaryentryList, + servicedictionaryentryUpdate, servicebackendCmdRoot, servicebackendCreate, servicebackendDelete, @@ -1367,6 +1374,11 @@ func Define( // nolint:revive // function-length aliasBackendDescribe, aliasBackendList, aliasBackendUpdate, + aliasDictionaryEntryCreate, + aliasDictionaryEntryDelete, + aliasDictionaryEntryDescribe, + aliasDictionaryEntryList, + aliasDictionaryEntryUpdate, aliasHealthcheckCreate, aliasHealthcheckDelete, aliasHealthcheckDescribe, diff --git a/pkg/commands/dictionaryentry/dictionaryitem_test.go b/pkg/commands/dictionaryentry/dictionaryitem_test.go deleted file mode 100644 index 2988ce88f..000000000 --- a/pkg/commands/dictionaryentry/dictionaryitem_test.go +++ /dev/null @@ -1,431 +0,0 @@ -package dictionaryentry_test - -import ( - "bytes" - "context" - "errors" - "io" - "net/http" - "os" - "strings" - "testing" - - "github.com/fastly/go-fastly/v12/fastly" - - "github.com/fastly/cli/pkg/app" - "github.com/fastly/cli/pkg/global" - "github.com/fastly/cli/pkg/mock" - "github.com/fastly/cli/pkg/testutil" -) - -func TestDictionaryItemDescribe(t *testing.T) { - args := testutil.SplitArgs - scenarios := []struct { - args []string - api mock.API - wantError string - wantOutput string - }{ - { - args: args("dictionary-entry describe --service-id 123 --key foo"), - api: mock.API{GetDictionaryItemFn: describeDictionaryItemOK}, - wantError: "error parsing arguments: required flag --dictionary-id not provided", - }, - { - args: args("dictionary-entry describe --service-id 123 --dictionary-id 456"), - api: mock.API{GetDictionaryItemFn: describeDictionaryItemOK}, - wantError: "error parsing arguments: required flag --key not provided", - }, - { - args: args("dictionary-entry describe --service-id 123 --dictionary-id 456 --key foo"), - api: mock.API{GetDictionaryItemFn: describeDictionaryItemOK}, - wantOutput: describeDictionaryItemOutput, - }, - { - args: args("dictionary-entry describe --service-id 123 --dictionary-id 456 --key foo-deleted"), - api: mock.API{GetDictionaryItemFn: describeDictionaryItemOKDeleted}, - wantOutput: describeDictionaryItemOutputDeleted, - }, - } - for testcaseIdx := range scenarios { - testcase := &scenarios[testcaseIdx] - t.Run(strings.Join(testcase.args, " "), func(t *testing.T) { - var stdout bytes.Buffer - app.Init = func(_ []string, _ io.Reader) (*global.Data, error) { - opts := testutil.MockGlobalData(testcase.args, &stdout) - opts.APIClientFactory = mock.APIClient(testcase.api) - return opts, nil - } - err := app.Run(testcase.args, nil) - testutil.AssertErrorContains(t, err, testcase.wantError) - testutil.AssertString(t, testcase.wantOutput, stdout.String()) - }) - } -} - -func TestDictionaryItemsList(t *testing.T) { - args := testutil.SplitArgs - scenarios := []struct { - args []string - api mock.API - wantError string - wantOutput string - }{ - { - args: args("dictionary-entry list --service-id 123"), - wantError: "error parsing arguments: required flag --dictionary-id not provided", - }, - { - args: args("dictionary-entry list --dictionary-id 456"), - wantError: "error reading service: no service ID found", - }, - { - api: mock.API{ - GetDictionaryItemsFn: func(ctx context.Context, _ *fastly.GetDictionaryItemsInput) *fastly.ListPaginator[fastly.DictionaryItem] { - return fastly.NewPaginator[fastly.DictionaryItem](ctx, &mock.HTTPClient{ - Errors: []error{ - testutil.Err, - }, - Responses: []*http.Response{nil}, - }, fastly.ListOpts{}, "/example") - }, - }, - args: args("dictionary-entry list --service-id 123 --dictionary-id 456"), - wantError: testutil.Err.Error(), - }, - { - api: mock.API{ - GetDictionaryItemsFn: func(ctx context.Context, _ *fastly.GetDictionaryItemsInput) *fastly.ListPaginator[fastly.DictionaryItem] { - return fastly.NewPaginator[fastly.DictionaryItem](ctx, &mock.HTTPClient{ - Errors: []error{nil}, - Responses: []*http.Response{ - { - Body: io.NopCloser(strings.NewReader(`[ - { - "dictionary_id": "123", - "item_key": "foo", - "item_value": "bar", - "created_at": "2021-06-15T23:00:00Z", - "updated_at": "2021-06-15T23:00:00Z" - }, - { - "dictionary_id": "456", - "item_key": "baz", - "item_value": "qux", - "created_at": "2021-06-15T23:00:00Z", - "updated_at": "2021-06-15T23:00:00Z", - "deleted_at": "2021-06-15T23:00:00Z" - } - ]`)), - }, - }, - }, fastly.ListOpts{}, "/example") - }, - }, - args: args("dictionary-entry list --service-id 123 --dictionary-id 456 --per-page 1"), - wantOutput: listDictionaryItemsOutput, - }, - } - for testcaseIdx := range scenarios { - testcase := &scenarios[testcaseIdx] - t.Run(strings.Join(testcase.args, " "), func(t *testing.T) { - var stdout bytes.Buffer - app.Init = func(_ []string, _ io.Reader) (*global.Data, error) { - opts := testutil.MockGlobalData(testcase.args, &stdout) - opts.APIClientFactory = mock.APIClient(testcase.api) - return opts, nil - } - err := app.Run(testcase.args, nil) - testutil.AssertErrorContains(t, err, testcase.wantError) - testutil.AssertString(t, testcase.wantOutput, stdout.String()) - }) - } -} - -func TestDictionaryItemCreate(t *testing.T) { - args := testutil.SplitArgs - scenarios := []struct { - args []string - api mock.API - wantError string - wantOutput string - }{ - { - args: args("dictionary-entry create --service-id 123"), - api: mock.API{CreateDictionaryItemFn: createDictionaryItemOK}, - wantError: "error parsing arguments: required flag ", - }, - { - args: args("dictionary-entry create --service-id 123 --dictionary-id 456"), - api: mock.API{CreateDictionaryItemFn: createDictionaryItemOK}, - wantError: "error parsing arguments: required flag ", - }, - { - args: args("dictionary-entry create --service-id 123 --dictionary-id 456 --key foo --value bar"), - api: mock.API{CreateDictionaryItemFn: createDictionaryItemOK}, - wantOutput: "SUCCESS: Created dictionary item foo (service 123, dictionary 456)\n", - }, - } - for testcaseIdx := range scenarios { - testcase := &scenarios[testcaseIdx] - t.Run(strings.Join(testcase.args, " "), func(t *testing.T) { - var stdout bytes.Buffer - app.Init = func(_ []string, _ io.Reader) (*global.Data, error) { - opts := testutil.MockGlobalData(testcase.args, &stdout) - opts.APIClientFactory = mock.APIClient(testcase.api) - return opts, nil - } - err := app.Run(testcase.args, nil) - testutil.AssertErrorContains(t, err, testcase.wantError) - testutil.AssertString(t, testcase.wantOutput, stdout.String()) - }) - } -} - -func TestDictionaryItemUpdate(t *testing.T) { - args := testutil.SplitArgs - scenarios := []struct { - args []string - api mock.API - fileData string - wantError string - wantOutput string - }{ - { - args: args("dictionary-entry update --service-id 123"), - api: mock.API{UpdateDictionaryItemFn: updateDictionaryItemOK}, - wantError: "error parsing arguments: required flag --dictionary-id not provided", - }, - { - args: args("dictionary-entry update --service-id 123 --dictionary-id 456"), - api: mock.API{UpdateDictionaryItemFn: updateDictionaryItemOK}, - wantError: "an empty value is not allowed for either the '--key' or '--value' flags", - }, - { - args: args("dictionary-entry update --service-id 123 --dictionary-id 456 --key foo --value bar"), - api: mock.API{UpdateDictionaryItemFn: updateDictionaryItemOK}, - wantOutput: updateDictionaryItemOutput, - }, - { - args: args("dictionary-entry update --service-id 123 --dictionary-id 456 --file filePath"), - fileData: `{invalid": "json"}`, - wantError: "invalid character 'i' looking for beginning of object key string", - }, - // NOTE: We don't specify the full error value in the wantError field - // because this would cause an error on different OS'. For example, Unix - // systems report 'no such file or directory', while Windows will report - // 'The system cannot find the file specified'. - { - args: args("dictionary-entry update --service-id 123 --dictionary-id 456 --file missingPath"), - wantError: "open missingPath:", - }, - { - args: args("dictionary-entry update --service-id 123 --dictionary-id 456 --file filePath"), - fileData: dictionaryItemBatchModifyInputOK, - api: mock.API{BatchModifyDictionaryItemsFn: batchModifyDictionaryItemsError}, - wantError: errTest.Error(), - }, - { - args: args("dictionary-entry update --service-id 123 --dictionary-id 456 --file filePath"), - fileData: dictionaryItemBatchModifyInputOK, - api: mock.API{BatchModifyDictionaryItemsFn: batchModifyDictionaryItemsOK}, - wantOutput: "SUCCESS: Made 4 modifications of Dictionary 456 on service 123\n", - }, - } - for testcaseIdx := range scenarios { - testcase := &scenarios[testcaseIdx] - t.Run(strings.Join(testcase.args, " "), func(t *testing.T) { - var filePath string - if testcase.fileData != "" { - filePath = testutil.MakeTempFile(t, testcase.fileData) - defer os.RemoveAll(filePath) - } - - // Insert temp file path into args when "filePath" is present as placeholder - for i, v := range testcase.args { - if v == "filePath" { - testcase.args[i] = filePath - } - } - - var stdout bytes.Buffer - app.Init = func(_ []string, _ io.Reader) (*global.Data, error) { - opts := testutil.MockGlobalData(testcase.args, &stdout) - opts.APIClientFactory = mock.APIClient(testcase.api) - return opts, nil - } - err := app.Run(testcase.args, nil) - testutil.AssertErrorContains(t, err, testcase.wantError) - testutil.AssertString(t, testcase.wantOutput, stdout.String()) - }) - } -} - -func TestDictionaryItemDelete(t *testing.T) { - args := testutil.SplitArgs - scenarios := []struct { - args []string - api mock.API - wantError string - wantOutput string - }{ - { - args: args("dictionary-entry delete --service-id 123"), - api: mock.API{DeleteDictionaryItemFn: deleteDictionaryItemOK}, - wantError: "error parsing arguments: required flag ", - }, - { - args: args("dictionary-entry delete --service-id 123 --dictionary-id 456"), - api: mock.API{DeleteDictionaryItemFn: deleteDictionaryItemOK}, - wantError: "error parsing arguments: required flag ", - }, - { - args: args("dictionary-entry delete --service-id 123 --dictionary-id 456 --key foo"), - api: mock.API{DeleteDictionaryItemFn: deleteDictionaryItemOK}, - wantOutput: "SUCCESS: Deleted dictionary item foo (service 123, dictionary 456)\n", - }, - } - for testcaseIdx := range scenarios { - testcase := &scenarios[testcaseIdx] - t.Run(strings.Join(testcase.args, " "), func(t *testing.T) { - var stdout bytes.Buffer - app.Init = func(_ []string, _ io.Reader) (*global.Data, error) { - opts := testutil.MockGlobalData(testcase.args, &stdout) - opts.APIClientFactory = mock.APIClient(testcase.api) - return opts, nil - } - err := app.Run(testcase.args, nil) - testutil.AssertErrorContains(t, err, testcase.wantError) - testutil.AssertString(t, testcase.wantOutput, stdout.String()) - }) - } -} - -func describeDictionaryItemOK(_ context.Context, i *fastly.GetDictionaryItemInput) (*fastly.DictionaryItem, error) { - return &fastly.DictionaryItem{ - ServiceID: fastly.ToPointer(i.ServiceID), - DictionaryID: fastly.ToPointer(i.DictionaryID), - ItemKey: fastly.ToPointer(i.ItemKey), - ItemValue: fastly.ToPointer("bar"), - CreatedAt: testutil.MustParseTimeRFC3339("2001-02-03T04:05:06Z"), - UpdatedAt: testutil.MustParseTimeRFC3339("2001-02-03T04:05:07Z"), - }, nil -} - -var describeDictionaryItemOutput = "\n" + `Service ID: 123 -Dictionary ID: 456 -Item Key: foo -Item Value: bar -Created (UTC): 2001-02-03 04:05 -Last edited (UTC): 2001-02-03 04:05 -` - -var updateDictionaryItemOutput = `SUCCESS: Updated dictionary item (service 123) - -Dictionary ID: 456 -Item Key: foo -Item Value: bar -Created (UTC): 2001-02-03 04:05 -Last edited (UTC): 2001-02-03 04:05 -` - -func describeDictionaryItemOKDeleted(_ context.Context, i *fastly.GetDictionaryItemInput) (*fastly.DictionaryItem, error) { - return &fastly.DictionaryItem{ - ServiceID: fastly.ToPointer(i.ServiceID), - DictionaryID: fastly.ToPointer(i.DictionaryID), - ItemKey: fastly.ToPointer(i.ItemKey), - ItemValue: fastly.ToPointer("bar"), - CreatedAt: testutil.MustParseTimeRFC3339("2001-02-03T04:05:06Z"), - UpdatedAt: testutil.MustParseTimeRFC3339("2001-02-03T04:05:07Z"), - DeletedAt: testutil.MustParseTimeRFC3339("2001-02-03T04:06:08Z"), - }, nil -} - -var describeDictionaryItemOutputDeleted = "\n" + strings.TrimSpace(` -Service ID: 123 -Dictionary ID: 456 -Item Key: foo-deleted -Item Value: bar -Created (UTC): 2001-02-03 04:05 -Last edited (UTC): 2001-02-03 04:05 -Deleted (UTC): 2001-02-03 04:06 -`) + "\n" - -var listDictionaryItemsOutput = "\n" + strings.TrimSpace(` -Service ID: 123 -Item: 1/2 - Dictionary ID: 123 - Item Key: foo - Item Value: bar - Created (UTC): 2021-06-15 23:00 - Last edited (UTC): 2021-06-15 23:00 - -Item: 2/2 - Dictionary ID: 456 - Item Key: baz - Item Value: qux - Created (UTC): 2021-06-15 23:00 - Last edited (UTC): 2021-06-15 23:00 - Deleted (UTC): 2021-06-15 23:00 -`) + "\n\n" - -func createDictionaryItemOK(_ context.Context, i *fastly.CreateDictionaryItemInput) (*fastly.DictionaryItem, error) { - return &fastly.DictionaryItem{ - ServiceID: fastly.ToPointer(i.ServiceID), - DictionaryID: fastly.ToPointer(i.DictionaryID), - ItemKey: i.ItemKey, - ItemValue: i.ItemValue, - CreatedAt: testutil.MustParseTimeRFC3339("2001-02-03T04:05:06Z"), - UpdatedAt: testutil.MustParseTimeRFC3339("2001-02-03T04:05:07Z"), - }, nil -} - -func updateDictionaryItemOK(_ context.Context, i *fastly.UpdateDictionaryItemInput) (*fastly.DictionaryItem, error) { - return &fastly.DictionaryItem{ - ServiceID: fastly.ToPointer(i.ServiceID), - DictionaryID: fastly.ToPointer(i.DictionaryID), - ItemKey: fastly.ToPointer(i.ItemKey), - ItemValue: fastly.ToPointer(i.ItemValue), - CreatedAt: testutil.MustParseTimeRFC3339("2001-02-03T04:05:06Z"), - UpdatedAt: testutil.MustParseTimeRFC3339("2001-02-03T04:05:07Z"), - }, nil -} - -func deleteDictionaryItemOK(_ context.Context, _ *fastly.DeleteDictionaryItemInput) error { - return nil -} - -var dictionaryItemBatchModifyInputOK = ` -{ - "items": [ - { - "op": "create", - "item_key": "some_key", - "item_value": "new_value" - }, - { - "op": "update", - "item_key": "some_key", - "item_value": "new_value" - }, - { - "op": "upsert", - "item_key": "some_key", - "item_value": "new_value" - }, - { - "op": "delete", - "item_key": "some_key" - } - ] -}` - -func batchModifyDictionaryItemsOK(_ context.Context, _ *fastly.BatchModifyDictionaryItemsInput) error { - return nil -} - -func batchModifyDictionaryItemsError(_ context.Context, _ *fastly.BatchModifyDictionaryItemsInput) error { - return errTest -} - -var errTest = errors.New("an expected error occurred") diff --git a/pkg/commands/dictionaryentry/create.go b/pkg/commands/service/dictionaryentry/create.go similarity index 100% rename from pkg/commands/dictionaryentry/create.go rename to pkg/commands/service/dictionaryentry/create.go diff --git a/pkg/commands/dictionaryentry/delete.go b/pkg/commands/service/dictionaryentry/delete.go similarity index 100% rename from pkg/commands/dictionaryentry/delete.go rename to pkg/commands/service/dictionaryentry/delete.go diff --git a/pkg/commands/dictionaryentry/describe.go b/pkg/commands/service/dictionaryentry/describe.go similarity index 100% rename from pkg/commands/dictionaryentry/describe.go rename to pkg/commands/service/dictionaryentry/describe.go diff --git a/pkg/commands/service/dictionaryentry/dictionaryitem_test.go b/pkg/commands/service/dictionaryentry/dictionaryitem_test.go new file mode 100644 index 000000000..ba71c681e --- /dev/null +++ b/pkg/commands/service/dictionaryentry/dictionaryitem_test.go @@ -0,0 +1,353 @@ +package dictionaryentry_test + +import ( + "context" + "errors" + "io" + "net/http" + "os" + "strings" + "testing" + + root "github.com/fastly/cli/pkg/commands/service" + sub "github.com/fastly/cli/pkg/commands/service/dictionaryentry" + "github.com/fastly/cli/pkg/mock" + "github.com/fastly/cli/pkg/testutil" + "github.com/fastly/go-fastly/v12/fastly" +) + +func TestDictionaryItemDescribe(t *testing.T) { + scenarios := []testutil.CLIScenario{ + { + Args: "--service-id 123 --key foo", + API: mock.API{GetDictionaryItemFn: describeDictionaryItemOK}, + WantError: "error parsing arguments: required flag --dictionary-id not provided", + }, + { + Args: "--service-id 123 --dictionary-id 456", + API: mock.API{GetDictionaryItemFn: describeDictionaryItemOK}, + WantError: "error parsing arguments: required flag --key not provided", + }, + { + Args: "--service-id 123 --dictionary-id 456 --key foo", + API: mock.API{GetDictionaryItemFn: describeDictionaryItemOK}, + WantOutput: describeDictionaryItemOutput, + }, + { + Args: "--service-id 123 --dictionary-id 456 --key foo-deleted", + API: mock.API{GetDictionaryItemFn: describeDictionaryItemOKDeleted}, + WantOutput: describeDictionaryItemOutputDeleted, + }, + } + testutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, "describe"}, scenarios) +} + +func TestDictionaryItemsList(t *testing.T) { + scenarios := []testutil.CLIScenario{ + { + Args: "--service-id 123", + WantError: "error parsing arguments: required flag --dictionary-id not provided", + }, + { + Args: "--dictionary-id 456", + WantError: "error reading service: no service ID found", + }, + { + API: mock.API{ + GetDictionaryItemsFn: func(ctx context.Context, _ *fastly.GetDictionaryItemsInput) *fastly.ListPaginator[fastly.DictionaryItem] { + return fastly.NewPaginator[fastly.DictionaryItem](ctx, &mock.HTTPClient{ + Errors: []error{ + testutil.Err, + }, + Responses: []*http.Response{nil}, + }, fastly.ListOpts{}, "/example") + }, + }, + Args: "--service-id 123 --dictionary-id 456", + WantError: testutil.Err.Error(), + }, + { + API: mock.API{ + GetDictionaryItemsFn: func(ctx context.Context, _ *fastly.GetDictionaryItemsInput) *fastly.ListPaginator[fastly.DictionaryItem] { + return fastly.NewPaginator[fastly.DictionaryItem](ctx, &mock.HTTPClient{ + Errors: []error{nil}, + Responses: []*http.Response{ + { + Body: io.NopCloser(strings.NewReader(`[ + { + "dictionary_id": "123", + "item_key": "foo", + "item_value": "bar", + "created_at": "2021-06-15T23:00:00Z", + "updated_at": "2021-06-15T23:00:00Z" + }, + { + "dictionary_id": "456", + "item_key": "baz", + "item_value": "qux", + "created_at": "2021-06-15T23:00:00Z", + "updated_at": "2021-06-15T23:00:00Z", + "deleted_at": "2021-06-15T23:00:00Z" + } + ]`)), + }, + }, + }, fastly.ListOpts{}, "/example") + }, + }, + Args: "--service-id 123 --dictionary-id 456 --per-page 1", + WantOutput: listDictionaryItemsOutput, + }, + } + testutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, "list"}, scenarios) +} + +func TestDictionaryItemCreate(t *testing.T) { + scenarios := []testutil.CLIScenario{ + { + Args: "--service-id 123", + API: mock.API{CreateDictionaryItemFn: createDictionaryItemOK}, + WantError: "error parsing arguments: required flag ", + }, + { + Args: "--service-id 123 --dictionary-id 456", + API: mock.API{CreateDictionaryItemFn: createDictionaryItemOK}, + WantError: "error parsing arguments: required flag ", + }, + { + Args: "--service-id 123 --dictionary-id 456 --key foo --value bar", + API: mock.API{CreateDictionaryItemFn: createDictionaryItemOK}, + WantOutput: "SUCCESS: Created dictionary item foo (service 123, dictionary 456)\n", + }, + } + testutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, "create"}, scenarios) +} + +func TestDictionaryItemUpdate(t *testing.T) { + scenarios := []testutil.CLIScenario{ + { + Args: "--service-id 123", + API: mock.API{UpdateDictionaryItemFn: updateDictionaryItemOK}, + WantError: "error parsing arguments: required flag --dictionary-id not provided", + }, + { + Args: "--service-id 123 --dictionary-id 456", + API: mock.API{UpdateDictionaryItemFn: updateDictionaryItemOK}, + WantError: "an empty value is not allowed for either the '--key' or '--value' flags", + }, + { + Args: "--service-id 123 --dictionary-id 456 --key foo --value bar", + API: mock.API{UpdateDictionaryItemFn: updateDictionaryItemOK}, + WantOutput: updateDictionaryItemOutput, + }, + } + testutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, "update"}, scenarios) + + // File-based test: invalid json + t.Run("invalid json file", func(t *testing.T) { + filePath := testutil.MakeTempFile(t, `{invalid": "json"}`) + defer os.RemoveAll(filePath) + + scenarios := []testutil.CLIScenario{ + { + Args: "--service-id 123 --dictionary-id 456 --file " + filePath, + WantError: "invalid character 'i' looking for beginning of object key string", + }, + } + testutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, "update"}, scenarios) + }) + + // NOTE: We don't specify the full error value in the WantError field + // because this would cause an error on different OS'. For example, Unix + // systems report 'no such file or directory', while Windows will report + // 'The system cannot find the file specified'. + t.Run("missing file", func(t *testing.T) { + scenarios := []testutil.CLIScenario{ + { + Args: "--service-id 123 --dictionary-id 456 --file missingPath", + WantError: "open missingPath:", + }, + } + testutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, "update"}, scenarios) + }) + + // File-based test: batch modify error + t.Run("batch modify error", func(t *testing.T) { + filePath := testutil.MakeTempFile(t, dictionaryItemBatchModifyInputOK) + defer os.RemoveAll(filePath) + + scenarios := []testutil.CLIScenario{ + { + Args: "--service-id 123 --dictionary-id 456 --file " + filePath, + API: mock.API{BatchModifyDictionaryItemsFn: batchModifyDictionaryItemsError}, + WantError: errTest.Error(), + }, + } + testutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, "update"}, scenarios) + }) + + // File-based test: batch modify success + t.Run("batch modify success", func(t *testing.T) { + filePath := testutil.MakeTempFile(t, dictionaryItemBatchModifyInputOK) + defer os.RemoveAll(filePath) + + scenarios := []testutil.CLIScenario{ + { + Args: "--service-id 123 --dictionary-id 456 --file " + filePath, + API: mock.API{BatchModifyDictionaryItemsFn: batchModifyDictionaryItemsOK}, + WantOutput: "SUCCESS: Made 4 modifications of Dictionary 456 on service 123\n", + }, + } + testutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, "update"}, scenarios) + }) +} + +func TestDictionaryItemDelete(t *testing.T) { + scenarios := []testutil.CLIScenario{ + { + Args: "--service-id 123", + API: mock.API{DeleteDictionaryItemFn: deleteDictionaryItemOK}, + WantError: "error parsing arguments: required flag ", + }, + { + Args: "--service-id 123 --dictionary-id 456", + API: mock.API{DeleteDictionaryItemFn: deleteDictionaryItemOK}, + WantError: "error parsing arguments: required flag ", + }, + { + Args: "--service-id 123 --dictionary-id 456 --key foo", + API: mock.API{DeleteDictionaryItemFn: deleteDictionaryItemOK}, + WantOutput: "SUCCESS: Deleted dictionary item foo (service 123, dictionary 456)\n", + }, + } + testutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, "delete"}, scenarios) +} + +func describeDictionaryItemOK(_ context.Context, i *fastly.GetDictionaryItemInput) (*fastly.DictionaryItem, error) { + return &fastly.DictionaryItem{ + ServiceID: fastly.ToPointer(i.ServiceID), + DictionaryID: fastly.ToPointer(i.DictionaryID), + ItemKey: fastly.ToPointer(i.ItemKey), + ItemValue: fastly.ToPointer("bar"), + CreatedAt: testutil.MustParseTimeRFC3339("2001-02-03T04:05:06Z"), + UpdatedAt: testutil.MustParseTimeRFC3339("2001-02-03T04:05:07Z"), + }, nil +} + +var describeDictionaryItemOutput = "\n" + `Service ID: 123 +Dictionary ID: 456 +Item Key: foo +Item Value: bar +Created (UTC): 2001-02-03 04:05 +Last edited (UTC): 2001-02-03 04:05 +` + +var updateDictionaryItemOutput = `SUCCESS: Updated dictionary item (service 123) + +Dictionary ID: 456 +Item Key: foo +Item Value: bar +Created (UTC): 2001-02-03 04:05 +Last edited (UTC): 2001-02-03 04:05 +` + +func describeDictionaryItemOKDeleted(_ context.Context, i *fastly.GetDictionaryItemInput) (*fastly.DictionaryItem, error) { + return &fastly.DictionaryItem{ + ServiceID: fastly.ToPointer(i.ServiceID), + DictionaryID: fastly.ToPointer(i.DictionaryID), + ItemKey: fastly.ToPointer(i.ItemKey), + ItemValue: fastly.ToPointer("bar"), + CreatedAt: testutil.MustParseTimeRFC3339("2001-02-03T04:05:06Z"), + UpdatedAt: testutil.MustParseTimeRFC3339("2001-02-03T04:05:07Z"), + DeletedAt: testutil.MustParseTimeRFC3339("2001-02-03T04:06:08Z"), + }, nil +} + +var describeDictionaryItemOutputDeleted = "\n" + strings.TrimSpace(` +Service ID: 123 +Dictionary ID: 456 +Item Key: foo-deleted +Item Value: bar +Created (UTC): 2001-02-03 04:05 +Last edited (UTC): 2001-02-03 04:05 +Deleted (UTC): 2001-02-03 04:06 +`) + "\n" + +var listDictionaryItemsOutput = "\n" + strings.TrimSpace(` +Service ID: 123 +Item: 1/2 + Dictionary ID: 123 + Item Key: foo + Item Value: bar + Created (UTC): 2021-06-15 23:00 + Last edited (UTC): 2021-06-15 23:00 + +Item: 2/2 + Dictionary ID: 456 + Item Key: baz + Item Value: qux + Created (UTC): 2021-06-15 23:00 + Last edited (UTC): 2021-06-15 23:00 + Deleted (UTC): 2021-06-15 23:00 +`) + "\n\n" + +func createDictionaryItemOK(_ context.Context, i *fastly.CreateDictionaryItemInput) (*fastly.DictionaryItem, error) { + return &fastly.DictionaryItem{ + ServiceID: fastly.ToPointer(i.ServiceID), + DictionaryID: fastly.ToPointer(i.DictionaryID), + ItemKey: i.ItemKey, + ItemValue: i.ItemValue, + CreatedAt: testutil.MustParseTimeRFC3339("2001-02-03T04:05:06Z"), + UpdatedAt: testutil.MustParseTimeRFC3339("2001-02-03T04:05:07Z"), + }, nil +} + +func updateDictionaryItemOK(_ context.Context, i *fastly.UpdateDictionaryItemInput) (*fastly.DictionaryItem, error) { + return &fastly.DictionaryItem{ + ServiceID: fastly.ToPointer(i.ServiceID), + DictionaryID: fastly.ToPointer(i.DictionaryID), + ItemKey: fastly.ToPointer(i.ItemKey), + ItemValue: fastly.ToPointer(i.ItemValue), + CreatedAt: testutil.MustParseTimeRFC3339("2001-02-03T04:05:06Z"), + UpdatedAt: testutil.MustParseTimeRFC3339("2001-02-03T04:05:07Z"), + }, nil +} + +func deleteDictionaryItemOK(_ context.Context, _ *fastly.DeleteDictionaryItemInput) error { + return nil +} + +var dictionaryItemBatchModifyInputOK = ` +{ + "items": [ + { + "op": "create", + "item_key": "some_key", + "item_value": "new_value" + }, + { + "op": "update", + "item_key": "some_key", + "item_value": "new_value" + }, + { + "op": "upsert", + "item_key": "some_key", + "item_value": "new_value" + }, + { + "op": "delete", + "item_key": "some_key" + } + ] +}` + +func batchModifyDictionaryItemsOK(_ context.Context, _ *fastly.BatchModifyDictionaryItemsInput) error { + return nil +} + +func batchModifyDictionaryItemsError(_ context.Context, _ *fastly.BatchModifyDictionaryItemsInput) error { + return errTest +} + +var errTest = errors.New("an expected error occurred") diff --git a/pkg/commands/dictionaryentry/doc.go b/pkg/commands/service/dictionaryentry/doc.go similarity index 100% rename from pkg/commands/dictionaryentry/doc.go rename to pkg/commands/service/dictionaryentry/doc.go diff --git a/pkg/commands/dictionaryentry/list.go b/pkg/commands/service/dictionaryentry/list.go similarity index 100% rename from pkg/commands/dictionaryentry/list.go rename to pkg/commands/service/dictionaryentry/list.go diff --git a/pkg/commands/dictionaryentry/root.go b/pkg/commands/service/dictionaryentry/root.go similarity index 100% rename from pkg/commands/dictionaryentry/root.go rename to pkg/commands/service/dictionaryentry/root.go diff --git a/pkg/commands/dictionaryentry/update.go b/pkg/commands/service/dictionaryentry/update.go similarity index 100% rename from pkg/commands/dictionaryentry/update.go rename to pkg/commands/service/dictionaryentry/update.go