Skip to content

Commit

Permalink
Test the org:create and org:info commands
Browse files Browse the repository at this point in the history
  • Loading branch information
pjcdawkins committed Oct 15, 2024
1 parent 9aad277 commit 31c7381
Show file tree
Hide file tree
Showing 10 changed files with 199 additions and 33 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/go-playground/validator/v10 v10.20.0
github.com/gofrs/flock v0.8.1
github.com/mattn/go-isatty v0.0.20
github.com/oklog/ulid/v2 v2.1.0
github.com/platformsh/platformify v0.2.11
github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.19.0
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU=
github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/platformsh/platformify v0.2.11 h1:9TRej4tDgQahRfl1tDOGaCry79yXYXbzDR1ZMdOPsU8=
Expand Down
2 changes: 2 additions & 0 deletions internal/mockapi/api_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ func NewHandler(t *testing.T) *Handler {
})

h.Mux.Get("/organizations", h.handleListOrgs)
h.Mux.Post("/organizations", h.handleCreateOrg)
h.Mux.Get("/organizations/{id}", h.handleGetOrg)
h.Mux.Patch("/organizations/{id}", h.handlePatchOrg)
h.Mux.Get("/users/{id}/organizations", h.handleListOrgs)
h.Mux.Get("/ref/organizations", h.handleOrgRefs)

Expand Down
48 changes: 46 additions & 2 deletions internal/mockapi/orgs.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package mockapi

import (
"crypto/rand"
"encoding/json"
"net/http"
"net/url"
"path"
"slices"
"strings"

"github.com/go-chi/chi/v5"
"github.com/oklog/ulid/v2"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -38,7 +41,7 @@ func (h *Handler) handleListOrgs(w http.ResponseWriter, _ *http.Request) {
orgs = append(orgs, o)
ownerIDs[o.Owner] = struct{}{}
}
slices.SortFunc(orgs, func(a, b *Org) int { return strings.Compare(a.ID, b.ID) })
slices.SortFunc(orgs, func(a, b *Org) int { return strings.Compare(a.Name, b.Name) })
_ = json.NewEncoder(w).Encode(struct {
Items []*Org `json:"items"`
Links HalLinks `json:"_links"`
Expand All @@ -53,7 +56,6 @@ func (h *Handler) handleGetOrg(w http.ResponseWriter, req *http.Request) {
defer h.store.RUnlock()
var org *Org

// TODO why doesn't Chi decode this?
orgID := chi.URLParam(req, "id")
if strings.HasPrefix(orgID, "name%3D") {
name := strings.TrimPrefix(orgID, "name%3D")
Expand All @@ -74,3 +76,45 @@ func (h *Handler) handleGetOrg(w http.ResponseWriter, req *http.Request) {

_ = json.NewEncoder(w).Encode(org)
}

func (h *Handler) handleCreateOrg(w http.ResponseWriter, req *http.Request) {
h.store.Lock()
defer h.store.Unlock()
var org Org
err := json.NewDecoder(req.Body).Decode(&org)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
for _, o := range h.store.orgs {
if o.Name == org.Name {
w.WriteHeader(http.StatusConflict)
return
}
}
org.ID = ulid.MustNew(ulid.Now(), rand.Reader).String()
org.Owner = h.store.myUser.ID
org.Capabilities = []string{}
org.Links = MakeHALLinks("self=/organizations/" + url.PathEscape(org.ID))
h.store.orgs[org.ID] = &org
_ = json.NewEncoder(w).Encode(&org)
}

func (h *Handler) handlePatchOrg(w http.ResponseWriter, req *http.Request) {
h.store.Lock()
defer h.store.Unlock()
projectID := chi.URLParam(req, "id")
p, ok := h.store.orgs[projectID]
if !ok {
w.WriteHeader(http.StatusNotFound)
return
}
patched := *p
err := json.NewDecoder(req.Body).Decode(&patched)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
h.store.orgs[projectID] = &patched
_ = json.NewEncoder(w).Encode(&patched)
}
38 changes: 16 additions & 22 deletions tests/app_list_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package tests

import (
"bytes"
"io"
"net/http/httptest"
"os"
"strings"
"testing"

Expand All @@ -23,14 +20,16 @@ func TestAppList(t *testing.T) {
apiServer := httptest.NewServer(apiHandler)
defer apiServer.Close()

projectID := "nu8ohgeizah1a"

apiHandler.SetProjects([]*mockapi.Project{{
ID: mockProjectID,
Links: mockapi.MakeHALLinks("self=/projects/"+mockProjectID,
"environments=/projects/"+mockProjectID+"/environments"),
ID: projectID,
Links: mockapi.MakeHALLinks("self=/projects/"+projectID,
"environments=/projects/"+projectID+"/environments"),
DefaultBranch: "main",
}})

main := makeEnv(mockProjectID, "main", "production", "active", nil)
main := makeEnv(projectID, "main", "production", "active", nil)
main.SetCurrentDeployment(&mockapi.Deployment{
WebApps: map[string]mockapi.App{
"app": {Name: "app", Type: "golang:1.23", Size: "AUTO"},
Expand All @@ -43,14 +42,14 @@ func TestAppList(t *testing.T) {
Worker: mockapi.WorkerInfo{Commands: mockapi.Commands{Start: "sleep 60"}},
},
},
Links: mockapi.MakeHALLinks("self=/projects/" + mockProjectID + "/environments/main/deployment/current"),
Links: mockapi.MakeHALLinks("self=/projects/" + projectID + "/environments/main/deployment/current"),
})

envs := []*mockapi.Environment{
main,
makeEnv(mockProjectID, "staging", "staging", "active", "main"),
makeEnv(mockProjectID, "dev", "development", "active", "staging"),
makeEnv(mockProjectID, "fix", "development", "inactive", "dev"),
makeEnv(projectID, "staging", "staging", "active", "main"),
makeEnv(projectID, "dev", "development", "active", "staging"),
makeEnv(projectID, "fix", "development", "inactive", "dev"),
}

apiHandler.SetEnvironments(envs)
Expand All @@ -60,23 +59,18 @@ func TestAppList(t *testing.T) {
assert.Equal(t, strings.TrimLeft(`
Name Type
app golang:1.23
`, "\n"), run("apps", "-p", mockProjectID, "-e", ".", "--refresh", "--format", "tsv"))
`, "\n"), run("apps", "-p", projectID, "-e", ".", "--refresh", "--format", "tsv"))

assert.Equal(t, strings.TrimLeft(`
+--------------+-------------+-------------------+
| Name | Type | Commands |
+--------------+-------------+-------------------+
| app--worker1 | golang:1.23 | start: 'sleep 60' |
+--------------+-------------+-------------------+
`, "\n"), run("workers", "-v", "-p", mockProjectID, "-e", "."))
`, "\n"), run("workers", "-v", "-p", projectID, "-e", "."))

servicesCmd := authenticatedCommand(t, apiServer.URL, authServer.URL,
"services", "-p", mockProjectID, "-e", "main")
stdErrBuf := bytes.Buffer{}
servicesCmd.Stderr = &stdErrBuf
if testing.Verbose() {
servicesCmd.Stderr = io.MultiWriter(&stdErrBuf, os.Stderr)
}
require.NoError(t, servicesCmd.Run())
assert.Contains(t, stdErrBuf.String(), "No services found")
runCombinedOutput := runnerCombinedOutput(t, apiServer.URL, authServer.URL)
co, err := runCombinedOutput("services", "-p", projectID, "-e", "main")
require.NoError(t, err)
assert.Contains(t, co, "No services found")
}
2 changes: 1 addition & 1 deletion tests/auth_info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func TestAuthInfo(t *testing.T) {
| email | my-user@example.com |
| phone_number_verified | true |
+-----------------------+---------------------+
`, "\n"), run("auth:info", "-v"))
`, "\n"), run("auth:info", "-v", "--refresh"))

assert.Equal(t, "my-user-id\n", run("auth:info", "-P", "id"))
}
63 changes: 63 additions & 0 deletions tests/org_create_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package tests

import (
"net/http/httptest"
"strings"
"testing"

"github.com/stretchr/testify/assert"

"github.com/platformsh/cli/internal/mockapi"
)

func TestOrgCreate(t *testing.T) {
authServer := mockapi.NewAuthServer(t)
defer authServer.Close()

myUserID := "user-for-org-create-test"

apiHandler := mockapi.NewHandler(t)
apiHandler.SetMyUser(&mockapi.User{ID: myUserID})
apiHandler.SetOrgs([]*mockapi.Org{
makeOrg("org-id-1", "acme", "ACME Inc.", myUserID),
})

apiServer := httptest.NewServer(apiHandler)
defer apiServer.Close()

run := runnerWithAuth(t, apiServer.URL, authServer.URL)

// TODO disable the cache?
run("cc")

assert.Equal(t, strings.TrimLeft(`
+------+-----------+--------------------------------------+
| Name | Label | Owner email |
+------+-----------+--------------------------------------+
| acme | ACME Inc. | user-for-org-create-test@example.com |
+------+-----------+--------------------------------------+
`, "\n"), run("orgs"))

runCombinedOutput := runnerCombinedOutput(t, apiServer.URL, authServer.URL)

co, err := runCombinedOutput("org:create", "--name", "hooli", "--yes")
assert.Error(t, err)
assert.Contains(t, co, "--country is required")

co, err = runCombinedOutput("org:create", "--name", "hooli", "--yes", "--country", "XY")
assert.Error(t, err)
assert.Contains(t, co, "Invalid country: XY")

co, err = runCombinedOutput("org:create", "--name", "hooli", "--yes", "--country", "US")
assert.NoError(t, err)
assert.Contains(t, co, "Hooli")

assert.Equal(t, strings.TrimLeft(`
+-------+-----------+--------------------------------------+
| Name | Label | Owner email |
+-------+-----------+--------------------------------------+
| acme | ACME Inc. | user-for-org-create-test@example.com |
| hooli | Hooli | user-for-org-create-test@example.com |
+-------+-----------+--------------------------------------+
`, "\n"), run("orgs"))
}
44 changes: 44 additions & 0 deletions tests/org_info_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package tests

import (
"net/http/httptest"
"testing"

"github.com/stretchr/testify/assert"

"github.com/platformsh/cli/internal/mockapi"
)

func TestOrgInfo(t *testing.T) {
authServer := mockapi.NewAuthServer(t)
defer authServer.Close()

myUserID := "user-for-org-info-test"

apiHandler := mockapi.NewHandler(t)
apiHandler.SetMyUser(&mockapi.User{ID: myUserID})
apiServer := httptest.NewServer(apiHandler)
defer apiServer.Close()

apiHandler.SetOrgs([]*mockapi.Org{
makeOrg("org-id-1", "org-1", "Org 1", myUserID),
})

run := runnerWithAuth(t, apiServer.URL, authServer.URL)

assert.Contains(t, run("org:info", "-o", "org-1", "--format", "csv", "--refresh"), `Property,Value
id,org-id-1
name,org-1
label,Org 1
owner_id,user-for-org-info-test
capabilities,`)

assert.Equal(t, "Org 1\n", run("org:info", "-o", "org-1", "label"))

runCombinedOutput := runnerCombinedOutput(t, apiServer.URL, authServer.URL)
co, err := runCombinedOutput("org:info", "-o", "org-1", "label", "New Label")
assert.NoError(t, err)
assert.Contains(t, co, "Property label set to: New Label\n")

assert.Equal(t, "New Label\n", run("org:info", "-o", "org-1", "label"))
}
17 changes: 9 additions & 8 deletions tests/org_list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,31 +35,32 @@ func TestOrgList(t *testing.T) {
| Name | Label | Owner email |
+--------------+--------------------------------+-----------------------+
| acme | ACME Inc. | user-id-1@example.com |
| four-seasons | Four Seasons Total Landscaping | user-id-1@example.com |
| duff | Duff Beer | user-id-2@example.com |
| four-seasons | Four Seasons Total Landscaping | user-id-1@example.com |
+--------------+--------------------------------+-----------------------+
`, "\n"), run("orgs"))

assert.Equal(t, strings.TrimLeft(`
Name Label Owner email
acme ACME Inc. user-id-1@example.com
four-seasons Four Seasons Total Landscaping user-id-1@example.com
duff Duff Beer user-id-2@example.com
four-seasons Four Seasons Total Landscaping user-id-1@example.com
`, "\n"), run("orgs", "--format", "plain"))

assert.Equal(t, strings.TrimLeft(`
org-id-1,acme
org-id-2,four-seasons
org-id-3,duff
org-id-2,four-seasons
`, "\n"), run("orgs", "--format", "csv", "--columns", "id,name", "--no-header"))
}

func makeOrg(id, name, label, owner string) *mockapi.Org {
return &mockapi.Org{
ID: id,
Name: name,
Label: label,
Owner: owner,
Links: mockapi.MakeHALLinks("self=/organizations/" + url.PathEscape(id)),
ID: id,
Name: name,
Label: label,
Owner: owner,
Capabilities: []string{},
Links: mockapi.MakeHALLinks("self=/organizations/" + url.PathEscape(id)),
}
}
14 changes: 14 additions & 0 deletions tests/tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package tests

import (
"bytes"
"os"
"os/exec"
"path/filepath"
Expand Down Expand Up @@ -75,6 +76,7 @@ func authenticatedCommand(t *testing.T, apiURL, authURL string, args ...string)
}

// runnerWithAuth returns a function to authenticate and run a CLI command, returning stdout output.
// This asserts that the command has not failed.
func runnerWithAuth(t *testing.T, apiURL, authURL string) func(args ...string) string {
return func(args ...string) string {
cmd := authenticatedCommand(t, apiURL, authURL, args...)
Expand All @@ -84,6 +86,18 @@ func runnerWithAuth(t *testing.T, apiURL, authURL string) func(args ...string) s
}
}

// runnerCombinedOutput returns a function to authenticate and run a CLI command, returning combined output.
func runnerCombinedOutput(t *testing.T, apiURL, authURL string) func(args ...string) (string, error) {
return func(args ...string) (string, error) {
cmd := authenticatedCommand(t, apiURL, authURL, args...)
var b bytes.Buffer
cmd.Stdout = &b
cmd.Stderr = &b
err := cmd.Run()
return b.String(), err
}
}

const EnvPrefix = "TEST_CLI_"

func testEnv() []string {
Expand Down

0 comments on commit 31c7381

Please sign in to comment.