feat: backend service
This commit is contained in:
+91
-78
@@ -4,18 +4,16 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"galaxy/gateway/internal/adminapi"
|
||||
"galaxy/gateway/internal/app"
|
||||
"galaxy/gateway/internal/authn"
|
||||
"galaxy/gateway/authn"
|
||||
"galaxy/gateway/internal/backendclient"
|
||||
"galaxy/gateway/internal/config"
|
||||
"galaxy/gateway/internal/downstream"
|
||||
"galaxy/gateway/internal/downstream/lobbyservice"
|
||||
"galaxy/gateway/internal/downstream/userservice"
|
||||
"galaxy/gateway/internal/events"
|
||||
"galaxy/gateway/internal/grpcapi"
|
||||
"galaxy/gateway/internal/logging"
|
||||
@@ -60,16 +58,29 @@ func run(ctx context.Context) (err error) {
|
||||
return fmt.Errorf("build gateway telemetry: %w", err)
|
||||
}
|
||||
|
||||
publicRESTDeps, closePublicRESTDeps, err := newPublicRESTDependencies(cfg, logger, telemetryRuntime)
|
||||
backend, err := backendclient.NewClient(backendclient.Config{
|
||||
HTTPBaseURL: cfg.Backend.HTTPBaseURL,
|
||||
GRPCPushURL: cfg.Backend.GRPCPushURL,
|
||||
GatewayClientID: cfg.Backend.GatewayClientID,
|
||||
HTTPTimeout: cfg.Backend.HTTPTimeout,
|
||||
PushReconnectBaseBackoff: cfg.Backend.PushReconnectBaseBackoff,
|
||||
PushReconnectMaxBackoff: cfg.Backend.PushReconnectMaxBackoff,
|
||||
})
|
||||
if err != nil {
|
||||
_ = telemetryRuntime.Shutdown(context.Background())
|
||||
_ = logging.Sync(logger)
|
||||
return err
|
||||
return fmt.Errorf("build backend client: %w", err)
|
||||
}
|
||||
|
||||
grpcDeps, components, cleanup, err := newAuthenticatedGRPCDependencies(ctx, cfg, logger, telemetryRuntime)
|
||||
publicRESTDeps := restapi.ServerDependencies{
|
||||
Logger: logger,
|
||||
Telemetry: telemetryRuntime,
|
||||
AuthService: authServiceAdapter{rest: backend.REST()},
|
||||
}
|
||||
|
||||
grpcDeps, components, cleanup, err := newAuthenticatedGRPCDependencies(ctx, cfg, logger, telemetryRuntime, backend)
|
||||
if err != nil {
|
||||
_ = closePublicRESTDeps()
|
||||
_ = backend.Close()
|
||||
_ = telemetryRuntime.Shutdown(context.Background())
|
||||
_ = logging.Sync(logger)
|
||||
return err
|
||||
@@ -80,8 +91,8 @@ func run(ctx context.Context) (err error) {
|
||||
|
||||
err = errors.Join(
|
||||
err,
|
||||
closePublicRESTDeps(),
|
||||
cleanup(),
|
||||
backend.Close(),
|
||||
telemetryRuntime.Shutdown(shutdownCtx),
|
||||
logging.Sync(logger),
|
||||
)
|
||||
@@ -103,6 +114,8 @@ func run(ctx context.Context) (err error) {
|
||||
zap.String("public_http_addr", cfg.PublicHTTP.Addr),
|
||||
zap.String("authenticated_grpc_addr", cfg.AuthenticatedGRPC.Addr),
|
||||
zap.String("admin_http_addr", cfg.AdminHTTP.Addr),
|
||||
zap.String("backend_http_url", cfg.Backend.HTTPBaseURL),
|
||||
zap.String("backend_grpc_push_url", cfg.Backend.GRPCPushURL),
|
||||
)
|
||||
|
||||
application := app.New(cfg, applicationComponents...)
|
||||
@@ -111,26 +124,7 @@ func run(ctx context.Context) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
func newPublicRESTDependencies(cfg config.Config, logger *zap.Logger, telemetryRuntime *telemetry.Runtime) (restapi.ServerDependencies, func() error, error) {
|
||||
deps := restapi.ServerDependencies{
|
||||
Logger: logger,
|
||||
Telemetry: telemetryRuntime,
|
||||
}
|
||||
|
||||
if cfg.AuthService.BaseURL == "" {
|
||||
return deps, errNoopClose, nil
|
||||
}
|
||||
|
||||
authService, err := restapi.NewHTTPAuthServiceClient(cfg.AuthService.BaseURL)
|
||||
if err != nil {
|
||||
return restapi.ServerDependencies{}, nil, fmt.Errorf("build public REST dependencies: auth service client: %w", err)
|
||||
}
|
||||
|
||||
deps.AuthService = authService
|
||||
return deps, authService.Close, nil
|
||||
}
|
||||
|
||||
func newAuthenticatedGRPCDependencies(ctx context.Context, cfg config.Config, logger *zap.Logger, telemetryRuntime *telemetry.Runtime) (grpcapi.ServerDependencies, []app.Component, func() error, error) {
|
||||
func newAuthenticatedGRPCDependencies(ctx context.Context, cfg config.Config, logger *zap.Logger, telemetryRuntime *telemetry.Runtime, backend *backendclient.Client) (grpcapi.ServerDependencies, []app.Component, func() error, error) {
|
||||
responseSigner, err := authn.LoadEd25519ResponseSignerFromPEMFile(cfg.ResponseSigner.PrivateKeyPEMPath)
|
||||
if err != nil {
|
||||
return grpcapi.ServerDependencies{}, nil, nil, fmt.Errorf("build authenticated grpc dependencies: load response signer: %w", err)
|
||||
@@ -159,7 +153,7 @@ func newAuthenticatedGRPCDependencies(ctx context.Context, cfg config.Config, lo
|
||||
)
|
||||
}
|
||||
|
||||
fallbackSessionCache, err := session.NewRedisCache(redisClient, cfg.SessionCacheRedis)
|
||||
sessionCache, err := session.NewBackendCache(backend.REST())
|
||||
if err != nil {
|
||||
return grpcapi.ServerDependencies{}, nil, nil, errors.Join(
|
||||
fmt.Errorf("build authenticated grpc dependencies: %w", err),
|
||||
@@ -175,59 +169,25 @@ func newAuthenticatedGRPCDependencies(ctx context.Context, cfg config.Config, lo
|
||||
)
|
||||
}
|
||||
|
||||
localSessionCache := session.NewMemoryCache()
|
||||
sessionCache, err := session.NewReadThroughCache(localSessionCache, fallbackSessionCache)
|
||||
if err != nil {
|
||||
return grpcapi.ServerDependencies{}, nil, nil, errors.Join(
|
||||
fmt.Errorf("build authenticated grpc dependencies: %w", err),
|
||||
closeRedisClient(),
|
||||
)
|
||||
}
|
||||
|
||||
pushHub := push.NewHubWithObserver(0, telemetry.NewPushObserver(telemetryRuntime))
|
||||
sessionSubscriber, err := events.NewRedisSessionSubscriberWithObservability(redisClient, cfg.SessionCacheRedis, cfg.SessionEventsRedis, localSessionCache, pushHub, logger, telemetryRuntime)
|
||||
if err != nil {
|
||||
return grpcapi.ServerDependencies{}, nil, nil, errors.Join(
|
||||
fmt.Errorf("build authenticated grpc dependencies: %w", err),
|
||||
closeRedisClient(),
|
||||
)
|
||||
}
|
||||
|
||||
clientEventSubscriber, err := events.NewRedisClientEventSubscriberWithObservability(redisClient, cfg.SessionCacheRedis, cfg.ClientEventsRedis, pushHub, logger, telemetryRuntime)
|
||||
if err != nil {
|
||||
return grpcapi.ServerDependencies{}, nil, nil, errors.Join(
|
||||
fmt.Errorf("build authenticated grpc dependencies: %w", err),
|
||||
closeRedisClient(),
|
||||
)
|
||||
}
|
||||
|
||||
userRoutes, closeUserServiceRoutes, err := userservice.NewRoutes(cfg.UserService.BaseURL)
|
||||
if err != nil {
|
||||
return grpcapi.ServerDependencies{}, nil, nil, errors.Join(
|
||||
fmt.Errorf("build authenticated grpc dependencies: user service routes: %w", err),
|
||||
closeRedisClient(),
|
||||
)
|
||||
}
|
||||
|
||||
lobbyRoutes, closeLobbyServiceRoutes, err := lobbyservice.NewRoutes(cfg.LobbyService.BaseURL)
|
||||
if err != nil {
|
||||
return grpcapi.ServerDependencies{}, nil, nil, errors.Join(
|
||||
fmt.Errorf("build authenticated grpc dependencies: lobby service routes: %w", err),
|
||||
closeUserServiceRoutes(),
|
||||
closeRedisClient(),
|
||||
)
|
||||
}
|
||||
dispatcher := events.NewDispatcher(pushHub, pushHub, logger, telemetryRuntime)
|
||||
pushClient := backend.Push().
|
||||
WithLogger(logger).
|
||||
WithHandler(dispatcher)
|
||||
|
||||
userRoutes := backendclient.UserRoutes(backend.REST())
|
||||
lobbyRoutes := backendclient.LobbyRoutes(backend.REST())
|
||||
allRoutes := make(map[string]downstream.Client, len(userRoutes)+len(lobbyRoutes))
|
||||
maps.Copy(allRoutes, userRoutes)
|
||||
maps.Copy(allRoutes, lobbyRoutes)
|
||||
for k, v := range userRoutes {
|
||||
allRoutes[k] = v
|
||||
}
|
||||
for k, v := range lobbyRoutes {
|
||||
allRoutes[k] = v
|
||||
}
|
||||
|
||||
cleanup := func() error {
|
||||
return errors.Join(
|
||||
closeLobbyServiceRoutes(),
|
||||
closeUserServiceRoutes(),
|
||||
closeRedisClient(),
|
||||
)
|
||||
return closeRedisClient()
|
||||
}
|
||||
|
||||
return grpcapi.ServerDependencies{
|
||||
@@ -239,5 +199,58 @@ func newAuthenticatedGRPCDependencies(ctx context.Context, cfg config.Config, lo
|
||||
Logger: logger,
|
||||
Telemetry: telemetryRuntime,
|
||||
PushHub: pushHub,
|
||||
}, []app.Component{sessionSubscriber, clientEventSubscriber}, cleanup, nil
|
||||
}, []app.Component{pushClient}, cleanup, nil
|
||||
}
|
||||
|
||||
// authServiceAdapter adapts backendclient.RESTClient to the
|
||||
// restapi.AuthServiceClient interface so the public REST handlers can stay
|
||||
// unchanged. The two surfaces share the same JSON wire shape; only the Go
|
||||
// type names differ.
|
||||
type authServiceAdapter struct {
|
||||
rest *backendclient.RESTClient
|
||||
}
|
||||
|
||||
func (a authServiceAdapter) SendEmailCode(ctx context.Context, input restapi.SendEmailCodeInput) (restapi.SendEmailCodeResult, error) {
|
||||
if a.rest == nil {
|
||||
return restapi.SendEmailCodeResult{}, errors.New("auth service adapter: nil backend client")
|
||||
}
|
||||
out, err := a.rest.SendEmailCode(ctx, backendclient.SendEmailCodeInput{
|
||||
Email: input.Email,
|
||||
PreferredLanguage: input.PreferredLanguage,
|
||||
})
|
||||
if err != nil {
|
||||
return restapi.SendEmailCodeResult{}, mapAuthError(err)
|
||||
}
|
||||
return restapi.SendEmailCodeResult{ChallengeID: out.ChallengeID}, nil
|
||||
}
|
||||
|
||||
func (a authServiceAdapter) ConfirmEmailCode(ctx context.Context, input restapi.ConfirmEmailCodeInput) (restapi.ConfirmEmailCodeResult, error) {
|
||||
if a.rest == nil {
|
||||
return restapi.ConfirmEmailCodeResult{}, errors.New("auth service adapter: nil backend client")
|
||||
}
|
||||
out, err := a.rest.ConfirmEmailCode(ctx, backendclient.ConfirmEmailCodeInput{
|
||||
ChallengeID: input.ChallengeID,
|
||||
Code: input.Code,
|
||||
ClientPublicKey: input.ClientPublicKey,
|
||||
TimeZone: input.TimeZone,
|
||||
})
|
||||
if err != nil {
|
||||
return restapi.ConfirmEmailCodeResult{}, mapAuthError(err)
|
||||
}
|
||||
return restapi.ConfirmEmailCodeResult{DeviceSessionID: out.DeviceSessionID}, nil
|
||||
}
|
||||
|
||||
func mapAuthError(err error) error {
|
||||
var ae *backendclient.AuthError
|
||||
if errors.As(err, &ae) {
|
||||
return &restapi.AuthServiceError{
|
||||
StatusCode: ae.StatusCode,
|
||||
Code: ae.Code,
|
||||
Message: ae.Message,
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
var _ restapi.AuthServiceClient = authServiceAdapter{}
|
||||
var _ = errNoopClose
|
||||
|
||||
+125
-266
@@ -7,14 +7,13 @@ import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"net"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"galaxy/gateway/internal/backendclient"
|
||||
"galaxy/gateway/internal/config"
|
||||
"galaxy/gateway/internal/restapi"
|
||||
"galaxy/redisconn"
|
||||
|
||||
"github.com/alicebob/miniredis/v2"
|
||||
@@ -33,284 +32,145 @@ func testRedisConn(masterAddr string, opTimeout time.Duration) redisconn.Config
|
||||
return cfg
|
||||
}
|
||||
|
||||
func TestNewPublicRESTDependencies(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
authServer := httptest.NewServer(nil)
|
||||
defer authServer.Close()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
cfg config.Config
|
||||
assert func(*testing.T, restapi.ServerDependencies)
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "default unavailable auth service when base url is empty",
|
||||
cfg: config.Config{},
|
||||
assert: func(t *testing.T, deps restapi.ServerDependencies) {
|
||||
t.Helper()
|
||||
assert.Nil(t, deps.AuthService)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "real auth service client when base url is configured",
|
||||
cfg: config.Config{
|
||||
AuthService: config.AuthServiceConfig{
|
||||
BaseURL: authServer.URL,
|
||||
},
|
||||
},
|
||||
assert: func(t *testing.T, deps restapi.ServerDependencies) {
|
||||
t.Helper()
|
||||
require.NotNil(t, deps.AuthService)
|
||||
_, ok := deps.AuthService.(*restapi.HTTPAuthServiceClient)
|
||||
assert.True(t, ok)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid auth service base url fails fast",
|
||||
cfg: config.Config{
|
||||
AuthService: config.AuthServiceConfig{
|
||||
BaseURL: "/relative",
|
||||
},
|
||||
},
|
||||
wantErr: "auth service client",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
deps, cleanup, err := newPublicRESTDependencies(tt.cfg, zap.NewNop(), nil)
|
||||
if tt.wantErr != "" {
|
||||
require.Error(t, err)
|
||||
assert.ErrorContains(t, err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cleanup)
|
||||
tt.assert(t, deps)
|
||||
assert.NoError(t, cleanup())
|
||||
})
|
||||
func newTestBackendConfig() config.BackendConfig {
|
||||
return config.BackendConfig{
|
||||
HTTPBaseURL: "http://127.0.0.1:8080",
|
||||
GRPCPushURL: "127.0.0.1:8081",
|
||||
GatewayClientID: "gw-test",
|
||||
HTTPTimeout: 250 * time.Millisecond,
|
||||
PushReconnectBaseBackoff: 100 * time.Millisecond,
|
||||
PushReconnectMaxBackoff: time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewAuthenticatedGRPCDependencies(t *testing.T) {
|
||||
func newTestBackendClient(t *testing.T) *backendclient.Client {
|
||||
t.Helper()
|
||||
cfg := newTestBackendConfig()
|
||||
client, err := backendclient.NewClient(backendclient.Config{
|
||||
HTTPBaseURL: cfg.HTTPBaseURL,
|
||||
GRPCPushURL: cfg.GRPCPushURL,
|
||||
GatewayClientID: cfg.GatewayClientID,
|
||||
HTTPTimeout: cfg.HTTPTimeout,
|
||||
PushReconnectBaseBackoff: cfg.PushReconnectBaseBackoff,
|
||||
PushReconnectMaxBackoff: cfg.PushReconnectMaxBackoff,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { _ = client.Close() })
|
||||
return client
|
||||
}
|
||||
|
||||
func TestNewAuthenticatedGRPCDependenciesSuccess(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
server := miniredis.RunT(t)
|
||||
responseSignerPEMPath := writeTestResponseSignerPEMFile(t)
|
||||
backend := newTestBackendClient(t)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
cfg config.Config
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
cfg: config.Config{
|
||||
Redis: testRedisConn(server.Addr(), 250*time.Millisecond),
|
||||
SessionCacheRedis: config.SessionCacheRedisConfig{
|
||||
KeyPrefix: "gateway:session:",
|
||||
LookupTimeout: 250 * time.Millisecond,
|
||||
},
|
||||
ReplayRedis: config.ReplayRedisConfig{
|
||||
KeyPrefix: "gateway:replay:",
|
||||
ReserveTimeout: 250 * time.Millisecond,
|
||||
},
|
||||
SessionEventsRedis: config.SessionEventsRedisConfig{
|
||||
Stream: "gateway:session_events",
|
||||
ReadBlockTimeout: time.Second,
|
||||
},
|
||||
ClientEventsRedis: config.ClientEventsRedisConfig{
|
||||
Stream: "gateway:client_events",
|
||||
ReadBlockTimeout: time.Second,
|
||||
},
|
||||
ResponseSigner: config.ResponseSignerConfig{
|
||||
PrivateKeyPEMPath: responseSignerPEMPath,
|
||||
},
|
||||
},
|
||||
cfg := config.Config{
|
||||
Redis: testRedisConn(server.Addr(), 250*time.Millisecond),
|
||||
ReplayRedis: config.ReplayRedisConfig{
|
||||
KeyPrefix: "gateway:replay:",
|
||||
ReserveTimeout: 250 * time.Millisecond,
|
||||
},
|
||||
{
|
||||
name: "invalid session cache key prefix",
|
||||
cfg: config.Config{
|
||||
Redis: testRedisConn(server.Addr(), 250*time.Millisecond),
|
||||
SessionCacheRedis: config.SessionCacheRedisConfig{
|
||||
LookupTimeout: 250 * time.Millisecond,
|
||||
},
|
||||
ReplayRedis: config.ReplayRedisConfig{
|
||||
KeyPrefix: "gateway:replay:",
|
||||
ReserveTimeout: 250 * time.Millisecond,
|
||||
},
|
||||
SessionEventsRedis: config.SessionEventsRedisConfig{
|
||||
Stream: "gateway:session_events",
|
||||
ReadBlockTimeout: time.Second,
|
||||
},
|
||||
ClientEventsRedis: config.ClientEventsRedisConfig{
|
||||
Stream: "gateway:client_events",
|
||||
ReadBlockTimeout: time.Second,
|
||||
},
|
||||
ResponseSigner: config.ResponseSignerConfig{
|
||||
PrivateKeyPEMPath: responseSignerPEMPath,
|
||||
},
|
||||
},
|
||||
wantErr: "redis key prefix must not be empty",
|
||||
},
|
||||
{
|
||||
name: "startup ping failure",
|
||||
cfg: config.Config{
|
||||
Redis: testRedisConn(unusedTCPAddr(t), 100*time.Millisecond),
|
||||
SessionCacheRedis: config.SessionCacheRedisConfig{
|
||||
KeyPrefix: "gateway:session:",
|
||||
LookupTimeout: 100 * time.Millisecond,
|
||||
},
|
||||
ReplayRedis: config.ReplayRedisConfig{
|
||||
KeyPrefix: "gateway:replay:",
|
||||
ReserveTimeout: 100 * time.Millisecond,
|
||||
},
|
||||
SessionEventsRedis: config.SessionEventsRedisConfig{
|
||||
Stream: "gateway:session_events",
|
||||
ReadBlockTimeout: time.Second,
|
||||
},
|
||||
ClientEventsRedis: config.ClientEventsRedisConfig{
|
||||
Stream: "gateway:client_events",
|
||||
ReadBlockTimeout: time.Second,
|
||||
},
|
||||
ResponseSigner: config.ResponseSignerConfig{
|
||||
PrivateKeyPEMPath: responseSignerPEMPath,
|
||||
},
|
||||
},
|
||||
wantErr: "ping redis",
|
||||
},
|
||||
{
|
||||
name: "invalid replay config",
|
||||
cfg: config.Config{
|
||||
Redis: testRedisConn(server.Addr(), 250*time.Millisecond),
|
||||
SessionCacheRedis: config.SessionCacheRedisConfig{
|
||||
KeyPrefix: "gateway:session:",
|
||||
LookupTimeout: 250 * time.Millisecond,
|
||||
},
|
||||
ReplayRedis: config.ReplayRedisConfig{
|
||||
ReserveTimeout: 250 * time.Millisecond,
|
||||
},
|
||||
SessionEventsRedis: config.SessionEventsRedisConfig{
|
||||
Stream: "gateway:session_events",
|
||||
ReadBlockTimeout: time.Second,
|
||||
},
|
||||
ClientEventsRedis: config.ClientEventsRedisConfig{
|
||||
Stream: "gateway:client_events",
|
||||
ReadBlockTimeout: time.Second,
|
||||
},
|
||||
ResponseSigner: config.ResponseSignerConfig{
|
||||
PrivateKeyPEMPath: responseSignerPEMPath,
|
||||
},
|
||||
},
|
||||
wantErr: "replay key prefix must not be empty",
|
||||
},
|
||||
{
|
||||
name: "invalid client event config",
|
||||
cfg: config.Config{
|
||||
Redis: testRedisConn(server.Addr(), 250*time.Millisecond),
|
||||
SessionCacheRedis: config.SessionCacheRedisConfig{
|
||||
KeyPrefix: "gateway:session:",
|
||||
LookupTimeout: 250 * time.Millisecond,
|
||||
},
|
||||
ReplayRedis: config.ReplayRedisConfig{
|
||||
KeyPrefix: "gateway:replay:",
|
||||
ReserveTimeout: 250 * time.Millisecond,
|
||||
},
|
||||
SessionEventsRedis: config.SessionEventsRedisConfig{
|
||||
Stream: "gateway:session_events",
|
||||
ReadBlockTimeout: time.Second,
|
||||
},
|
||||
ClientEventsRedis: config.ClientEventsRedisConfig{
|
||||
ReadBlockTimeout: time.Second,
|
||||
},
|
||||
ResponseSigner: config.ResponseSignerConfig{
|
||||
PrivateKeyPEMPath: responseSignerPEMPath,
|
||||
},
|
||||
},
|
||||
wantErr: "client event subscriber: stream must not be empty",
|
||||
},
|
||||
{
|
||||
name: "missing response signer path",
|
||||
cfg: config.Config{
|
||||
Redis: testRedisConn(server.Addr(), 250*time.Millisecond),
|
||||
SessionCacheRedis: config.SessionCacheRedisConfig{
|
||||
KeyPrefix: "gateway:session:",
|
||||
LookupTimeout: 250 * time.Millisecond,
|
||||
},
|
||||
ReplayRedis: config.ReplayRedisConfig{
|
||||
KeyPrefix: "gateway:replay:",
|
||||
ReserveTimeout: 250 * time.Millisecond,
|
||||
},
|
||||
SessionEventsRedis: config.SessionEventsRedisConfig{
|
||||
Stream: "gateway:session_events",
|
||||
ReadBlockTimeout: time.Second,
|
||||
},
|
||||
ClientEventsRedis: config.ClientEventsRedisConfig{
|
||||
Stream: "gateway:client_events",
|
||||
ReadBlockTimeout: time.Second,
|
||||
},
|
||||
},
|
||||
wantErr: "load response signer",
|
||||
},
|
||||
{
|
||||
name: "invalid response signer pem",
|
||||
cfg: config.Config{
|
||||
Redis: testRedisConn(server.Addr(), 250*time.Millisecond),
|
||||
SessionCacheRedis: config.SessionCacheRedisConfig{
|
||||
KeyPrefix: "gateway:session:",
|
||||
LookupTimeout: 250 * time.Millisecond,
|
||||
},
|
||||
ReplayRedis: config.ReplayRedisConfig{
|
||||
KeyPrefix: "gateway:replay:",
|
||||
ReserveTimeout: 250 * time.Millisecond,
|
||||
},
|
||||
SessionEventsRedis: config.SessionEventsRedisConfig{
|
||||
Stream: "gateway:session_events",
|
||||
ReadBlockTimeout: time.Second,
|
||||
},
|
||||
ClientEventsRedis: config.ClientEventsRedisConfig{
|
||||
Stream: "gateway:client_events",
|
||||
ReadBlockTimeout: time.Second,
|
||||
},
|
||||
ResponseSigner: config.ResponseSignerConfig{
|
||||
PrivateKeyPEMPath: writeInvalidPEMFile(t),
|
||||
},
|
||||
},
|
||||
wantErr: "response signer private key",
|
||||
Backend: newTestBackendConfig(),
|
||||
ResponseSigner: config.ResponseSignerConfig{
|
||||
PrivateKeyPEMPath: responseSignerPEMPath,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
deps, components, cleanup, err := newAuthenticatedGRPCDependencies(context.Background(), cfg, zap.NewNop(), nil, backend)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, deps.SessionCache)
|
||||
require.NotNil(t, deps.ReplayStore)
|
||||
require.NotNil(t, deps.ResponseSigner)
|
||||
require.NotNil(t, deps.Router)
|
||||
require.NotNil(t, deps.Service)
|
||||
require.Len(t, components, 1)
|
||||
require.NotNil(t, cleanup)
|
||||
assert.NoError(t, cleanup())
|
||||
}
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
func TestNewAuthenticatedGRPCDependenciesPingFailure(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
deps, components, cleanup, err := newAuthenticatedGRPCDependencies(context.Background(), tt.cfg, zap.NewNop(), nil)
|
||||
if tt.wantErr != "" {
|
||||
require.Error(t, err)
|
||||
assert.ErrorContains(t, err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
responseSignerPEMPath := writeTestResponseSignerPEMFile(t)
|
||||
backend := newTestBackendClient(t)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, deps.SessionCache)
|
||||
require.NotNil(t, deps.ReplayStore)
|
||||
require.NotNil(t, deps.ResponseSigner)
|
||||
require.NotNil(t, deps.Router)
|
||||
require.NotNil(t, deps.Service)
|
||||
require.Len(t, components, 2)
|
||||
require.NotNil(t, cleanup)
|
||||
assert.NoError(t, cleanup())
|
||||
})
|
||||
cfg := config.Config{
|
||||
Redis: testRedisConn(unusedTCPAddr(t), 100*time.Millisecond),
|
||||
ReplayRedis: config.ReplayRedisConfig{
|
||||
KeyPrefix: "gateway:replay:",
|
||||
ReserveTimeout: 100 * time.Millisecond,
|
||||
},
|
||||
Backend: newTestBackendConfig(),
|
||||
ResponseSigner: config.ResponseSignerConfig{
|
||||
PrivateKeyPEMPath: responseSignerPEMPath,
|
||||
},
|
||||
}
|
||||
|
||||
_, _, _, err := newAuthenticatedGRPCDependencies(context.Background(), cfg, zap.NewNop(), nil, backend)
|
||||
require.Error(t, err)
|
||||
assert.ErrorContains(t, err, "ping redis")
|
||||
}
|
||||
|
||||
func TestNewAuthenticatedGRPCDependenciesInvalidReplayConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
server := miniredis.RunT(t)
|
||||
responseSignerPEMPath := writeTestResponseSignerPEMFile(t)
|
||||
backend := newTestBackendClient(t)
|
||||
|
||||
cfg := config.Config{
|
||||
Redis: testRedisConn(server.Addr(), 250*time.Millisecond),
|
||||
ReplayRedis: config.ReplayRedisConfig{
|
||||
ReserveTimeout: 250 * time.Millisecond,
|
||||
},
|
||||
Backend: newTestBackendConfig(),
|
||||
ResponseSigner: config.ResponseSignerConfig{
|
||||
PrivateKeyPEMPath: responseSignerPEMPath,
|
||||
},
|
||||
}
|
||||
|
||||
_, _, _, err := newAuthenticatedGRPCDependencies(context.Background(), cfg, zap.NewNop(), nil, backend)
|
||||
require.Error(t, err)
|
||||
assert.ErrorContains(t, err, "replay key prefix must not be empty")
|
||||
}
|
||||
|
||||
func TestNewAuthenticatedGRPCDependenciesMissingResponseSigner(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
backend := newTestBackendClient(t)
|
||||
|
||||
cfg := config.Config{
|
||||
Backend: newTestBackendConfig(),
|
||||
}
|
||||
|
||||
_, _, _, err := newAuthenticatedGRPCDependencies(context.Background(), cfg, zap.NewNop(), nil, backend)
|
||||
require.Error(t, err)
|
||||
assert.ErrorContains(t, err, "load response signer")
|
||||
}
|
||||
|
||||
func TestNewAuthenticatedGRPCDependenciesInvalidResponseSignerPEM(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
backend := newTestBackendClient(t)
|
||||
server := miniredis.RunT(t)
|
||||
|
||||
cfg := config.Config{
|
||||
Redis: testRedisConn(server.Addr(), 250*time.Millisecond),
|
||||
ReplayRedis: config.ReplayRedisConfig{
|
||||
KeyPrefix: "gateway:replay:",
|
||||
ReserveTimeout: 250 * time.Millisecond,
|
||||
},
|
||||
Backend: newTestBackendConfig(),
|
||||
ResponseSigner: config.ResponseSignerConfig{
|
||||
PrivateKeyPEMPath: writeInvalidPEMFile(t),
|
||||
},
|
||||
}
|
||||
|
||||
_, _, _, err := newAuthenticatedGRPCDependencies(context.Background(), cfg, zap.NewNop(), nil, backend)
|
||||
require.Error(t, err)
|
||||
assert.ErrorContains(t, err, "response signer private key")
|
||||
}
|
||||
|
||||
func unusedTCPAddr(t *testing.T) string {
|
||||
@@ -348,8 +208,7 @@ func writeInvalidPEMFile(t *testing.T) string {
|
||||
t.Helper()
|
||||
|
||||
path := filepath.Join(t.TempDir(), "invalid-response-signer.pem")
|
||||
err := os.WriteFile(path, []byte("not a valid pem"), 0o600)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, os.WriteFile(path, []byte("not a valid pem"), 0o600))
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user