Files
galaxy-game/gateway/internal/backendclient/rest_test.go
T
2026-05-06 10:14:55 +03:00

191 lines
6.1 KiB
Go

package backendclient_test
import (
"context"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"galaxy/gateway/internal/backendclient"
"galaxy/gateway/internal/session"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func newRESTClient(t *testing.T, server *httptest.Server) *backendclient.RESTClient {
t.Helper()
cfg := backendclient.Config{
HTTPBaseURL: server.URL,
GRPCPushURL: "passthrough://test",
GatewayClientID: "test-gateway",
HTTPTimeout: time.Second,
PushReconnectBaseBackoff: 10 * time.Millisecond,
PushReconnectMaxBackoff: 100 * time.Millisecond,
}
client, err := backendclient.NewRESTClient(cfg)
require.NoError(t, err)
t.Cleanup(func() { _ = client.Close() })
return client
}
func TestRESTClientLookupSessionReturnsActiveRecord(t *testing.T) {
t.Parallel()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, http.MethodGet, r.Method)
require.Equal(t, "/api/v1/internal/sessions/device-1", r.URL.Path)
writeJSON(t, w, http.StatusOK, map[string]any{
"device_session_id": "device-1",
"user_id": "user-1",
"status": "active",
"client_public_key": "pk-1",
"created_at": "2026-04-01T00:00:00Z",
})
}))
t.Cleanup(server.Close)
client := newRESTClient(t, server)
rec, err := client.LookupSession(context.Background(), "device-1")
require.NoError(t, err)
assert.Equal(t, session.Record{
DeviceSessionID: "device-1",
UserID: "user-1",
ClientPublicKey: "pk-1",
Status: session.StatusActive,
}, rec)
}
func TestRESTClientLookupSessionReturnsRevokedRecord(t *testing.T) {
t.Parallel()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
writeJSON(t, w, http.StatusOK, map[string]any{
"device_session_id": "device-2",
"user_id": "user-2",
"status": "revoked",
"client_public_key": "pk-2",
"created_at": "2026-04-01T00:00:00Z",
"revoked_at": "2026-04-01T00:01:00Z",
})
}))
t.Cleanup(server.Close)
client := newRESTClient(t, server)
rec, err := client.LookupSession(context.Background(), "device-2")
require.NoError(t, err)
assert.Equal(t, session.StatusRevoked, rec.Status)
require.NotNil(t, rec.RevokedAtMS)
assert.Equal(t, time.Date(2026, 4, 1, 0, 1, 0, 0, time.UTC).UnixMilli(), *rec.RevokedAtMS)
}
func TestRESTClientLookupSessionMapsNotFound(t *testing.T) {
t.Parallel()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
writeJSON(t, w, http.StatusNotFound, map[string]any{"error": map[string]any{"code": "subject_not_found", "message": "missing"}})
}))
t.Cleanup(server.Close)
client := newRESTClient(t, server)
_, err := client.LookupSession(context.Background(), "missing")
require.Error(t, err)
assert.True(t, errors.Is(err, session.ErrNotFound))
}
func TestRESTClientLookupSessionRejectsMismatchedID(t *testing.T) {
t.Parallel()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
writeJSON(t, w, http.StatusOK, map[string]any{
"device_session_id": "other",
"user_id": "user-1",
"status": "active",
"client_public_key": "pk-1",
"created_at": "2026-04-01T00:00:00Z",
})
}))
t.Cleanup(server.Close)
client := newRESTClient(t, server)
_, err := client.LookupSession(context.Background(), "device-1")
require.Error(t, err)
assert.Contains(t, err.Error(), "does not match requested")
}
func TestRESTClientSendEmailCodeForwardsAcceptLanguage(t *testing.T) {
t.Parallel()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, http.MethodPost, r.Method)
require.Equal(t, "/api/v1/public/auth/send-email-code", r.URL.Path)
require.Equal(t, "ru-RU", r.Header.Get("Accept-Language"))
writeJSON(t, w, http.StatusOK, map[string]any{"challenge_id": "challenge-1"})
}))
t.Cleanup(server.Close)
client := newRESTClient(t, server)
out, err := client.SendEmailCode(context.Background(), backendclient.SendEmailCodeInput{
Email: "user@example.com",
PreferredLanguage: "ru-RU",
})
require.NoError(t, err)
assert.Equal(t, "challenge-1", out.ChallengeID)
}
func TestRESTClientSendEmailCodeProjectsAuthError(t *testing.T) {
t.Parallel()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
writeJSON(t, w, http.StatusBadRequest, map[string]any{
"error": map[string]any{"code": "invalid_request", "message": "bad email"},
})
}))
t.Cleanup(server.Close)
client := newRESTClient(t, server)
_, err := client.SendEmailCode(context.Background(), backendclient.SendEmailCodeInput{Email: "user@example.com"})
require.Error(t, err)
var authErr *backendclient.AuthError
require.ErrorAs(t, err, &authErr)
assert.Equal(t, http.StatusBadRequest, authErr.StatusCode)
assert.Equal(t, "invalid_request", authErr.Code)
assert.Equal(t, "bad email", authErr.Message)
}
func TestRESTClientConfirmEmailCodeReturnsDeviceSession(t *testing.T) {
t.Parallel()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, "/api/v1/public/auth/confirm-email-code", r.URL.Path)
var body backendclient.ConfirmEmailCodeInput
require.NoError(t, json.NewDecoder(r.Body).Decode(&body))
assert.Equal(t, "challenge-1", body.ChallengeID)
writeJSON(t, w, http.StatusOK, map[string]any{"device_session_id": "device-1"})
}))
t.Cleanup(server.Close)
client := newRESTClient(t, server)
out, err := client.ConfirmEmailCode(context.Background(), backendclient.ConfirmEmailCodeInput{
ChallengeID: "challenge-1",
Code: "12345",
})
require.NoError(t, err)
assert.Equal(t, "device-1", out.DeviceSessionID)
}
func writeJSON(t *testing.T, w http.ResponseWriter, status int, body any) {
t.Helper()
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
require.NoError(t, json.NewEncoder(w).Encode(body))
}
// guard ensures package keeps testify dependency.
var _ = strings.TrimSpace