tests/request_binding_test.go
Rama master · réplica de lectura del repositorio original.
package main
import (
"net/http"
"testing"
"github.com/go-resty/resty/v2"
"github.com/shellhub-io/shellhub/pkg/api/authorizer"
"github.com/shellhub-io/shellhub/pkg/api/requests"
"github.com/shellhub-io/shellhub/pkg/models"
"github.com/shellhub-io/shellhub/tests/environment"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const (
secondUsername = "other"
secondEmail = "other@shellhub.io"
secondPassword = "other-password"
secondNamespace = "otherspace"
secondTenant = "11111111-1111-4111-8111-111111111111"
)
func seedTwoNamespaces(t *testing.T, compose *environment.DockerCompose) {
t.Helper()
compose.NewUser(t, ShellHubUsername, ShellHubEmail, ShellHubPassword)
compose.NewNamespace(t, ShellHubUsername, ShellHubNamespaceName, ShellHubNamespace)
compose.NewUser(t, secondUsername, secondEmail, secondPassword)
compose.NewNamespace(t, secondUsername, secondNamespace, secondTenant)
}
func TestRequestFieldsKeepTheirDeclaredSource(t *testing.T) {
t.Parallel()
compose := environment.New(t, run).Up(t.Context())
t.Cleanup(compose.Down)
seedTwoNamespaces(t, compose)
caller := compose.AuthUser(t, ShellHubUsername, ShellHubPassword)
other := compose.AuthUser(t, secondUsername, secondPassword)
compose.JWT(caller.Token)
key := compose.CreateAPIKey(t, &requests.CreateAPIKey{
Name: "automation",
ExpiresAt: -1,
OptRole: authorizer.RoleAdministrator,
})
compose.JWT(other.Token)
before := compose.GetNamespace(t, secondTenant)
client := resty.New().SetBaseURL(compose.BaseURL()).SetAllowGetMethodPayload(true)
asOwner := func(t *testing.T) *resty.Request {
t.Helper()
return client.R().SetContext(t.Context()).SetAuthToken(caller.Token)
}
asAPIKey := func(t *testing.T) *resty.Request {
t.Helper()
return client.R().SetContext(t.Context()).SetHeader("X-API-Key", key.Key)
}
cases := []struct {
description string
request func(*testing.T) *resty.Request
method string
path string
body map[string]any
status int
}{
{
description: "PUT /api/namespaces/:tenant reads the tenant from the path",
request: asOwner,
method: http.MethodPut,
path: "/api/namespaces/" + ShellHubNamespace,
body: map[string]any{"Tenant": secondTenant, "name": "renamed", "settings": map[string]any{"session_record": false}},
status: http.StatusUnprocessableEntity,
},
{
description: "PUT /api/users/security/:tenant reads the tenant from the path",
request: asOwner,
method: http.MethodPut,
path: "/api/users/security/" + ShellHubNamespace,
body: map[string]any{"Tenant": secondTenant, "session_record": false},
status: http.StatusUnprocessableEntity,
},
{
description: "GET /api/namespaces/:tenant/members reads the tenant from the path",
request: asOwner,
method: http.MethodGet,
path: "/api/namespaces/" + ShellHubNamespace + "/members",
body: map[string]any{"TenantID": secondTenant},
status: http.StatusUnprocessableEntity,
},
{
description: "GET /api/namespaces/:tenant reads the tenant from the path for an API key",
request: asAPIKey,
method: http.MethodGet,
path: "/api/namespaces/" + ShellHubNamespace,
body: map[string]any{"Tenant": secondTenant},
status: http.StatusUnprocessableEntity,
},
{
description: "POST /api/namespaces/:tenant/members reads the user from the identity",
request: asAPIKey,
method: http.MethodPost,
path: "/api/namespaces/" + ShellHubNamespace + "/members",
body: map[string]any{"UserID": other.ID, "email": "member@shellhub.io", "role": "administrator"},
status: http.StatusUnprocessableEntity,
},
{
description: "GET /api/namespaces/:tenant/invitations reads the user from the identity",
request: asAPIKey,
method: http.MethodGet,
path: "/api/namespaces/" + ShellHubNamespace + "/invitations",
body: map[string]any{"UserID": other.ID},
status: http.StatusUnprocessableEntity,
},
{
description: "POST /api/namespaces/:tenant/members needs a user an API key does not have",
request: asAPIKey,
method: http.MethodPost,
path: "/api/namespaces/" + ShellHubNamespace + "/members",
body: map[string]any{"email": "member@shellhub.io", "role": "administrator"},
status: http.StatusBadRequest,
},
{
description: "DELETE /api/namespaces/:tenant reads the tenant from the path",
request: asOwner,
method: http.MethodDelete,
path: "/api/namespaces/" + ShellHubNamespace,
body: map[string]any{"Tenant": secondTenant},
status: http.StatusUnprocessableEntity,
},
}
for _, tc := range cases {
t.Run(tc.description, func(t *testing.T) {
resp, err := tc.request(t).SetBody(tc.body).Execute(tc.method, tc.path)
require.NoError(t, err)
assert.Equal(t, tc.status, resp.StatusCode(), resp.String())
assert.NotContains(t, resp.String(), secondNamespace)
})
}
compose.JWT(other.Token)
after := compose.GetNamespace(t, secondTenant)
assert.Equal(t, before.Name, after.Name)
assert.Equal(t, before.Settings, after.Settings)
compose.JWT(caller.Token)
assert.Equal(t, ShellHubNamespaceName, compose.GetNamespace(t, ShellHubNamespace).Name)
}
func TestMemberRoutesActOnTheTokenNamespace(t *testing.T) {
t.Parallel()
compose := environment.New(t, run).Up(t.Context())
t.Cleanup(compose.Down)
seedTwoNamespaces(t, compose)
compose.NewMember(t, ShellHubUsername, secondNamespace, string(authorizer.RoleAdministrator))
login := compose.AuthUser(t, ShellHubUsername, ShellHubPassword)
scoped := new(models.UserAuthResponse)
resp, err := compose.Anonymous(t.Context()).SetAuthToken(login.Token).SetResult(scoped).Get("/api/auth/token/" + ShellHubNamespace)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode(), resp.String())
require.NotNil(t, scoped.Tenant)
require.Equal(t, ShellHubNamespace, *scoped.Tenant)
invite := map[string]any{"email": "member@shellhub.io", "role": "observer"}
for _, path := range []string{
"/api/namespaces/" + secondTenant + "/members",
"/api/namespaces/" + secondTenant + "/invitations/links",
} {
t.Run("POST "+path, func(t *testing.T) {
resp, err := compose.Anonymous(t.Context()).SetAuthToken(scoped.Token).SetBody(invite).Post(path)
require.NoError(t, err)
assert.Equal(t, http.StatusForbidden, resp.StatusCode(), resp.String())
})
}
compose.JWT(compose.AuthUser(t, secondUsername, secondPassword).Token)
resp, err = compose.R(t.Context()).Get("/api/namespaces/" + secondTenant + "/invitations")
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode(), resp.String())
assert.NotContains(t, resp.String(), "member@shellhub.io")
}