mirror of
https://github.com/hanzo-fi/auth.git
synced 2026-10-30 20:33:21 +00:00
300 lines
7.7 KiB
Go
300 lines
7.7 KiB
Go
package testserver
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
authlib "github.com/hanzo-fi/go-libs/v3/auth"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/hanzo-fi/auth/cmd"
|
|
auth "github.com/hanzo-fi/auth/pkg"
|
|
"github.com/hanzo-fi/go-libs/v3/bun/bunconnect"
|
|
"gopkg.in/yaml.v3"
|
|
|
|
authclient "github.com/hanzo-fi/auth/pkg/client"
|
|
"github.com/hanzo-fi/go-libs/v3/httpserver"
|
|
"github.com/hanzo-fi/go-libs/v3/logging"
|
|
"github.com/hanzo-fi/go-libs/v3/otlp"
|
|
"github.com/hanzo-fi/go-libs/v3/otlp/otlpmetrics"
|
|
"github.com/hanzo-fi/go-libs/v3/service"
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type T interface {
|
|
require.TestingT
|
|
Cleanup(func())
|
|
Helper()
|
|
Logf(format string, args ...any)
|
|
}
|
|
|
|
type OTLPConfig struct {
|
|
BaseConfig otlp.Config
|
|
Metrics *otlpmetrics.ModuleConfig
|
|
}
|
|
|
|
type DelegatedConfiguration struct {
|
|
ClientID string
|
|
ClientSecret string
|
|
Issuer string
|
|
}
|
|
|
|
type ClientConfiguration struct {
|
|
ClientID string
|
|
ClientSecret string
|
|
Scopes []string
|
|
}
|
|
|
|
type Configuration struct {
|
|
DelegatedConfiguration DelegatedConfiguration
|
|
PostgresConfiguration bunconnect.ConnectionOptions
|
|
Output io.Writer
|
|
Debug bool
|
|
OTLPConfig *OTLPConfig
|
|
BaseURL string
|
|
Clients []auth.StaticClient
|
|
CheckScopes bool
|
|
}
|
|
|
|
type Logger interface {
|
|
Logf(fmt string, args ...any)
|
|
}
|
|
|
|
type Server struct {
|
|
configuration Configuration
|
|
logger Logger
|
|
cancel func()
|
|
ctx context.Context
|
|
errorChan chan error
|
|
id string
|
|
serverURL string
|
|
}
|
|
|
|
func (s *Server) Start() error {
|
|
rootCmd := cmd.NewRootCommand()
|
|
args := []string{
|
|
"serve",
|
|
"--" + cmd.ListenFlag, ":0",
|
|
"--" + cmd.BaseUrlFlag, s.configuration.BaseURL,
|
|
"--" + bunconnect.PostgresURIFlag, s.configuration.PostgresConfiguration.DatabaseSourceName,
|
|
"--" + bunconnect.PostgresMaxOpenConnsFlag, fmt.Sprint(s.configuration.PostgresConfiguration.MaxOpenConns),
|
|
"--" + bunconnect.PostgresConnMaxIdleTimeFlag, fmt.Sprint(s.configuration.PostgresConfiguration.ConnMaxIdleTime),
|
|
}
|
|
if s.configuration.PostgresConfiguration.MaxIdleConns != 0 {
|
|
args = append(
|
|
args,
|
|
"--"+bunconnect.PostgresMaxIdleConnsFlag,
|
|
fmt.Sprint(s.configuration.PostgresConfiguration.MaxIdleConns),
|
|
)
|
|
}
|
|
if s.configuration.PostgresConfiguration.MaxOpenConns != 0 {
|
|
args = append(
|
|
args,
|
|
"--"+bunconnect.PostgresMaxOpenConnsFlag,
|
|
fmt.Sprint(s.configuration.PostgresConfiguration.MaxOpenConns),
|
|
)
|
|
}
|
|
if s.configuration.PostgresConfiguration.ConnMaxIdleTime != 0 {
|
|
args = append(
|
|
args,
|
|
"--"+bunconnect.PostgresConnMaxIdleTimeFlag,
|
|
fmt.Sprint(s.configuration.PostgresConfiguration.ConnMaxIdleTime),
|
|
)
|
|
}
|
|
if s.configuration.DelegatedConfiguration.ClientID != "" {
|
|
args = append(args, "--"+cmd.DelegatedClientIDFlag, s.configuration.DelegatedConfiguration.ClientID)
|
|
}
|
|
if s.configuration.DelegatedConfiguration.ClientSecret != "" {
|
|
args = append(args, "--"+cmd.DelegatedClientSecretFlag, s.configuration.DelegatedConfiguration.ClientSecret)
|
|
}
|
|
if s.configuration.DelegatedConfiguration.Issuer != "" {
|
|
args = append(args, "--"+cmd.DelegatedIssuerFlag, s.configuration.DelegatedConfiguration.Issuer)
|
|
}
|
|
if len(s.configuration.Clients) > 0 {
|
|
tmpDir := os.TempDir()
|
|
configFile := filepath.Join(tmpDir, s.id+".yaml")
|
|
f, err := os.OpenFile(configFile, os.O_RDWR|os.O_CREATE, 0666)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = yaml.NewEncoder(f).Encode(struct {
|
|
Clients []auth.StaticClient `yaml:"clients"`
|
|
}{
|
|
Clients: s.configuration.Clients,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
args = append(args, "--"+cmd.ConfigFlag, configFile)
|
|
}
|
|
if s.configuration.OTLPConfig != nil {
|
|
if s.configuration.OTLPConfig.Metrics != nil {
|
|
args = append(
|
|
args,
|
|
"--"+otlpmetrics.OtelMetricsExporterFlag, s.configuration.OTLPConfig.Metrics.Exporter,
|
|
)
|
|
if s.configuration.OTLPConfig.Metrics.KeepInMemory {
|
|
args = append(
|
|
args,
|
|
"--"+otlpmetrics.OtelMetricsKeepInMemoryFlag,
|
|
)
|
|
}
|
|
if s.configuration.OTLPConfig.Metrics.OTLPConfig != nil {
|
|
args = append(
|
|
args,
|
|
"--"+otlpmetrics.OtelMetricsExporterOTLPEndpointFlag, s.configuration.OTLPConfig.Metrics.OTLPConfig.Endpoint,
|
|
"--"+otlpmetrics.OtelMetricsExporterOTLPModeFlag, s.configuration.OTLPConfig.Metrics.OTLPConfig.Mode,
|
|
)
|
|
if s.configuration.OTLPConfig.Metrics.OTLPConfig.Insecure {
|
|
args = append(args, "--"+otlpmetrics.OtelMetricsExporterOTLPInsecureFlag)
|
|
}
|
|
}
|
|
if s.configuration.OTLPConfig.Metrics.RuntimeMetrics {
|
|
args = append(args, "--"+otlpmetrics.OtelMetricsRuntimeFlag)
|
|
}
|
|
if s.configuration.OTLPConfig.Metrics.MinimumReadMemStatsInterval != 0 {
|
|
args = append(
|
|
args,
|
|
"--"+otlpmetrics.OtelMetricsRuntimeMinimumReadMemStatsIntervalFlag,
|
|
s.configuration.OTLPConfig.Metrics.MinimumReadMemStatsInterval.String(),
|
|
)
|
|
}
|
|
if s.configuration.OTLPConfig.Metrics.PushInterval != 0 {
|
|
args = append(
|
|
args,
|
|
"--"+otlpmetrics.OtelMetricsExporterPushIntervalFlag,
|
|
s.configuration.OTLPConfig.Metrics.PushInterval.String(),
|
|
)
|
|
}
|
|
if len(s.configuration.OTLPConfig.Metrics.ResourceAttributes) > 0 {
|
|
args = append(
|
|
args,
|
|
"--"+otlp.OtelResourceAttributesFlag,
|
|
strings.Join(s.configuration.OTLPConfig.Metrics.ResourceAttributes, ","),
|
|
)
|
|
}
|
|
}
|
|
if s.configuration.OTLPConfig.BaseConfig.ServiceName != "" {
|
|
args = append(args, "--"+otlp.OtelServiceNameFlag, s.configuration.OTLPConfig.BaseConfig.ServiceName)
|
|
}
|
|
}
|
|
if s.configuration.Debug {
|
|
args = append(args, "--"+service.DebugFlag)
|
|
}
|
|
if s.configuration.CheckScopes {
|
|
args = append(args, "--"+authlib.AuthCheckScopesFlag)
|
|
}
|
|
|
|
s.logger.Logf("Starting application with flags: %s", strings.Join(args, " "))
|
|
rootCmd.SetArgs(args)
|
|
rootCmd.SilenceErrors = true
|
|
output := s.configuration.Output
|
|
if output == nil {
|
|
output = io.Discard
|
|
}
|
|
rootCmd.SetOut(output)
|
|
rootCmd.SetErr(output)
|
|
|
|
ctx := logging.TestingContext()
|
|
ctx = service.ContextWithLifecycle(ctx)
|
|
ctx = httpserver.ContextWithServerInfo(ctx)
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
|
|
go func() {
|
|
s.errorChan <- rootCmd.ExecuteContext(ctx)
|
|
}()
|
|
|
|
select {
|
|
case <-service.Ready(ctx):
|
|
case err := <-s.errorChan:
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return errors.New("unexpected service stop")
|
|
}
|
|
|
|
s.ctx, s.cancel = ctx, cancel
|
|
s.serverURL = httpserver.URL(s.ctx)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Server) Stop(ctx context.Context) error {
|
|
if s.cancel == nil {
|
|
return nil
|
|
}
|
|
s.cancel()
|
|
s.cancel = nil
|
|
|
|
// Wait app to be marked as stopped
|
|
select {
|
|
case <-service.Stopped(s.ctx):
|
|
case <-ctx.Done():
|
|
return errors.New("service should have been stopped")
|
|
}
|
|
|
|
// Ensure the app has been properly shutdown
|
|
select {
|
|
case err := <-s.errorChan:
|
|
return err
|
|
case <-ctx.Done():
|
|
return errors.New("service should have been stopped without error")
|
|
}
|
|
}
|
|
|
|
func (s *Server) Client(httpClient *http.Client) *authclient.Formance {
|
|
return authclient.New(
|
|
authclient.WithServerURL(s.serverURL),
|
|
authclient.WithClient(httpClient),
|
|
)
|
|
}
|
|
|
|
func (s *Server) ServerURL() string {
|
|
return s.serverURL
|
|
}
|
|
|
|
func (s *Server) Restart(ctx context.Context) error {
|
|
if err := s.Stop(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.Start(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Server) Issuer() string {
|
|
return fmt.Sprintf("http://%s", s.serverURL)
|
|
}
|
|
|
|
func New(t T, configuration Configuration) *Server {
|
|
t.Helper()
|
|
|
|
srv := &Server{
|
|
logger: t,
|
|
configuration: configuration,
|
|
id: uuid.NewString()[:8],
|
|
errorChan: make(chan error, 1),
|
|
}
|
|
t.Logf("Start testing server")
|
|
require.NoError(t, srv.Start())
|
|
t.Cleanup(func() {
|
|
t.Logf("Stop testing server")
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
require.NoError(t, srv.Stop(ctx))
|
|
})
|
|
|
|
return srv
|
|
}
|