Skip to content

Commit

Permalink
Add integration tests in Go for important commands
Browse files Browse the repository at this point in the history
  • Loading branch information
pjcdawkins committed Oct 12, 2024
1 parent d4f0f09 commit a961dba
Show file tree
Hide file tree
Showing 29 changed files with 1,373 additions and 84 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,6 @@ jobs:
uses: golangci/golangci-lint-action@v3
with:
version: v1.59

- name: Run tests
run: make test
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ php-*
!internal/legacy/archives/windows_php.ini.tpl
completion

# Executable built with `go build ./cmd/platform` (or `make platform`)
/platform

# Binaries for programs and plugins
*.exe
*.exe~
Expand Down
20 changes: 8 additions & 12 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ stages:
- check
- php
- build
- behave-test
- integration-test
- release

.go-cache:
Expand Down Expand Up @@ -127,18 +127,14 @@ build:
- dist/
expire_in: 1 day

behave-test-linux:
stage: behave-test
integration-test-linux:
stage: integration-test
image: cimg/go:1.22
extends: .go-cache
dependencies:
- build
variables:
PATH_CLI: platform_linux_amd64_v1/platform
before_script:
- apt-get install -y python3 python3-pip
- pip3 install --no-cache-dir behave sh selenium requests
script:
- bash tests/test-behave.sh
image: pjcdawkins/platformsh-cli
script: |
TEST_CLI_PATH=$PWD/dist/platform_linux_amd64_v1/platform go test -v ./tests/...
release:
stage: release
Expand All @@ -152,7 +148,7 @@ release:
- make release
dependencies:
- unit-test-lint
- behave-test-linux
- integration-test-linux
- build-php-linux-arm
- build-php-linux-x86
- build-php-macos-arm
Expand Down
11 changes: 9 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ php: $(PHP_BINARY_PATH)

.PHONY: goreleaser
goreleaser:
go install github.com/goreleaser/goreleaser@$(GORELEASER_VERSION)
command -v goreleaser >/dev/null || go install github.com/goreleaser/goreleaser@$(GORELEASER_VERSION)

.PHONY: single
single: goreleaser internal/legacy/archives/platform.phar php ## Build a single target release for Platform.sh or Upsun
Expand All @@ -86,8 +86,15 @@ test: ## Run unit tests
go clean -testcache
go test -v -race -mod=readonly -cover ./...

platform: internal/legacy/archives/platform.phar php
go build ./cmd/platform

.PHONY: integration-test
integration-test: platform
TEST_CLI_PATH="$(PWD)/platform" go test -v ./tests/...

golangci-lint:
go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION)
command -v golangci-lint >/dev/null || go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION)

.PHONY: lint
lint: golangci-lint ## Run linter
Expand Down
2 changes: 1 addition & 1 deletion commands/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ import (
"os/exec"
"path/filepath"
"regexp"
"slices"
"strings"

"github.com/fatih/color"
"github.com/platformsh/platformify/commands"
"github.com/platformsh/platformify/vendorization"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"golang.org/x/exp/slices"

"github.com/platformsh/cli/internal"
"github.com/platformsh/cli/internal/config"
Expand Down
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ toolchain go1.22.4

require (
github.com/fatih/color v1.17.0
github.com/go-chi/chi/v5 v5.1.0
github.com/go-playground/validator/v10 v10.20.0
github.com/gofrs/flock v0.8.1
github.com/mattn/go-isatty v0.0.20
Expand All @@ -14,7 +15,7 @@ require (
github.com/spf13/viper v1.19.0
github.com/stretchr/testify v1.9.0
github.com/wk8/go-ordered-map/v2 v2.1.8
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
golang.org/x/crypto v0.24.0
gopkg.in/yaml.v3 v3.0.1
)

Expand Down Expand Up @@ -58,7 +59,7 @@ require (
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.24.0 // indirect
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/term v0.21.0 // indirect
Expand Down
6 changes: 2 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
Expand Down Expand Up @@ -92,10 +94,6 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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.9 h1:JKakEM6kY3p+IG3/J427Cw8T97pyPnPMomx9FzWuvgE=
github.com/platformsh/platformify v0.2.9/go.mod h1:fgmCcfQfHbhe1oXsIdIhpnniyZu8IdIMOlcBAa/ygic=
github.com/platformsh/platformify v0.2.10 h1:5/b5hXpXWV0rVswstvx1fSmE7c7qaYs3u2pICDCcA3E=
github.com/platformsh/platformify v0.2.10/go.mod h1:fgmCcfQfHbhe1oXsIdIhpnniyZu8IdIMOlcBAa/ygic=
github.com/platformsh/platformify v0.2.11 h1:9TRej4tDgQahRfl1tDOGaCry79yXYXbzDR1ZMdOPsU8=
github.com/platformsh/platformify v0.2.11/go.mod h1:fgmCcfQfHbhe1oXsIdIhpnniyZu8IdIMOlcBAa/ygic=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
Expand Down
73 changes: 73 additions & 0 deletions internal/mockapi/api_server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Package mockapi provides mocks of the HTTP API for use in integration tests.
package mockapi

import (
"encoding/json"
"net/http"
"strings"
"testing"

"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/stretchr/testify/require"
)

type Handler struct {
*chi.Mux

t *testing.T

store
}

func NewHandler(t *testing.T) *Handler {
h := &Handler{t: t}
h.Mux = chi.NewRouter()

if testing.Verbose() {
h.Mux.Use(middleware.DefaultLogger)
}

h.Mux.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
authHeader := req.Header.Get("Authorization")
require.NotEmpty(t, authHeader)
require.True(t, strings.HasPrefix(authHeader, "Bearer "))
next.ServeHTTP(w, req)
})
})

h.Mux.Get("/users/me", h.handleUsersMe)
h.Mux.Get("/users/{id}/extended-access", h.handleUserExtendedAccess)
h.Mux.Get("/ref/users", h.handleUserRefs)
h.Mux.Post("/me/verification", func(w http.ResponseWriter, _ *http.Request) {
_ = json.NewEncoder(w).Encode(map[string]any{"state": false, "type": ""})
})

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

h.Mux.Post("/organizations/{id}/subscriptions", h.handleCreateSubscription)
h.Mux.Get("/subscriptions/{id}", h.handleGetSubscription)
h.Mux.Get("/organizations/{id}/setup/options", func(w http.ResponseWriter, _ *http.Request) {
type options struct {
Plans []string `json:"plans"`
Regions []string `json:"regions"`
}
_ = json.NewEncoder(w).Encode(options{[]string{"development"}, []string{"test-region"}})
})
h.Mux.Get("/organizations/{id}/subscriptions/estimate", func(w http.ResponseWriter, _ *http.Request) {
_ = json.NewEncoder(w).Encode(map[string]any{"total": "$1,000 USD"})
})

h.Mux.Get("/projects/{id}", h.handleGetProject)
h.Mux.Get("/projects/{id}/environments", h.handleListEnvironments)
h.Mux.Get("/projects/{project_id}/environments/{environment_id}/deployments/current", h.handleGetCurrentDeployment)
h.Mux.Get("/ref/projects", h.handleProjectRefs)

h.Mux.Get("/regions", h.handleListRegions)

return h
}
107 changes: 107 additions & 0 deletions internal/mockapi/auth_server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package mockapi

import (
"crypto/ed25519"
"crypto/rand"
"encoding/json"
"net/http"
"net/http/httptest"
"slices"
"testing"
"time"

"github.com/stretchr/testify/require"
"golang.org/x/crypto/ssh"
)

var ValidAPITokens = []string{"api-token-1"}
var accessTokens = []string{"access-token-1"}

// NewAuthServer creates a new mock authentication server.
// The caller must call Close() on the server when finished.
func NewAuthServer(t *testing.T) *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
if testing.Verbose() {
t.Log(req)
}
if req.Method == http.MethodPost && req.URL.Path == "/oauth2/token" {
require.NoError(t, req.ParseForm())
if gt := req.Form.Get("grant_type"); gt != "api_token" {
w.WriteHeader(http.StatusBadRequest)
_ = json.NewEncoder(w).Encode(map[string]string{"error": "invalid grant type: " + gt})
return
}
apiToken := req.Form.Get("api_token")
if slices.Contains(ValidAPITokens, apiToken) {
_ = json.NewEncoder(w).Encode(struct {
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
Type string `json:"token_type"`
}{AccessToken: accessTokens[0], ExpiresIn: 60, Type: "bearer"})
return
}
w.WriteHeader(http.StatusBadRequest)
_ = json.NewEncoder(w).Encode(map[string]string{"error": "invalid API token"})
return
}

if req.Method == http.MethodPost && req.URL.Path == "/ssh" {
var options struct {
PublicKey string `json:"key"`
}
err := json.NewDecoder(req.Body).Decode(&options)
require.NoError(t, err)
key, _, _, _, err := ssh.ParseAuthorizedKey([]byte(options.PublicKey))
require.NoError(t, err)
signer, err := sshSigner()
require.NoError(t, err)
extensions := make(map[string]string)

// Add standard ssh options
extensions["permit-X11-forwarding"] = ""
extensions["permit-agent-forwarding"] = ""
extensions["permit-port-forwarding"] = ""
extensions["permit-pty"] = ""
extensions["permit-user-rc"] = ""
cert := &ssh.Certificate{
Key: key,
Serial: 0,
CertType: ssh.UserCert,
KeyId: "test-key-id",
ValidAfter: uint64(time.Now().Add(-1 * time.Second).Unix()),
ValidBefore: uint64(time.Now().Add(time.Minute).Unix()),
Permissions: ssh.Permissions{
Extensions: extensions,
},
}
err = cert.SignCert(rand.Reader, signer)
require.NoError(t, err)
_ = json.NewEncoder(w).Encode(struct {
Cert string `json:"certificate"`
}{string(ssh.MarshalAuthorizedKey(cert))})
require.NoError(t, err)
return
}

w.WriteHeader(http.StatusNotFound)
_ = json.NewEncoder(w).Encode(map[string]string{"error": "not found"})
}))
}

var signer ssh.Signer // TODO reuse to validate SSH connection

func sshSigner() (ssh.Signer, error) {
if signer != nil {
return signer, nil
}
_, privateKey, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
return nil, err
}
s, err := ssh.NewSignerFromKey(privateKey)
if err != nil {
return nil, err
}
signer = s
return s, nil
}
Loading

0 comments on commit a961dba

Please sign in to comment.