Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions api/dashboard/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,43 @@ func TestListApplications_Unauthorized(t *testing.T) {
assert.Contains(t, err.Error(), "session expired")
}

func TestListApplications_ParsesBlockedStatus(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/1/applications", func(w http.ResponseWriter, r *http.Request) {
require.NoError(t, json.NewEncoder(w).Encode(ApplicationsResponse{
Data: []ApplicationResource{
{
ID: "APP1",
Type: "application",
Attributes: ApplicationAttributes{
ApplicationID: "APP1",
Name: "Active App",
IsBlocked: false,
},
},
{
ID: "APP2",
Type: "application",
Attributes: ApplicationAttributes{
ApplicationID: "APP2",
Name: "Blocked App",
IsBlocked: true,
},
},
},
}))
})

ts, client := newTestClient(mux)
defer ts.Close()

apps, err := client.ListApplications("test-token")
require.NoError(t, err)
require.Len(t, apps, 2)
assert.Equal(t, ApplicationStatusActive, apps[0].Status)
assert.Equal(t, ApplicationStatusBlocked, apps[1].Status)
}

func TestGetApplication_Success(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/1/application/APP1", func(w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -263,6 +300,29 @@ func TestGetApplication_ParsesPlanLabel(t *testing.T) {
assert.Equal(t, "Grow Plus", app.PlanLabel)
}

func TestGetApplication_ParsesACL(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/1/application/APP1", func(w http.ResponseWriter, r *http.Request) {
require.NoError(t, json.NewEncoder(w).Encode(SingleApplicationResponse{
Data: ApplicationResource{
ID: "APP1", Type: "application",
Attributes: ApplicationAttributes{
ApplicationID: "APP1",
Name: "My App",
Permissions: []string{"search", "analytics"},
},
},
}))
})

ts, client := newTestClient(mux)
defer ts.Close()

app, err := client.GetApplication("test-token", "APP1")
require.NoError(t, err)
assert.Equal(t, []string{"search", "analytics"}, app.ACL)
}

func TestCreateApplication_Success(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/1/applications", func(w http.ResponseWriter, r *http.Request) {
Expand Down
29 changes: 23 additions & 6 deletions api/dashboard/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ type ApplicationAttributes struct {
ApplicationID string `json:"application_id"`
APIKey string `json:"api_key"`
Plan ApplicationPlan `json:"plan"`
IsBlocked bool `json:"is_blocked"`
Permissions []string `json:"permissions"`
}

// ApplicationPlan is the plan applied to an application (attributes.plan).
Expand All @@ -47,12 +49,21 @@ type ApplicationPlan struct {

// Application is a flattened view of an Algolia application for CLI consumption.
type Application struct {
ID string `json:"id"`
Name string `json:"name"`
APIKey string `json:"api_key,omitempty"`
APIKeyUUID string `json:"api_key_uuid,omitempty"`
PlanLabel string `json:"plan_label,omitempty"` // current plan label, e.g. "Grow Plus"
}
ID string `json:"id"`
Name string `json:"name"`
APIKey string `json:"api_key,omitempty"`
APIKeyUUID string `json:"api_key_uuid,omitempty"`
PlanLabel string `json:"plan_label,omitempty"` // current plan label, e.g. "Grow Plus"
Status string `json:"status,omitempty"` // ApplicationStatusActive or ApplicationStatusBlocked, from attributes.is_blocked
ACL []string `json:"acl,omitempty"` // authenticated user's permissions on this application
}

// ApplicationStatusActive and ApplicationStatusBlocked are the values toApplication
// assigns to Application.Status, derived from ApplicationAttributes.IsBlocked.
const (
ApplicationStatusActive = "active"
ApplicationStatusBlocked = "blocked"
)

// PaginationMeta contains page-based pagination metadata.
type PaginationMeta struct {
Expand Down Expand Up @@ -218,11 +229,17 @@ func (r *APIKeyResource) toAPIKey() APIKey {

// toApplication flattens a JSON:API resource into a simple Application.
func (r *ApplicationResource) toApplication() Application {
status := ApplicationStatusActive
if r.Attributes.IsBlocked {
status = ApplicationStatusBlocked
}
return Application{
ID: r.Attributes.ApplicationID,
Name: r.Attributes.Name,
APIKey: r.Attributes.APIKey,
PlanLabel: r.Attributes.Plan.Label,
Status: status,
ACL: r.Attributes.Permissions,
}
}

Expand Down
7 changes: 7 additions & 0 deletions pkg/cmd/application/current/current.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package current

import (
"fmt"
"strings"

"github.com/MakeNowJust/heredoc"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -101,6 +102,12 @@ func runCurrentCmd(opts *CurrentOptions) error {
if current.Plan != "" {
fmt.Fprintf(opts.IO.Out, " Plan: %s\n", current.Plan)
}
if current.Status != "" {
fmt.Fprintf(opts.IO.Out, " Status: %s\n", current.Status)
}
if len(current.ACL) > 0 {
fmt.Fprintf(opts.IO.Out, " ACL: %s\n", strings.Join(current.ACL, ", "))
}
if current.Name == "" && current.Plan == "" {
if signedOut {
fmt.Fprintf(
Expand Down
5 changes: 5 additions & 0 deletions pkg/cmd/application/current/current_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ func newServer(t *testing.T, status int) *httptest.Server {
ApplicationID: "APP1",
Name: "My App",
Plan: dashboard.ApplicationPlan{Label: "Grow Plus"},
Permissions: []string{"search", "settings_read"},
},
},
}))
Expand Down Expand Up @@ -110,6 +111,8 @@ func Test_runCurrentCmd(t *testing.T) {
assert.Contains(t, got, "my-alias")
assert.Contains(t, got, "My App")
assert.Contains(t, got, "Grow Plus")
assert.Contains(t, got, "search, settings_read")
assert.Contains(t, got, "active")
}

func Test_runCurrentCmd_notConfigured(t *testing.T) {
Expand Down Expand Up @@ -166,4 +169,6 @@ func Test_runCurrentCmd_outputJSON(t *testing.T) {
assert.Contains(t, got, `"alias":"my-alias"`)
assert.Contains(t, got, `"name":"My App"`)
assert.Contains(t, got, `"plan":"Grow Plus"`)
assert.Contains(t, got, `"status":"active"`)
assert.Contains(t, got, `"acl":["search","settings_read"]`)
}
7 changes: 4 additions & 3 deletions pkg/cmd/application/selectapp/select_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,10 @@ func Test_runSelectCmd_NonInteractiveWritesJSONOnlyToStdout(t *testing.T) {
var got apputil.ApplicationOutput
require.NoError(t, json.Unmarshal(stdout.Bytes(), &got), "stdout: %q", stdout.String())
assert.Equal(t, apputil.ApplicationOutput{
ID: "APP1",
Alias: "my app",
Name: "My App",
ID: "APP1",
Alias: "my app",
Name: "My App",
Status: "active",
}, got)
assert.NotContains(t, stdout.String(), "API key")
assert.NotContains(t, stdout.String(), "new-key")
Expand Down
18 changes: 11 additions & 7 deletions pkg/cmd/shared/apputil/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,23 @@ import (
// every command that emits one (`application current`, `application select`,
// `auth login`)
type ApplicationOutput struct {
ID string `json:"id"`
Alias string `json:"alias"`
Name string `json:"name"`
Plan string `json:"plan"`
ID string `json:"id"`
Alias string `json:"alias"`
Name string `json:"name"`
Plan string `json:"plan"`
Status string `json:"status,omitempty"`
ACL []string `json:"acl,omitempty"`
}

// NewApplicationOutput builds the output view, reading the alias from the
// config so it reflects what was actually persisted.
func NewApplicationOutput(cfg config.IConfig, app *dashboard.Application) ApplicationOutput {
out := ApplicationOutput{
ID: app.ID,
Name: app.Name,
Plan: app.PlanLabel,
ID: app.ID,
Name: app.Name,
Plan: app.PlanLabel,
Status: app.Status,
ACL: app.ACL,
}
if alias, ok := cfg.ApplicationAlias(app.ID); ok {
out.Alias = alias
Expand Down
Loading