feat: initial commit
All checks were successful
Cadoles/bouncer/pipeline/head This commit looks good

This commit is contained in:
2023-04-24 20:52:12 +02:00
commit fe2ee907f9
124 changed files with 7433 additions and 0 deletions

8
internal/store/error.go Normal file
View File

@ -0,0 +1,8 @@
package store
import "errors"
var (
ErrAlreadyExist = errors.New("already exist")
ErrNotFound = errors.New("not found")
)

25
internal/store/layer.go Normal file
View File

@ -0,0 +1,25 @@
package store
import "time"
type (
LayerName Name
LayerType string
)
type LayerHeader struct {
Proxy ProxyName
Name LayerName
Type LayerType
Weight int
Enabled bool
}
type Layer struct {
LayerHeader
CreatedAt time.Time
UpdatedAt time.Time
Options map[string]any
}

View File

@ -0,0 +1,57 @@
package store
import (
"context"
)
type LayerOptions map[string]any
type LayerRepository interface {
CreateLayer(ctx context.Context, proxyName ProxyName, layerName LayerName, layerType LayerType, options LayerOptions) (*Layer, error)
UpdateLayer(ctx context.Context, proxyName ProxyName, layerName LayerName, options LayerOptions) error
DeleteLayer(ctx context.Context, proxyName ProxyName, layerName LayerName) error
GetLayer(ctx context.Context, proxyName ProxyName, layerName LayerName) (*Layer, error)
QueryLayers(ctx context.Context, funcs ...QueryLayerOptionFunc) ([]*LayerHeader, error)
}
type QueryLayerOptionFunc func(*QueryLayerOptions)
type QueryLayerOptions struct {
From []string
Offset *int
Limit *int
Type *LayerType
Name *LayerName
Proxy *ProxyName
}
func WithLayerQueryOffset(offset int) QueryLayerOptionFunc {
return func(o *QueryLayerOptions) {
o.Offset = &offset
}
}
func WithLayerQueryLimit(limit int) QueryLayerOptionFunc {
return func(o *QueryLayerOptions) {
o.Limit = &limit
}
}
func WithLayerQueryType(layerType LayerType) QueryLayerOptionFunc {
return func(o *QueryLayerOptions) {
o.Type = &layerType
}
}
func WithLayerQueryName(layerName LayerName) QueryLayerOptionFunc {
return func(o *QueryLayerOptions) {
o.Name = &layerName
}
}
func WithLayerQueryProxy(proxyName ProxyName) QueryLayerOptionFunc {
return func(o *QueryLayerOptions) {
o.Proxy = &proxyName
}
}

14
internal/store/name.go Normal file
View File

@ -0,0 +1,14 @@
package store
import "github.com/pkg/errors"
type Name string
var ErrEmptyName = errors.New("name cannot be empty")
func ValidateName(name string) (Name, error) {
if name == "" {
return "", errors.WithStack(ErrEmptyName)
}
return Name(name), nil
}

23
internal/store/proxy.go Normal file
View File

@ -0,0 +1,23 @@
package store
import (
"net/url"
"time"
)
type ProxyName Name
type ProxyHeader struct {
Name ProxyName
Weight int
Enabled bool
}
type Proxy struct {
ProxyHeader
To *url.URL
From []string
CreatedAt time.Time
UpdatedAt time.Time
}

View File

@ -0,0 +1,87 @@
package store
import (
"context"
"net/url"
)
type ProxyRepository interface {
CreateProxy(ctx context.Context, name ProxyName, to *url.URL, from ...string) (*Proxy, error)
UpdateProxy(ctx context.Context, name ProxyName, funcs ...UpdateProxyOptionFunc) (*Proxy, error)
QueryProxy(ctx context.Context, funcs ...QueryProxyOptionFunc) ([]*ProxyHeader, error)
GetProxy(ctx context.Context, name ProxyName) (*Proxy, error)
DeleteProxy(ctx context.Context, name ProxyName) error
}
type UpdateProxyOptionFunc func(*UpdateProxyOptions)
type UpdateProxyOptions struct {
To *url.URL
From []string
Enabled *bool
Weight *int
}
func WithProxyUpdateEnabled(enabled bool) UpdateProxyOptionFunc {
return func(o *UpdateProxyOptions) {
o.Enabled = &enabled
}
}
func WithProxyUpdateWeight(weight int) UpdateProxyOptionFunc {
return func(o *UpdateProxyOptions) {
o.Weight = &weight
}
}
func WithProxyUpdateTo(to *url.URL) UpdateProxyOptionFunc {
return func(o *UpdateProxyOptions) {
o.To = to
}
}
func WithProxyUpdateFrom(from ...string) UpdateProxyOptionFunc {
return func(o *UpdateProxyOptions) {
o.From = from
}
}
type QueryProxyOptionFunc func(*QueryProxyOptions)
type QueryProxyOptions struct {
To *url.URL
Names []ProxyName
From []string
Offset *int
Limit *int
}
func WithProxyQueryOffset(offset int) QueryProxyOptionFunc {
return func(o *QueryProxyOptions) {
o.Offset = &offset
}
}
func WithProxyQueryLimit(limit int) QueryProxyOptionFunc {
return func(o *QueryProxyOptions) {
o.Limit = &limit
}
}
func WithProxyQueryTo(to *url.URL) QueryProxyOptionFunc {
return func(o *QueryProxyOptions) {
o.To = to
}
}
func WithProxyQueryFrom(from ...string) QueryProxyOptionFunc {
return func(o *QueryProxyOptions) {
o.From = from
}
}
func WithProxyQueryNames(names ...ProxyName) QueryProxyOptionFunc {
return func(o *QueryProxyOptions) {
o.Names = names
}
}

View File

@ -0,0 +1,64 @@
package redis
import (
"encoding/json"
"strings"
"github.com/pkg/errors"
)
type jsonWrapper[T any] struct {
value T
}
func (w *jsonWrapper[T]) MarshalBinary() ([]byte, error) {
data, err := json.Marshal(w.value)
if err != nil {
return nil, errors.WithStack(err)
}
return data, nil
}
func (w *jsonWrapper[T]) UnmarshalBinary(data []byte) error {
if err := json.Unmarshal(data, &w.value); err != nil {
return errors.WithStack(err)
}
return nil
}
func (w *jsonWrapper[T]) UnmarshalText(data []byte) error {
if err := json.Unmarshal(data, &w.value); err != nil {
return errors.WithStack(err)
}
return nil
}
func (w *jsonWrapper[T]) Value() T {
return w.value
}
func wrap[T any](v T) *jsonWrapper[T] {
return &jsonWrapper[T]{v}
}
func unwrap[T any](v any) (T, error) {
str, ok := v.(string)
if !ok {
return *new(T), errors.Errorf("could not unwrap value of type '%T'", v)
}
u := new(T)
if err := json.Unmarshal([]byte(str), u); err != nil {
return *new(T), errors.WithStack(err)
}
return *u, nil
}
func key(parts ...string) string {
return strings.Join(parts, ":")
}

View File

@ -0,0 +1,60 @@
package redis
import (
"time"
"forge.cadoles.com/cadoles/bouncer/internal/store"
"github.com/pkg/errors"
)
type layerHeaderItem struct {
Proxy string `redis:"proxy"`
Name string `redis:"name"`
Type string `redis:"type"`
Weight int `redis:"weight"`
Enabled bool `redis:"enabled"`
}
func (i *layerHeaderItem) ToLayerHeader() (*store.LayerHeader, error) {
layerHeader := &store.LayerHeader{
Proxy: store.ProxyName(i.Proxy),
Name: store.LayerName(i.Name),
Type: store.LayerType(i.Type),
}
return layerHeader, nil
}
type layerItem struct {
layerHeaderItem
Options *jsonWrapper[map[string]any] `redis:"options"`
CreatedAt *jsonWrapper[time.Time] `redis:"created_at"`
UpdatedAt *jsonWrapper[time.Time] `redis:"updated_at"`
}
func (i *layerItem) ToLayer() (*store.Layer, error) {
layerHeader, err := i.layerHeaderItem.ToLayerHeader()
if err != nil {
return nil, errors.WithStack(err)
}
layer := &store.Layer{
LayerHeader: *layerHeader,
}
if i.Options != nil {
layer.Options = i.Options.Value()
}
if i.CreatedAt != nil {
layer.CreatedAt = i.CreatedAt.Value()
}
if i.UpdatedAt != nil {
layer.UpdatedAt = i.UpdatedAt.Value()
}
return layer, nil
}

View File

@ -0,0 +1,191 @@
package redis
import (
"context"
"time"
"forge.cadoles.com/cadoles/bouncer/internal/store"
"github.com/pkg/errors"
"github.com/redis/go-redis/v9"
)
const (
keyPrefixLayer = "layer"
)
type LayerRepository struct {
client redis.UniversalClient
}
// CreateLayer implements store.LayerRepository
func (r *LayerRepository) CreateLayer(ctx context.Context, proxyName store.ProxyName, layerName store.LayerName, layerType store.LayerType, options store.LayerOptions) (*store.Layer, error) {
now := time.Now().UTC()
key := layerKey(proxyName, layerName)
layerItem := &layerItem{
layerHeaderItem: layerHeaderItem{
Proxy: string(proxyName),
Name: string(layerName),
Type: string(layerType),
Weight: 0,
Enabled: false,
},
CreatedAt: wrap(now),
UpdatedAt: wrap(now),
Options: wrap(map[string]any{}),
}
txf := func(tx *redis.Tx) error {
exists, err := tx.Exists(ctx, key).Uint64()
if err != nil {
return errors.WithStack(err)
}
if exists > 0 {
return errors.WithStack(store.ErrAlreadyExist)
}
_, err = tx.TxPipelined(ctx, func(p redis.Pipeliner) error {
p.HMSet(ctx, key, &layerItem.layerHeaderItem)
p.HMSet(ctx, key, layerItem)
return nil
})
if err != nil {
return errors.WithStack(err)
}
return nil
}
err := r.client.Watch(ctx, txf, key)
if err != nil {
return nil, errors.WithStack(err)
}
return &store.Layer{
LayerHeader: store.LayerHeader{
Name: layerName,
Proxy: proxyName,
Type: layerType,
Weight: 0,
Enabled: false,
},
CreatedAt: now,
UpdatedAt: now,
Options: map[string]any{},
}, nil
}
// DeleteLayer implements store.LayerRepository
func (r *LayerRepository) DeleteLayer(ctx context.Context, proxyName store.ProxyName, layerName store.LayerName) error {
key := layerKey(proxyName, layerName)
if cmd := r.client.Del(ctx, key); cmd.Err() != nil {
return errors.WithStack(cmd.Err())
}
return nil
}
// GetLayer implements store.LayerRepository
func (r *LayerRepository) GetLayer(ctx context.Context, proxyName store.ProxyName, layerName store.LayerName) (*store.Layer, error) {
key := layerKey(proxyName, layerName)
layerItem := &layerItem{}
cmd := r.client.Exists(ctx, key)
if err := cmd.Err(); err != nil {
return nil, errors.WithStack(err)
}
if count := cmd.Val(); count == 0 {
return nil, errors.WithStack(store.ErrNotFound)
}
if err := r.client.HGetAll(ctx, key).Scan(&layerItem.layerHeaderItem); err != nil {
return nil, errors.WithStack(err)
}
if err := r.client.HGetAll(ctx, key).Scan(layerItem); err != nil {
return nil, errors.WithStack(err)
}
layer, err := layerItem.ToLayer()
if err != nil {
return nil, errors.WithStack(err)
}
return layer, nil
}
// QueryLayers implements store.LayerRepository
func (r *LayerRepository) QueryLayers(ctx context.Context, funcs ...store.QueryLayerOptionFunc) ([]*store.LayerHeader, error) {
opts := &store.QueryLayerOptions{}
for _, fn := range funcs {
fn(opts)
}
keyParts := []string{keyPrefixLayer}
if opts.Proxy != nil {
keyParts = append(keyParts, string(*opts.Proxy))
} else {
keyParts = append(keyParts, "*")
}
if opts.Name != nil {
keyParts = append(keyParts, string(*opts.Name))
} else {
keyParts = append(keyParts, "*")
}
key := key(keyParts...)
iter := r.client.Scan(ctx, uint64(*opts.Offset), key, int64(*opts.Limit)).Iterator()
headers := make([]*store.LayerHeader, 0)
for iter.Next(ctx) {
key := iter.Val()
layerHeaderItem := &layerHeaderItem{}
if err := r.client.HGetAll(ctx, key).Scan(layerHeaderItem); err != nil {
return nil, errors.WithStack(err)
}
layerHeader, err := layerHeaderItem.ToLayerHeader()
if err != nil {
return nil, errors.WithStack(err)
}
headers = append(headers, layerHeader)
}
if err := iter.Err(); err != nil {
return nil, errors.WithStack(err)
}
return headers, nil
}
// UpdateLayer implements store.LayerRepository
func (r *LayerRepository) UpdateLayer(ctx context.Context, proxyName store.ProxyName, layerName store.LayerName, options store.LayerOptions) error {
panic("unimplemented")
}
func NewLayerRepository(client redis.UniversalClient) *LayerRepository {
return &LayerRepository{
client: client,
}
}
var _ store.LayerRepository = &LayerRepository{}
func layerKey(proxyName store.ProxyName, layerName store.LayerName) string {
return key(keyPrefixLayer, string(proxyName), string(layerName))
}

View File

@ -0,0 +1,12 @@
package redis
import (
"testing"
"forge.cadoles.com/cadoles/bouncer/internal/store/testsuite"
)
func TestLayerRepository(t *testing.T) {
repository := NewLayerRepository(client)
testsuite.TestLayerRepository(t, repository)
}

View File

@ -0,0 +1,58 @@
package redis
import (
"context"
"log"
"os"
"testing"
"github.com/ory/dockertest/v3"
"github.com/pkg/errors"
"github.com/redis/go-redis/v9"
)
var client redis.UniversalClient
func TestMain(m *testing.M) {
// uses a sensible default on windows (tcp/http) and linux/osx (socket)
pool, err := dockertest.NewPool("")
if err != nil {
log.Fatalf("%+v", errors.WithStack(err))
}
// uses pool to try to connect to Docker
err = pool.Client.Ping()
if err != nil {
log.Fatalf("%+v", errors.WithStack(err))
}
// pulls an image, creates a container based on it and runs it
resource, err := pool.Run("redis", "alpine3.17", []string{})
if err != nil {
log.Fatalf("%+v", errors.WithStack(err))
}
if err := pool.Retry(func() error {
client = redis.NewUniversalClient(&redis.UniversalOptions{
Addrs: []string{resource.GetHostPort("6379/tcp")},
})
ctx := context.Background()
if cmd := client.Ping(ctx); cmd.Err() != nil {
return errors.WithStack(err)
}
return nil
}); err != nil {
log.Fatalf("%+v", errors.WithStack(err))
}
code := m.Run()
if err := pool.Purge(resource); err != nil {
log.Fatalf("%+v", errors.WithStack(err))
}
os.Exit(code)
}

View File

@ -0,0 +1,64 @@
package redis
import (
"net/url"
"time"
"forge.cadoles.com/cadoles/bouncer/internal/store"
"github.com/pkg/errors"
)
type proxyHeaderItem struct {
Name string `redis:"name"`
Weight int `redis:"weight"`
Enabled bool `redis:"enabled"`
CreatedAt *jsonWrapper[time.Time] `redis:"created_at"`
UpdatedAt *jsonWrapper[time.Time] `redis:"updated_at"`
}
func (i *proxyHeaderItem) ToProxyHeader() (*store.ProxyHeader, error) {
proxyHeader := &store.ProxyHeader{
Name: store.ProxyName(i.Name),
}
return proxyHeader, nil
}
type proxyItem struct {
proxyHeaderItem
To string `redis:"to"`
From *jsonWrapper[[]string] `redis:"from"`
}
func (i *proxyItem) ToProxy() (*store.Proxy, error) {
proxyHeader, err := i.proxyHeaderItem.ToProxyHeader()
if err != nil {
return nil, errors.WithStack(err)
}
to, err := url.Parse(i.To)
if err != nil {
return nil, errors.WithStack(err)
}
proxy := &store.Proxy{
ProxyHeader: *proxyHeader,
To: to,
}
if i.CreatedAt != nil {
proxy.CreatedAt = i.CreatedAt.Value()
}
if i.UpdatedAt != nil {
proxy.UpdatedAt = i.UpdatedAt.Value()
}
if i.From != nil {
proxy.From = i.From.Value()
}
return proxy, nil
}

View File

@ -0,0 +1,167 @@
package redis
import (
"context"
"net/url"
"time"
"forge.cadoles.com/cadoles/bouncer/internal/store"
"github.com/pkg/errors"
"github.com/redis/go-redis/v9"
)
const (
keyPrefixProxy = "proxy"
)
type ProxyRepository struct {
client redis.UniversalClient
}
// GetProxy implements store.ProxyRepository
func (r *ProxyRepository) GetProxy(ctx context.Context, name store.ProxyName) (*store.Proxy, error) {
key := proxyKey(name)
proxyItem := &proxyItem{}
cmd := r.client.Exists(ctx, key)
if err := cmd.Err(); err != nil {
return nil, errors.WithStack(err)
}
if count := cmd.Val(); count == 0 {
return nil, errors.WithStack(store.ErrNotFound)
}
if err := r.client.HGetAll(ctx, key).Scan(&proxyItem.proxyHeaderItem); err != nil {
return nil, errors.WithStack(err)
}
if err := r.client.HGetAll(ctx, key).Scan(proxyItem); err != nil {
return nil, errors.WithStack(err)
}
proxy, err := proxyItem.ToProxy()
if err != nil {
return nil, errors.WithStack(err)
}
return proxy, nil
}
// CreateProxy implements store.ProxyRepository
func (r *ProxyRepository) CreateProxy(ctx context.Context, name store.ProxyName, to *url.URL, from ...string) (*store.Proxy, error) {
now := time.Now().UTC()
key := proxyKey(name)
txf := func(tx *redis.Tx) error {
exists, err := tx.Exists(ctx, key).Uint64()
if err != nil {
return errors.WithStack(err)
}
if exists > 0 {
return errors.WithStack(store.ErrAlreadyExist)
}
proxyItem := &proxyItem{
proxyHeaderItem: proxyHeaderItem{
Name: string(name),
CreatedAt: wrap(now),
UpdatedAt: wrap(now),
Weight: 0,
Enabled: false,
},
To: to.String(),
From: wrap(from),
}
_, err = tx.TxPipelined(ctx, func(p redis.Pipeliner) error {
p.HMSet(ctx, key, proxyItem.proxyHeaderItem)
p.HMSet(ctx, key, proxyItem)
return nil
})
if err != nil {
return errors.WithStack(err)
}
return nil
}
err := r.client.Watch(ctx, txf, key)
if err != nil {
return nil, errors.WithStack(err)
}
return &store.Proxy{
ProxyHeader: store.ProxyHeader{
Name: name,
Weight: 0,
Enabled: false,
},
To: to,
From: from,
CreatedAt: now,
UpdatedAt: now,
}, nil
}
// DeleteProxy implements store.ProxyRepository
func (r *ProxyRepository) DeleteProxy(ctx context.Context, name store.ProxyName) error {
key := proxyKey(name)
if cmd := r.client.Del(ctx, key); cmd.Err() != nil {
return errors.WithStack(cmd.Err())
}
return nil
}
// QueryProxy implements store.ProxyRepository
func (r *ProxyRepository) QueryProxy(ctx context.Context, funcs ...store.QueryProxyOptionFunc) ([]*store.ProxyHeader, error) {
iter := r.client.Scan(ctx, 0, keyPrefixProxy+"*", 0).Iterator()
headers := make([]*store.ProxyHeader, 0)
for iter.Next(ctx) {
key := iter.Val()
proxyHeaderItem := &proxyHeaderItem{}
if err := r.client.HGetAll(ctx, key).Scan(proxyHeaderItem); err != nil {
return nil, errors.WithStack(err)
}
proxyHeader, err := proxyHeaderItem.ToProxyHeader()
if err != nil {
return nil, errors.WithStack(err)
}
headers = append(headers, proxyHeader)
}
if err := iter.Err(); err != nil {
return nil, errors.WithStack(err)
}
return headers, nil
}
// UpdateProxy implements store.ProxyRepository
func (*ProxyRepository) UpdateProxy(ctx context.Context, name store.ProxyName, funcs ...store.UpdateProxyOptionFunc) (*store.Proxy, error) {
panic("unimplemented")
}
func NewProxyRepository(client redis.UniversalClient) *ProxyRepository {
return &ProxyRepository{
client: client,
}
}
var _ store.ProxyRepository = &ProxyRepository{}
func proxyKey(name store.ProxyName) string {
return key(keyPrefixProxy, string(name))
}

View File

@ -0,0 +1,12 @@
package redis
import (
"testing"
"forge.cadoles.com/cadoles/bouncer/internal/store/testsuite"
)
func TestProxyRepository(t *testing.T) {
repository := NewProxyRepository(client)
testsuite.TestProxyRepository(t, repository)
}

View File

@ -0,0 +1,66 @@
package testsuite
import (
"context"
"testing"
"forge.cadoles.com/cadoles/bouncer/internal/store"
"github.com/pkg/errors"
)
type layerRepositoryTestCase struct {
Name string
Do func(repo store.LayerRepository) error
}
const layerType store.LayerType = "test"
var layerRepositoryTestCases = []layerRepositoryTestCase{
{
Name: "Create layer",
Do: func(repo store.LayerRepository) error {
ctx := context.Background()
options := map[string]any{}
layer, err := repo.CreateLayer(ctx, "create_layer_proxy", "create_layer", layerType, options)
if err != nil {
return errors.WithStack(err)
}
if layer == nil {
return errors.Errorf("layer should not be nil")
}
if layer.Name == "" {
return errors.Errorf("layer.Name should not be empty")
}
if layer.Proxy == "" {
return errors.Errorf("layer.Proxy should not be empty")
}
if layer.CreatedAt.IsZero() {
return errors.Errorf("layer.CreatedAt should not be zero value")
}
if layer.UpdatedAt.IsZero() {
return errors.Errorf("layer.UpdatedAt should not be zero value")
}
return nil
},
},
}
func TestLayerRepository(t *testing.T, repo store.LayerRepository) {
for _, tc := range layerRepositoryTestCases {
func(tc layerRepositoryTestCase) {
t.Run(tc.Name, func(t *testing.T) {
if err := tc.Do(repo); err != nil {
t.Fatalf("%+v", errors.WithStack(err))
}
})
}(tc)
}
}

View File

@ -0,0 +1,220 @@
package testsuite
import (
"context"
"net/url"
"reflect"
"testing"
"forge.cadoles.com/cadoles/bouncer/internal/store"
"github.com/pkg/errors"
)
type proxyRepositoryTestCase struct {
Name string
Do func(repo store.ProxyRepository) error
}
var proxyRepositoryTestCases = []proxyRepositoryTestCase{
{
Name: "Create proxy",
Do: func(repo store.ProxyRepository) error {
ctx := context.Background()
url, err := url.Parse("http://example.com")
if err != nil {
return errors.WithStack(err)
}
proxy, err := repo.CreateProxy(ctx, "create_proxy", url, "*:*")
if err != nil {
return errors.WithStack(err)
}
if proxy == nil {
return errors.Errorf("proxy should not be nil")
}
if proxy.Name == "" {
return errors.Errorf("proxy.Name should not be empty")
}
if proxy.To == nil {
return errors.Errorf("proxy.To should not be nil")
}
if e, g := url.String(), proxy.To.String(); e != g {
return errors.Errorf("proxy.URL.String(): expected '%v', got '%v'", url.String(), proxy.To.String())
}
if proxy.CreatedAt.IsZero() {
return errors.Errorf("proxy.CreatedAt should not be zero value")
}
if proxy.UpdatedAt.IsZero() {
return errors.Errorf("proxy.UpdatedAt should not be zero value")
}
return nil
},
},
{
Name: "Create then get proxy",
Do: func(repo store.ProxyRepository) error {
ctx := context.Background()
url, err := url.Parse("http://example.com")
if err != nil {
return errors.WithStack(err)
}
createdProxy, err := repo.CreateProxy(ctx, "create_then_get_proxy", url, "127.0.0.1:*", "localhost:*")
if err != nil {
return errors.WithStack(err)
}
foundProxy, err := repo.GetProxy(ctx, createdProxy.Name)
if err != nil {
return errors.WithStack(err)
}
if e, g := createdProxy.Name, foundProxy.Name; e != g {
return errors.Errorf("foundProxy.Name: expected '%v', got '%v'", createdProxy.Name, foundProxy.Name)
}
if e, g := createdProxy.From, foundProxy.From; !reflect.DeepEqual(e, g) {
return errors.Errorf("foundProxy.From: expected '%v', got '%v'", createdProxy.From, foundProxy.From)
}
if e, g := createdProxy.To.String(), foundProxy.To.String(); e != g {
return errors.Errorf("foundProxy.To: expected '%v', got '%v'", createdProxy.To, foundProxy.To)
}
if e, g := createdProxy.CreatedAt, foundProxy.CreatedAt; e != g {
return errors.Errorf("foundProxy.CreatedAt: expected '%v', got '%v'", createdProxy.CreatedAt, foundProxy.CreatedAt)
}
if e, g := createdProxy.UpdatedAt, foundProxy.UpdatedAt; e != g {
return errors.Errorf("foundProxy.UpdatedAt: expected '%v', got '%v'", createdProxy.UpdatedAt, foundProxy.UpdatedAt)
}
return nil
},
},
{
Name: "Create then delete proxy",
Do: func(repo store.ProxyRepository) error {
ctx := context.Background()
url, err := url.Parse("http://example.com")
if err != nil {
return errors.WithStack(err)
}
createdProxy, err := repo.CreateProxy(ctx, "create_then_delete_proxy", url, "127.0.0.1:*", "localhost:*")
if err != nil {
return errors.WithStack(err)
}
if err := repo.DeleteProxy(ctx, createdProxy.Name); err != nil {
return errors.WithStack(err)
}
foundProxy, err := repo.GetProxy(ctx, createdProxy.Name)
if err == nil {
return errors.New("err should not be nil")
}
if !errors.Is(err, store.ErrNotFound) {
return errors.Errorf("err should be store.ErrNotFound, got '%+v'", err)
}
if foundProxy != nil {
return errors.Errorf("foundProxy should be nil, got '%v'", foundProxy)
}
return nil
},
},
{
Name: "Create then query",
Do: func(repo store.ProxyRepository) error {
ctx := context.Background()
url, err := url.Parse("http://example.com")
if err != nil {
return errors.WithStack(err)
}
createdProxy, err := repo.CreateProxy(ctx, "create_then_query", url, "127.0.0.1:*", "localhost:*")
if err != nil {
return errors.WithStack(err)
}
headers, err := repo.QueryProxy(ctx)
if err != nil {
return errors.WithStack(err)
}
if len(headers) < 1 {
return errors.Errorf("len(headers): expected value > 1, got '%v'", len(headers))
}
found := false
for _, h := range headers {
if h.Name == createdProxy.Name {
found = true
break
}
}
if !found {
return errors.New("could not find created proxy in query results")
}
return nil
},
},
{
Name: "Create already existing proxy",
Do: func(repo store.ProxyRepository) error {
ctx := context.Background()
url, err := url.Parse("http://example.com")
if err != nil {
return errors.WithStack(err)
}
var name store.ProxyName = "create_already_existing_proxy"
_, err = repo.CreateProxy(ctx, name, url, "127.0.0.1:*", "localhost:*")
if err != nil {
return errors.WithStack(err)
}
_, err = repo.CreateProxy(ctx, name, url, "127.0.0.1:*")
if err == nil {
return errors.New("err should not be nil")
}
if !errors.Is(err, store.ErrAlreadyExist) {
return errors.Errorf("err: expected store.ErrAlreadyExists, got '%+v'", err)
}
return nil
},
},
}
func TestProxyRepository(t *testing.T, repo store.ProxyRepository) {
for _, tc := range proxyRepositoryTestCases {
func(tc proxyRepositoryTestCase) {
t.Run(tc.Name, func(t *testing.T) {
if err := tc.Do(repo); err != nil {
t.Fatalf("%+v", errors.WithStack(err))
}
})
}(tc)
}
}