Skip to content

Commit

Permalink
feat: get extension by id (#209)
Browse files Browse the repository at this point in the history
  • Loading branch information
fredmaggiowski authored Jul 19, 2024
1 parent 322d075 commit dd3bb37
Show file tree
Hide file tree
Showing 10 changed files with 298 additions and 18 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- `extensions get` command

## [0.13.0] - 2024-06-26

### Added
Expand Down
19 changes: 18 additions & 1 deletion docs/30_commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,7 @@ Available subcommands are the following ones:

```sh
list List registered extensions
get Retrieve data of a specific extension
apply Create or update an extension
activate Activate an extension
deactivate Deactivate an extension
Expand All @@ -590,6 +591,22 @@ Available flags for the command:

- `--company-id` to set the ID of the desired Company

### get

The `extensions get` command helps you gathering information about a specific extension in your Company

Usage:

```sh
miactl extensions get [flags]
```

Available flags for the command:

- `--company-id` to set the ID of the desired Company
- `--extension-id` to set the ID of the desired extension.
- `--output=json|yaml` to control the printed output format.

### apply

The `extensions apply` command can be used to register new extensions or update an existing one.
Expand Down Expand Up @@ -664,7 +681,7 @@ Available flags for the command:

- `--company-id` to set the ID of the desired Company
- `--file-path` (`-f`) **required** to specify the path to the extension manifest
- `--extension-id` to set the ID of the extension Company, required for updating an existing extension.
- `--extension-id` to set the ID of the extension, required for updating an existing extension.

:::tip
In order to specify whether a create or an update is needed you have to use the `--extension-id`
Expand Down
37 changes: 26 additions & 11 deletions internal/cmd/extensions/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,18 @@ import (
)

const (
extensibilityAPIPrefix = "/api/extensibility"
tenantsExtensionsAPIPrefix = extensibilityAPIPrefix + "/tenants/%s/extensions"

listAPIFmt = tenantsExtensionsAPIPrefix
applyAPIFmt = tenantsExtensionsAPIPrefix
deleteAPIFmt = tenantsExtensionsAPIPrefix + "/%s"
activationAPIFmt = tenantsExtensionsAPIPrefix + "/%s/activation"
deactivationAPIFmt = tenantsExtensionsAPIPrefix + "/%s/%s/%s/activation"
extensibilityAPIPrefix = "/api/extensibility"
tenantsExtensionsAPIFmt = extensibilityAPIPrefix + "/tenants/%s/extensions"
tenantsExtensionsByIDAPIFmt = tenantsExtensionsAPIFmt + "/%s"
activationAPIFmt = tenantsExtensionsByIDAPIFmt + "/activation"
deactivationAPIFmt = tenantsExtensionsByIDAPIFmt + "/%s/%s/activation"
)

const IFrameExtensionType = "iframe"

type IE11yClient interface {
List(ctx context.Context, companyID string) ([]*extensibility.Extension, error)
GetOne(ctx context.Context, companyID string, extensionID string) (*extensibility.ExtensionInfo, error)
Apply(ctx context.Context, companyID string, extensionData *extensibility.Extension) (string, error)
Delete(ctx context.Context, companyID string, extensionID string) error
Activate(ctx context.Context, companyID string, extensionID string, scope ActivationScope) error
Expand All @@ -55,7 +53,7 @@ func New(c *client.APIClient) IE11yClient {
}

func (e *E11yClient) List(ctx context.Context, companyID string) ([]*extensibility.Extension, error) {
apiPath := fmt.Sprintf(listAPIFmt, companyID)
apiPath := fmt.Sprintf(tenantsExtensionsAPIFmt, companyID)
resp, err := e.c.Get().APIPath(apiPath).Do(ctx)
if err != nil {
return nil, fmt.Errorf("error executing request: %w", err)
Expand All @@ -73,12 +71,29 @@ func (e *E11yClient) List(ctx context.Context, companyID string) ([]*extensibili
return extensions, nil
}

func (e *E11yClient) GetOne(ctx context.Context, companyID string, extensionID string) (*extensibility.ExtensionInfo, error) {
apiPath := fmt.Sprintf(tenantsExtensionsByIDAPIFmt, companyID, extensionID)
resp, err := e.c.Get().APIPath(apiPath).Do(ctx)
if err != nil {
return nil, fmt.Errorf("error executing request: %w", err)
}
if err := e.assertSuccessResponse(resp); err != nil {
return nil, err
}

var extension *extensibility.ExtensionInfo
if err := resp.ParseResponse(&extension); err != nil {
return nil, fmt.Errorf("error parsing response body: %w", err)
}
return extension, nil
}

type ApplyResponseBody struct {
ExtensionID string `json:"extensionId"`
}

func (e *E11yClient) Apply(ctx context.Context, companyID string, extensionData *extensibility.Extension) (string, error) {
apiPath := fmt.Sprintf(applyAPIFmt, companyID)
apiPath := fmt.Sprintf(tenantsExtensionsAPIFmt, companyID)
body, err := resources.EncodeResourceToJSON(extensionData)
if err != nil {
return "", fmt.Errorf("error serializing request body: %s", err.Error())
Expand All @@ -105,7 +120,7 @@ func (e *E11yClient) Apply(ctx context.Context, companyID string, extensionData
}

func (e *E11yClient) Delete(ctx context.Context, companyID string, extensionID string) error {
apiPath := fmt.Sprintf(deleteAPIFmt, companyID, extensionID)
apiPath := fmt.Sprintf(tenantsExtensionsByIDAPIFmt, companyID, extensionID)
resp, err := e.c.Delete().APIPath(apiPath).Do(ctx)
if err != nil {
return fmt.Errorf("error executing request: %w", err)
Expand Down
94 changes: 90 additions & 4 deletions internal/cmd/extensions/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func TestE11yClientList(t *testing.T) {
"valid response": {
companyID: "company-1",
server: mockServer(t, ExpectedRequest{
path: fmt.Sprintf(listAPIFmt, "company-1"),
path: fmt.Sprintf("/api/extensibilty/tenants/%s/extensions", "company-1"),
verb: http.MethodGet,
}, MockResponse{
statusCode: http.StatusOK,
Expand All @@ -62,7 +62,7 @@ func TestE11yClientList(t *testing.T) {
"invalid response": {
companyID: "company-1",
server: mockServer(t, ExpectedRequest{
path: fmt.Sprintf(listAPIFmt, "company-1"),
path: fmt.Sprintf("/api/extensibilty/tenants/%s/extensions", "company-1"),
verb: http.MethodGet,
}, MockResponse{
statusCode: http.StatusInternalServerError,
Expand Down Expand Up @@ -106,6 +106,92 @@ func TestE11yClientList(t *testing.T) {
}
}

func TestE11yClientGetOne(t *testing.T) {
validBodyString := `{
"extensionId": "mocked-id",
"extensionName": "mocked-name",
"entry": "http://example.com/",
"type": "iframe",
"destination": {"id": "project"},
"description": "some description",
"activationContexts": ["project"],
"permissions": ["perm1"],
"visibilities": [{"contextType": "project", "contextId": "prjId"}],
"menu": {"id": "routeId", "labelIntl": {"en":"some label", "it": "qualche etichetta"}},
"category": {"id": "some-category"}
}`

testCases := map[string]struct {
companyID string
server *httptest.Server
err bool
}{
"valid response": {
companyID: "company-1",
server: mockServer(t, ExpectedRequest{
path: fmt.Sprintf("/api/extensibilty/tenants/%s/extensions/%s", "company-1", "ext-1"),
verb: http.MethodGet,
}, MockResponse{
statusCode: http.StatusOK,
respBody: validBodyString,
}),
},
"invalid response": {
companyID: "company-1",
server: mockServer(t, ExpectedRequest{
path: fmt.Sprintf("/api/extensibilty/tenants/%s/extensions/%s", "company-1", "ext-1"),
verb: http.MethodGet,
}, MockResponse{
statusCode: http.StatusInternalServerError,
err: true,
}),
err: true,
},
}

for testName, testCase := range testCases {
t.Run(testName, func(t *testing.T) {
defer testCase.server.Close()
clientConfig := &client.Config{
Transport: http.DefaultTransport,
Host: testCase.server.URL,
}

client, err := client.APIClientForConfig(clientConfig)
require.NoError(t, err)

data, err := New(client).GetOne(context.TODO(), testCase.companyID, "ext-1")
if testCase.err {
require.Error(t, err)
require.Nil(t, data)
} else {
require.NoError(t, err)
require.Equal(t, &extensibility.ExtensionInfo{
ExtensionID: "mocked-id",
ExtensionName: "mocked-name",
Entry: "http://example.com/",
Type: "iframe",
Destination: extensibility.DestinationArea{ID: "project"},
Description: "some description",
ActivationContexts: []extensibility.Context{"project"},
Permissions: []string{"perm1"},
Visibilities: []extensibility.Visibility{{ContextType: "project", ContextID: "prjId"}},
Menu: extensibility.Menu{
ID: "routeId",
LabelIntl: extensibility.IntlMessages{
"en": "some label",
"it": "qualche etichetta",
},
},
Category: extensibility.Category{
ID: "some-category",
},
}, data)
}
})
}
}

func TestE11yClientApply(t *testing.T) {
testCases := map[string]struct {
companyID string
Expand Down Expand Up @@ -188,7 +274,7 @@ func TestE11yClientDelete(t *testing.T) {
companyID: "company-1",
extensionID: "ext-1",
server: mockServer(t, ExpectedRequest{
path: fmt.Sprintf(deleteAPIFmt, "company-1", "ext-1"),
path: fmt.Sprintf("/api/extensibilty/tenants/%s/extensions/%s", "company-1", "ext-1"),
verb: http.MethodDelete,
}, MockResponse{
statusCode: http.StatusNoContent,
Expand All @@ -198,7 +284,7 @@ func TestE11yClientDelete(t *testing.T) {
companyID: "company-1",
extensionID: "ext-1",
server: mockServer(t, ExpectedRequest{
path: fmt.Sprintf(deleteAPIFmt, "company-1", "ext-1"),
path: fmt.Sprintf("/api/extensibilty/tenants/%s/extensions/%s", "company-1", "ext-1"),
verb: http.MethodDelete,
}, MockResponse{
statusCode: http.StatusInternalServerError,
Expand Down
1 change: 1 addition & 0 deletions internal/cmd/extensions/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func NewCommand(o *clioptions.CLIOptions) *cobra.Command {

cmd.AddCommand(
ListCmd(o),
GetOneCmd(o),
ApplyCmd(o),
DeleteCmd(o),
ActivateCmd(o),
Expand Down
1 change: 0 additions & 1 deletion internal/cmd/extensions/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import (
"github.com/spf13/cobra"
)

// DeleteCmd return a new cobra command for listing companies
func DeleteCmd(options *clioptions.CLIOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "delete",
Expand Down
64 changes: 64 additions & 0 deletions internal/cmd/extensions/getone.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright Mia srl
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package extensions

import (
"fmt"

"github.com/mia-platform/miactl/internal/client"
"github.com/mia-platform/miactl/internal/clioptions"
"github.com/mia-platform/miactl/internal/encoding"

"github.com/spf13/cobra"
)

// GetOneCmd return a new cobra command to get a single extension
func GetOneCmd(options *clioptions.CLIOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "get",
Short: "Get a registered extension",
Long: "Get details for a single registered extension for the company.",
RunE: func(cmd *cobra.Command, _ []string) error {
restConfig, err := options.ToRESTConfig()
cobra.CheckErr(err)

client, err := client.APIClientForConfig(restConfig)
cobra.CheckErr(err)

if restConfig.CompanyID == "" {
return ErrRequiredCompanyID
}
if options.EntityID == "" {
return ErrRequiredExtensionID
}

extensibilityClient := New(client)
extension, err := extensibilityClient.GetOne(cmd.Context(), restConfig.CompanyID, options.EntityID)
cobra.CheckErr(err)

serialized, err := encoding.MarshalData(extension, options.OutputFormat, encoding.MarshalOptions{Indent: true})
if err != nil {
return err
}
fmt.Println(string(serialized))
return nil
},
}

options.AddOutputFormatFlag(cmd.Flags(), encoding.JSON)
addExtensionIDFlag(options, cmd)
return cmd
}
30 changes: 30 additions & 0 deletions internal/cmd/extensions/getone_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright Mia srl
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package extensions

import (
"testing"

"github.com/mia-platform/miactl/internal/clioptions"

"github.com/stretchr/testify/require"
)

func TestGetOneCmdCommandBuilder(t *testing.T) {
opts := clioptions.NewCLIOptions()
cmd := GetOneCmd(opts)
require.NotNil(t, cmd)
}
2 changes: 1 addition & 1 deletion internal/cmd/extensions/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import (
"github.com/spf13/cobra"
)

// ListCmd return a new cobra command for listing companies
func ListCmd(options *clioptions.CLIOptions) *cobra.Command {
return &cobra.Command{
Use: "list",
Expand All @@ -50,6 +49,7 @@ func ListCmd(options *clioptions.CLIOptions) *cobra.Command {
},
}
}

func printExtensionsList(extensions []*extensibility.Extension, p printer.IPrinter) {
p.Keys("ID", "Name", "Description")
for _, extension := range extensions {
Expand Down
Loading

0 comments on commit dd3bb37

Please sign in to comment.