145 lines
4.2 KiB
Go
145 lines
4.2 KiB
Go
|
|
//go:build e2e
|
||
|
|
|
||
|
|
package e2e
|
||
|
|
|
||
|
|
import (
|
||
|
|
"bytes"
|
||
|
|
"encoding/json"
|
||
|
|
"fmt"
|
||
|
|
"io"
|
||
|
|
"net/http"
|
||
|
|
"os"
|
||
|
|
"testing"
|
||
|
|
|
||
|
|
"github.com/stretchr/testify/require"
|
||
|
|
)
|
||
|
|
|
||
|
|
const successCode = 102000
|
||
|
|
|
||
|
|
// Fixture holds seed output from cmd/e2e-seed.
|
||
|
|
type Fixture struct {
|
||
|
|
BaseURL string `json:"base_url"`
|
||
|
|
TenantID string `json:"tenant_id"`
|
||
|
|
TenantSlug string `json:"tenant_slug"`
|
||
|
|
UID string `json:"uid"`
|
||
|
|
Email string `json:"email"`
|
||
|
|
AccessToken string `json:"access_token"`
|
||
|
|
RefreshToken string `json:"refresh_token"`
|
||
|
|
RoleKey string `json:"role_key"`
|
||
|
|
NoRoleUID string `json:"no_role_uid"`
|
||
|
|
NoRoleEmail string `json:"no_role_email"`
|
||
|
|
NoRoleAccessToken string `json:"no_role_access_token"`
|
||
|
|
NoRoleRefreshToken string `json:"no_role_refresh_token"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// Client is a thin HTTP helper for Gateway E2E tests.
|
||
|
|
type Client struct {
|
||
|
|
BaseURL string
|
||
|
|
HTTP *http.Client
|
||
|
|
Fixture Fixture
|
||
|
|
}
|
||
|
|
|
||
|
|
// Envelope matches gateway/types.Status JSON.
|
||
|
|
type Envelope struct {
|
||
|
|
Code int64 `json:"code"`
|
||
|
|
Message string `json:"message"`
|
||
|
|
Data json.RawMessage `json:"data"`
|
||
|
|
Error json.RawMessage `json:"error,omitempty"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// LoadFixture returns the shared bootstrap fixture (refreshed once in TestMain).
|
||
|
|
func LoadFixture(t *testing.T) Fixture {
|
||
|
|
t.Helper()
|
||
|
|
fx := loadFixture()
|
||
|
|
require.NotEmpty(t, fx.BaseURL)
|
||
|
|
require.NotEmpty(t, fx.AccessToken)
|
||
|
|
return fx
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewClient builds a client from the shared fixture.
|
||
|
|
func NewClient(t *testing.T) *Client {
|
||
|
|
t.Helper()
|
||
|
|
return freshClient(t)
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewNoRoleClient builds a client for the seeded member that intentionally
|
||
|
|
// has no role assignment. It is used by Casbin-enabled authorization tests.
|
||
|
|
func NewNoRoleClient(t *testing.T) *Client {
|
||
|
|
t.Helper()
|
||
|
|
c := freshClient(t)
|
||
|
|
require.NotEmpty(t, c.Fixture.NoRoleUID)
|
||
|
|
require.NotEmpty(t, c.Fixture.NoRoleAccessToken)
|
||
|
|
c.Fixture.UID = c.Fixture.NoRoleUID
|
||
|
|
c.Fixture.Email = c.Fixture.NoRoleEmail
|
||
|
|
c.Fixture.AccessToken = c.Fixture.NoRoleAccessToken
|
||
|
|
c.Fixture.RefreshToken = c.Fixture.NoRoleRefreshToken
|
||
|
|
return c
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *Client) URL(path string) string {
|
||
|
|
return c.BaseURL + path
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *Client) Do(t *testing.T, method, path string, body any, auth bool) (*http.Response, Envelope) {
|
||
|
|
t.Helper()
|
||
|
|
var r io.Reader
|
||
|
|
if body != nil {
|
||
|
|
raw, err := json.Marshal(body)
|
||
|
|
require.NoError(t, err)
|
||
|
|
r = bytes.NewReader(raw)
|
||
|
|
}
|
||
|
|
req, err := http.NewRequest(method, c.URL(path), r)
|
||
|
|
require.NoError(t, err)
|
||
|
|
if body != nil {
|
||
|
|
req.Header.Set("Content-Type", "application/json")
|
||
|
|
}
|
||
|
|
if auth {
|
||
|
|
req.Header.Set("Authorization", "Bearer "+c.Fixture.AccessToken)
|
||
|
|
}
|
||
|
|
resp, err := c.HTTP.Do(req)
|
||
|
|
require.NoError(t, err)
|
||
|
|
defer resp.Body.Close()
|
||
|
|
respBody, err := io.ReadAll(resp.Body)
|
||
|
|
require.NoError(t, err)
|
||
|
|
var env Envelope
|
||
|
|
require.NoError(t, json.Unmarshal(respBody, &env), "body=%s", string(respBody))
|
||
|
|
return resp, env
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *Client) DoExpectOK(t *testing.T, method, path string, body any, auth bool) Envelope {
|
||
|
|
t.Helper()
|
||
|
|
resp, env := c.Do(t, method, path, body, auth)
|
||
|
|
require.Equal(t, http.StatusOK, resp.StatusCode, "path=%s code=%d body=%s", path, env.Code, string(mustRaw(env)))
|
||
|
|
require.Equal(t, int64(successCode), env.Code, "path=%s message=%s", path, env.Message)
|
||
|
|
return env
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *Client) DoExpectHTTP(t *testing.T, method, path string, body any, auth bool, httpStatus int) Envelope {
|
||
|
|
t.Helper()
|
||
|
|
resp, env := c.Do(t, method, path, body, auth)
|
||
|
|
require.Equal(t, httpStatus, resp.StatusCode, "path=%s env=%+v", path, env)
|
||
|
|
return env
|
||
|
|
}
|
||
|
|
|
||
|
|
func mustRaw(env Envelope) []byte {
|
||
|
|
if len(env.Data) == 0 {
|
||
|
|
return []byte(env.Message)
|
||
|
|
}
|
||
|
|
return env.Data
|
||
|
|
}
|
||
|
|
|
||
|
|
// FetchE2EOTP reads OTP plain code stashed by verify_helper when GATEWAY_E2E=1.
|
||
|
|
func FetchE2EOTP(t *testing.T, challengeID string) string {
|
||
|
|
t.Helper()
|
||
|
|
if cli := os.Getenv("REDISCLI"); cli != "" {
|
||
|
|
out, err := runCmd(cli, "-c", fmt.Sprintf("GET e2e:otp:%s", challengeID))
|
||
|
|
require.NoError(t, err)
|
||
|
|
require.NotEmpty(t, out, "missing e2e otp for challenge %s", challengeID)
|
||
|
|
return out
|
||
|
|
}
|
||
|
|
out, err := runCmd("docker", "exec", "gateway-redis", "redis-cli", "GET", "e2e:otp:"+challengeID)
|
||
|
|
require.NoError(t, err, "fetch otp via docker exec (is gateway-redis running?)")
|
||
|
|
require.NotEmpty(t, out, "missing e2e otp for challenge %s", challengeID)
|
||
|
|
return out
|
||
|
|
}
|