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
73 changes: 64 additions & 9 deletions pkg/cmd/whoami.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (

"github.com/hookdeck/hookdeck-cli/pkg/ansi"
"github.com/hookdeck/hookdeck-cli/pkg/config"
"github.com/hookdeck/hookdeck-cli/pkg/hookdeck"
"github.com/hookdeck/hookdeck-cli/pkg/project"
"github.com/hookdeck/hookdeck-cli/pkg/validators"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -42,24 +44,77 @@ func (lc *whoamiCmd) runWhoamiCmd(cmd *cobra.Command, args []string) error {
return err
}

fmt.Printf(
"Logged in as %s (%s) on project %s in organization %s\n",
color.Bold(response.UserName),
color.Bold(response.UserEmail),
color.Bold(response.ProjectName),
color.Bold(response.OrganizationName),
)
projectName, orgName, projectMode, note := resolveActiveProject(response, Config.Profile.ProjectId, func() ([]hookdeck.Project, error) {
return Config.GetAPIClient().ListProjects()
})

if orgName != "" {
fmt.Printf(
"Logged in as %s (%s) on project %s in organization %s\n",
color.Bold(response.UserName),
color.Bold(response.UserEmail),
color.Bold(projectName),
color.Bold(orgName),
)
} else {
fmt.Printf(
"Logged in as %s (%s) on project %s\n",
color.Bold(response.UserName),
color.Bold(response.UserEmail),
color.Bold(projectName),
)
}
if note != "" {
fmt.Printf("%s\n", note)
}

projectType := Config.Profile.ProjectType
if projectType == "" && Config.Profile.ProjectMode != "" {
projectType = config.ModeToProjectType(Config.Profile.ProjectMode)
}
if projectType == "" && response.ProjectMode != "" {
projectType = config.ModeToProjectType(response.ProjectMode)
if projectType == "" && projectMode != "" {
projectType = config.ModeToProjectType(projectMode)
}
if projectType != "" {
fmt.Printf("Project type: %s\n", projectType)
}

return nil
}

// resolveActiveProject returns the project name, organization name, and project
// mode to display. /cli-auth/validate resolves the project from the API key's
// bound team and ignores the profile's active project_id, so when the two
// differ the active project is looked up via listProjects. A non-empty note is
// returned when the active project could not be resolved and the key-bound
// values are shown instead.
func resolveActiveProject(response *hookdeck.ValidateAPIKeyResponse, activeProjectID string, listProjects func() ([]hookdeck.Project, error)) (projectName, orgName, projectMode, note string) {
projectName = response.ProjectName
orgName = response.OrganizationName
projectMode = response.ProjectMode

if activeProjectID == "" || activeProjectID == response.ProjectID {
return projectName, orgName, projectMode, ""
}

projects, err := listProjects()
if err != nil {
note = fmt.Sprintf("Warning: could not look up the active project (%s); showing the project associated with your API key.", activeProjectID)
return projectName, orgName, projectMode, note
}

for _, p := range projects {
if p.Id != activeProjectID {
continue
}
org, proj, parseErr := project.ParseProjectName(p.Name)
if parseErr != nil {
org = ""
proj = p.Name
}
return proj, org, p.Mode, ""
}

note = fmt.Sprintf("Warning: the active project (%s) was not found; showing the project associated with your API key. Run 'hookdeck project use' to select a project.", activeProjectID)
return projectName, orgName, projectMode, note
}
93 changes: 93 additions & 0 deletions pkg/cmd/whoami_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package cmd

import (
"errors"
"testing"

"github.com/hookdeck/hookdeck-cli/pkg/hookdeck"
)

func TestResolveActiveProject(t *testing.T) {
validateResponse := &hookdeck.ValidateAPIKeyResponse{
ProjectID: "tm_bound",
ProjectName: "Bound Project",
ProjectMode: "inbound",
OrganizationName: "Org A",
}

projects := []hookdeck.Project{
{Id: "tm_bound", Name: "[Org A] Bound Project", Mode: "inbound"},
{Id: "tm_active", Name: "[Org B] Active Project", Mode: "outbound"},
{Id: "tm_unparsable", Name: "No Org Format", Mode: "inbound"},
}

t.Run("no active project id uses validate response", func(t *testing.T) {
called := false
name, org, mode, note := resolveActiveProject(validateResponse, "", func() ([]hookdeck.Project, error) {
called = true
return projects, nil
})
if called {
t.Error("listProjects should not be called when no active project id is set")
}
if name != "Bound Project" || org != "Org A" || mode != "inbound" || note != "" {
t.Errorf("got (%q, %q, %q, %q)", name, org, mode, note)
}
})

t.Run("active project matches key-bound project uses validate response", func(t *testing.T) {
called := false
name, org, mode, note := resolveActiveProject(validateResponse, "tm_bound", func() ([]hookdeck.Project, error) {
called = true
return projects, nil
})
if called {
t.Error("listProjects should not be called when active project matches the key-bound project")
}
if name != "Bound Project" || org != "Org A" || mode != "inbound" || note != "" {
t.Errorf("got (%q, %q, %q, %q)", name, org, mode, note)
}
})

t.Run("active project differs and is resolved from project list", func(t *testing.T) {
name, org, mode, note := resolveActiveProject(validateResponse, "tm_active", func() ([]hookdeck.Project, error) {
return projects, nil
})
if name != "Active Project" || org != "Org B" || mode != "outbound" || note != "" {
t.Errorf("got (%q, %q, %q, %q)", name, org, mode, note)
}
})

t.Run("unparsable project name falls back to full name without org", func(t *testing.T) {
name, org, mode, note := resolveActiveProject(validateResponse, "tm_unparsable", func() ([]hookdeck.Project, error) {
return projects, nil
})
if name != "No Org Format" || org != "" || mode != "inbound" || note != "" {
t.Errorf("got (%q, %q, %q, %q)", name, org, mode, note)
}
})

t.Run("list error falls back to validate response with warning", func(t *testing.T) {
name, org, mode, note := resolveActiveProject(validateResponse, "tm_active", func() ([]hookdeck.Project, error) {
return nil, errors.New("boom")
})
if name != "Bound Project" || org != "Org A" || mode != "inbound" {
t.Errorf("got (%q, %q, %q)", name, org, mode)
}
if note == "" {
t.Error("expected a warning note when the project list cannot be fetched")
}
})

t.Run("active project missing from list falls back with warning", func(t *testing.T) {
name, org, mode, note := resolveActiveProject(validateResponse, "tm_deleted", func() ([]hookdeck.Project, error) {
return projects, nil
})
if name != "Bound Project" || org != "Org A" || mode != "inbound" {
t.Errorf("got (%q, %q, %q)", name, org, mode)
}
if note == "" {
t.Error("expected a warning note when the active project is not in the list")
}
})
}
Loading