template-monorepo/test/e2e/client.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
}