From cdfadf71b2ae121b9560de5c50d41c8b7008a63b Mon Sep 17 00:00:00 2001 From: Matt Siwiec Date: Mon, 21 Aug 2023 10:31:46 -0600 Subject: [PATCH] input fixes and tests (#210) Signed-off-by: Matt Siwiec --- go.sum | 8 + .../generated/loadbalancer/loadbalancer.go | 2 + internal/ent/generated/loadbalancer_create.go | 5 + internal/ent/generated/provider/provider.go | 2 + internal/ent/generated/provider_create.go | 5 + internal/ent/generated/runtime.go | 8 + internal/ent/schema/loadbalancer.go | 1 + internal/ent/schema/provider.go | 1 + internal/graphapi/errors.go | 6 + internal/graphapi/loadbalancer_test.go | 185 +++++++++++++ internal/graphapi/port.resolvers.go | 18 +- internal/graphapi/port_test.go | 250 ++++++++++++++++++ internal/graphapi/provider_test.go | 173 ++++++++++++ internal/graphapi/tools_test.go | 5 +- 14 files changed, 664 insertions(+), 5 deletions(-) create mode 100644 internal/graphapi/errors.go diff --git a/go.sum b/go.sum index 46e85684b..67031c3a4 100644 --- a/go.sum +++ b/go.sum @@ -100,6 +100,7 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7 github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= @@ -400,6 +401,8 @@ github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjR github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sebdah/goldie/v2 v2.5.3 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y= @@ -448,6 +451,9 @@ github.com/testcontainers/testcontainers-go v0.21.0 h1:syePAxdeTzfkap+RrJaQZpJQ/ github.com/testcontainers/testcontainers-go v0.21.0/go.mod h1:c1ez3WVRHq7T/Aj+X3TIipFBwkBaNT5iNCY8+1b83Ng= github.com/testcontainers/testcontainers-go/modules/postgres v0.21.0 h1:rFPyTR7pPMiHcDktXwd5iZ+mA1cHH/WRa+knxBcY8wU= github.com/testcontainers/testcontainers-go/modules/postgres v0.21.0/go.mod h1:Uoia8PX1RewxkJTbeXGBK6vgMjlmRbnL/4n0EXH2Z54= +github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= +github.com/urfave/cli/v2 v2.25.5 h1:d0NIAyhh5shGscroL7ek/Ya9QYQE0KNabJgiUinIQkc= +github.com/urfave/cli/v2 v2.25.5/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= @@ -463,6 +469,8 @@ github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vb github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= github.com/wundergraph/graphql-go-tools v1.62.3 h1:WKpkhKqWoTq/AE0AbrZ+66ezUe3l/wAqZZurZeL5qqQ= github.com/wundergraph/graphql-go-tools v1.62.3/go.mod h1:Lsg/b4nVfNQLyJE1mjPV73O/JuhhCxH5qmaWQjitVHM= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/internal/ent/generated/loadbalancer/loadbalancer.go b/internal/ent/generated/loadbalancer/loadbalancer.go index 51f94bdcb..9f1f03a1e 100644 --- a/internal/ent/generated/loadbalancer/loadbalancer.go +++ b/internal/ent/generated/loadbalancer/loadbalancer.go @@ -93,6 +93,8 @@ var ( UpdateDefaultUpdatedAt func() time.Time // NameValidator is a validator for the "name" field. It is called by the builders before save. NameValidator func(string) error + // OwnerIDValidator is a validator for the "owner_id" field. It is called by the builders before save. + OwnerIDValidator func(string) error // LocationIDValidator is a validator for the "location_id" field. It is called by the builders before save. LocationIDValidator func(string) error // ProviderIDValidator is a validator for the "provider_id" field. It is called by the builders before save. diff --git a/internal/ent/generated/loadbalancer_create.go b/internal/ent/generated/loadbalancer_create.go index 98ae4698a..054cc3dc9 100644 --- a/internal/ent/generated/loadbalancer_create.go +++ b/internal/ent/generated/loadbalancer_create.go @@ -191,6 +191,11 @@ func (lbc *LoadBalancerCreate) check() error { if _, ok := lbc.mutation.OwnerID(); !ok { return &ValidationError{Name: "owner_id", err: errors.New(`generated: missing required field "LoadBalancer.owner_id"`)} } + if v, ok := lbc.mutation.OwnerID(); ok { + if err := loadbalancer.OwnerIDValidator(string(v)); err != nil { + return &ValidationError{Name: "owner_id", err: fmt.Errorf(`generated: validator failed for field "LoadBalancer.owner_id": %w`, err)} + } + } if _, ok := lbc.mutation.LocationID(); !ok { return &ValidationError{Name: "location_id", err: errors.New(`generated: missing required field "LoadBalancer.location_id"`)} } diff --git a/internal/ent/generated/provider/provider.go b/internal/ent/generated/provider/provider.go index 28aec5dae..22ffdf7c9 100644 --- a/internal/ent/generated/provider/provider.go +++ b/internal/ent/generated/provider/provider.go @@ -78,6 +78,8 @@ var ( UpdateDefaultUpdatedAt func() time.Time // NameValidator is a validator for the "name" field. It is called by the builders before save. NameValidator func(string) error + // OwnerIDValidator is a validator for the "owner_id" field. It is called by the builders before save. + OwnerIDValidator func(string) error // DefaultID holds the default value on creation for the "id" field. DefaultID func() gidx.PrefixedID ) diff --git a/internal/ent/generated/provider_create.go b/internal/ent/generated/provider_create.go index 4b9e93ff1..79cdc6e60 100644 --- a/internal/ent/generated/provider_create.go +++ b/internal/ent/generated/provider_create.go @@ -173,6 +173,11 @@ func (pc *ProviderCreate) check() error { if _, ok := pc.mutation.OwnerID(); !ok { return &ValidationError{Name: "owner_id", err: errors.New(`generated: missing required field "Provider.owner_id"`)} } + if v, ok := pc.mutation.OwnerID(); ok { + if err := provider.OwnerIDValidator(string(v)); err != nil { + return &ValidationError{Name: "owner_id", err: fmt.Errorf(`generated: validator failed for field "Provider.owner_id": %w`, err)} + } + } return nil } diff --git a/internal/ent/generated/runtime.go b/internal/ent/generated/runtime.go index dc8671f37..394a7833e 100644 --- a/internal/ent/generated/runtime.go +++ b/internal/ent/generated/runtime.go @@ -51,6 +51,10 @@ func init() { loadbalancerDescName := loadbalancerFields[1].Descriptor() // loadbalancer.NameValidator is a validator for the "name" field. It is called by the builders before save. loadbalancer.NameValidator = loadbalancerDescName.Validators[0].(func(string) error) + // loadbalancerDescOwnerID is the schema descriptor for owner_id field. + loadbalancerDescOwnerID := loadbalancerFields[2].Descriptor() + // loadbalancer.OwnerIDValidator is a validator for the "owner_id" field. It is called by the builders before save. + loadbalancer.OwnerIDValidator = loadbalancerDescOwnerID.Validators[0].(func(string) error) // loadbalancerDescLocationID is the schema descriptor for location_id field. loadbalancerDescLocationID := loadbalancerFields[3].Descriptor() // loadbalancer.LocationIDValidator is a validator for the "location_id" field. It is called by the builders before save. @@ -207,6 +211,10 @@ func init() { providerDescName := providerFields[1].Descriptor() // provider.NameValidator is a validator for the "name" field. It is called by the builders before save. provider.NameValidator = providerDescName.Validators[0].(func(string) error) + // providerDescOwnerID is the schema descriptor for owner_id field. + providerDescOwnerID := providerFields[2].Descriptor() + // provider.OwnerIDValidator is a validator for the "owner_id" field. It is called by the builders before save. + provider.OwnerIDValidator = providerDescOwnerID.Validators[0].(func(string) error) // providerDescID is the schema descriptor for id field. providerDescID := providerFields[0].Descriptor() // provider.DefaultID holds the default value on creation for the id field. diff --git a/internal/ent/schema/loadbalancer.go b/internal/ent/schema/loadbalancer.go index 7bf49c408..bffa7031f 100644 --- a/internal/ent/schema/loadbalancer.go +++ b/internal/ent/schema/loadbalancer.go @@ -49,6 +49,7 @@ func (LoadBalancer) Fields() []ent.Field { field.String("owner_id"). GoType(gidx.PrefixedID("")). Immutable(). + NotEmpty(). Comment("The ID for the owner for this load balancer."). Annotations( entgql.QueryField(), diff --git a/internal/ent/schema/provider.go b/internal/ent/schema/provider.go index 3d74b2d3d..a486b3bdb 100644 --- a/internal/ent/schema/provider.go +++ b/internal/ent/schema/provider.go @@ -44,6 +44,7 @@ func (Provider) Fields() []ent.Field { field.String("owner_id"). GoType(gidx.PrefixedID("")). Immutable(). + NotEmpty(). Comment("The ID for the owner for this load balancer."). Annotations( entgql.QueryField(), diff --git a/internal/graphapi/errors.go b/internal/graphapi/errors.go new file mode 100644 index 000000000..b9e8e22c0 --- /dev/null +++ b/internal/graphapi/errors.go @@ -0,0 +1,6 @@ +package graphapi + +import "errors" + +// ErrPortNumberInUse is returned when a port number is already in use. +var ErrPortNumberInUse = errors.New("port number already in use") diff --git a/internal/graphapi/loadbalancer_test.go b/internal/graphapi/loadbalancer_test.go index 0819585d8..4628169c4 100644 --- a/internal/graphapi/loadbalancer_test.go +++ b/internal/graphapi/loadbalancer_test.go @@ -48,6 +48,9 @@ func TestQuery_loadBalancer(t *testing.T) { for _, tt := range testCases { t.Run(tt.TestName, func(t *testing.T) { + tt := tt + t.Parallel() + resp, err := graphTestClient().GetLoadBalancer(ctx, tt.QueryID) if tt.errorMsg != "" { @@ -66,6 +69,188 @@ func TestQuery_loadBalancer(t *testing.T) { } } +func TestCreate_loadBalancer(t *testing.T) { + ctx := context.Background() + + // Permit request + ctx = context.WithValue(ctx, permissions.CheckerCtxKey, permissions.DefaultAllowChecker) + + prov := (&ProviderBuilder{}).MustNew(ctx) + ownerID := gidx.MustNewID(ownerPrefix) + locationID := gidx.MustNewID(locationPrefix) + name := gofakeit.DomainName() + + testCases := []struct { + TestName string + Input graphclient.CreateLoadBalancerInput + ExpectedLB *ent.LoadBalancer + errorMsg string + }{ + { + TestName: "creates loadbalancer", + Input: graphclient.CreateLoadBalancerInput{Name: name, ProviderID: prov.ID, OwnerID: ownerID, LocationID: locationID}, + ExpectedLB: &ent.LoadBalancer{ + Name: name, + ProviderID: prov.ID, + OwnerID: ownerID, + LocationID: locationID, + }, + }, + { + TestName: "fails to create loadbalancer with empty name", + Input: graphclient.CreateLoadBalancerInput{Name: "", ProviderID: prov.ID, OwnerID: ownerID, LocationID: locationID}, + errorMsg: "value is less than the required length", + }, + { + TestName: "fails to create loadbalancer with empty ownerID", + Input: graphclient.CreateLoadBalancerInput{Name: name, ProviderID: prov.ID, OwnerID: "", LocationID: locationID}, + errorMsg: "value is less than the required length", + }, + { + TestName: "fails to create loadbalancer with empty locationID", + Input: graphclient.CreateLoadBalancerInput{Name: name, ProviderID: prov.ID, OwnerID: ownerID, LocationID: ""}, + errorMsg: "value is less than the required length", + }, + } + + for _, tt := range testCases { + t.Run(tt.TestName, func(t *testing.T) { + tt := tt + t.Parallel() + + resp, err := graphTestClient().LoadBalancerCreate(ctx, tt.Input) + + if tt.errorMsg != "" { + require.Error(t, err) + assert.ErrorContains(t, err, tt.errorMsg) + assert.Nil(t, resp) + + return + } + + require.NoError(t, err) + require.NotNil(t, resp) + require.NotNil(t, resp.LoadBalancerCreate) + + createdLB := resp.LoadBalancerCreate.LoadBalancer + assert.Equal(t, tt.ExpectedLB.Name, createdLB.Name) + assert.Equal(t, "loadbal", createdLB.ID.Prefix()) + assert.Equal(t, prov.ID, createdLB.LoadBalancerProvider.ID) + assert.Equal(t, locationID, createdLB.Location.ID) + assert.Equal(t, ownerID, createdLB.Owner.ID) + }) + } +} + +func TestUpdate_loadBalancer(t *testing.T) { + ctx := context.Background() + + // Permit request + ctx = context.WithValue(ctx, permissions.CheckerCtxKey, permissions.DefaultAllowChecker) + + lb := (&LoadBalancerBuilder{}).MustNew(ctx) + updateName := gofakeit.DomainName() + emptyName := "" + + testCases := []struct { + TestName string + Input graphclient.UpdateLoadBalancerInput + ExpectedLB *ent.LoadBalancer + errorMsg string + }{ + { + TestName: "updates loadbalancer", + Input: graphclient.UpdateLoadBalancerInput{Name: &updateName}, + ExpectedLB: &ent.LoadBalancer{ + Name: updateName, + ProviderID: lb.ProviderID, + OwnerID: lb.OwnerID, + LocationID: lb.LocationID, + }, + }, + { + TestName: "fails to update name to empty", + Input: graphclient.UpdateLoadBalancerInput{Name: &emptyName}, + errorMsg: "value is less than the required length", + }, + } + + for _, tt := range testCases { + t.Run(tt.TestName, func(t *testing.T) { + resp, err := graphTestClient().LoadBalancerUpdate(ctx, lb.ID, tt.Input) + + if tt.errorMsg != "" { + require.Error(t, err) + assert.ErrorContains(t, err, tt.errorMsg) + assert.Nil(t, resp) + + return + } + + require.NoError(t, err) + require.NotNil(t, resp) + require.NotNil(t, resp.LoadBalancerUpdate) + + updatedLB := resp.LoadBalancerUpdate.LoadBalancer + assert.Equal(t, tt.ExpectedLB.Name, updatedLB.Name) + assert.Equal(t, lb.ID, updatedLB.ID) + }) + } +} + +func TestDelete_loadBalancer(t *testing.T) { + ctx := context.Background() + + // Permit request + ctx = context.WithValue(ctx, permissions.CheckerCtxKey, permissions.DefaultAllowChecker) + + lb := (&LoadBalancerBuilder{}).MustNew(ctx) + + testCases := []struct { + TestName string + Input gidx.PrefixedID + ExpectedID gidx.PrefixedID + errorMsg string + }{ + { + TestName: "deletes loadbalancer", + Input: lb.ID, + ExpectedID: lb.ID, + }, + { + TestName: "fails to delete loadbalancer that does not exist", + Input: gidx.PrefixedID("loadbal-dne"), + errorMsg: "load_balancer not found", + }, + { + TestName: "fails to delete empty loadbalancer ID", + Input: gidx.PrefixedID(""), + errorMsg: "load_balancer not found", + }, + } + + for _, tt := range testCases { + t.Run(tt.TestName, func(t *testing.T) { + resp, err := graphTestClient().LoadBalancerDelete(ctx, tt.Input) + + if tt.errorMsg != "" { + require.Error(t, err) + assert.ErrorContains(t, err, tt.errorMsg) + assert.Nil(t, resp) + + return + } + + require.NoError(t, err) + require.NotNil(t, resp) + require.NotNil(t, resp.LoadBalancerDelete) + + deletedLB := resp.LoadBalancerDelete + assert.EqualValues(t, tt.ExpectedID, deletedLB.DeletedID) + }) + } +} + func TestFullLoadBalancerLifecycle(t *testing.T) { ctx := context.Background() diff --git a/internal/graphapi/port.resolvers.go b/internal/graphapi/port.resolvers.go index 39abc1dba..1704ed1f4 100644 --- a/internal/graphapi/port.resolvers.go +++ b/internal/graphapi/port.resolvers.go @@ -6,10 +6,12 @@ package graphapi import ( "context" + "strings" - "go.infratographer.com/load-balancer-api/internal/ent/generated" "go.infratographer.com/permissions-api/pkg/permissions" "go.infratographer.com/x/gidx" + + "go.infratographer.com/load-balancer-api/internal/ent/generated" ) // LoadBalancerPortCreate is the resolver for the loadBalancerPortCreate field. @@ -20,8 +22,14 @@ func (r *mutationResolver) LoadBalancerPortCreate(ctx context.Context, input gen p, err := r.client.Port.Create().SetInput(input).Save(ctx) if err != nil { - return nil, err + switch { + case generated.IsConstraintError(err) && strings.Contains(err.Error(), "number"): + return nil, ErrPortNumberInUse + default: + return nil, err + } } + return &LoadBalancerPortCreatePayload{LoadBalancerPort: p}, nil } @@ -38,7 +46,11 @@ func (r *mutationResolver) LoadBalancerPortUpdate(ctx context.Context, id gidx.P p, err = p.Update().SetInput(input).Save(ctx) if err != nil { - return nil, err + if generated.IsConstraintError(err) && strings.Contains(err.Error(), "number") { + return nil, ErrPortNumberInUse + } else { + return nil, err + } } return &LoadBalancerPortUpdatePayload{LoadBalancerPort: p}, nil diff --git a/internal/graphapi/port_test.go b/internal/graphapi/port_test.go index a115b03f4..689a53c1c 100644 --- a/internal/graphapi/port_test.go +++ b/internal/graphapi/port_test.go @@ -9,10 +9,260 @@ import ( "github.com/stretchr/testify/require" "go.infratographer.com/permissions-api/pkg/permissions" + "go.infratographer.com/x/gidx" "go.infratographer.com/load-balancer-api/internal/graphclient" ) +func TestCreate_LoadbalancerPort(t *testing.T) { + ctx := context.Background() + + // Permit request + ctx = context.WithValue(ctx, permissions.CheckerCtxKey, permissions.DefaultAllowChecker) + + lb := (&LoadBalancerBuilder{}).MustNew(ctx) + _ = (&PortBuilder{Name: "port80", LoadBalancerID: lb.ID, Number: 80}).MustNew(ctx) + + testCases := []struct { + TestName string + Input graphclient.CreateLoadBalancerPortInput + Expected *graphclient.LoadBalancerPort + errorMsg string + }{ + { + TestName: "creates loadbalancer port", + Input: graphclient.CreateLoadBalancerPortInput{ + Name: "lb-port", + LoadBalancerID: lb.ID, + Number: 22, + }, + Expected: &graphclient.LoadBalancerPort{ + Name: "lb-port", + Number: 22, + }, + }, + { + TestName: "fails to create loadbalancer port with empty name", + Input: graphclient.CreateLoadBalancerPortInput{ + Name: "", + LoadBalancerID: lb.ID, + Number: 22, + }, + errorMsg: "value is less than the required length", + }, + { + TestName: "fails to create loadbalancer port with empty loadbalancer id", + Input: graphclient.CreateLoadBalancerPortInput{ + Name: "lb-port", + LoadBalancerID: "", + Number: 22, + }, + errorMsg: "value is less than the required length", + }, + { + TestName: "fails to create loadbalancer port with number < min", + Input: graphclient.CreateLoadBalancerPortInput{ + Name: "lb-port", + LoadBalancerID: lb.ID, + Number: 0, + }, + errorMsg: "value out of range", + }, + { + TestName: "fails to create loadbalancer port with number > max", + Input: graphclient.CreateLoadBalancerPortInput{ + Name: "lb-port", + LoadBalancerID: lb.ID, + Number: 65536, + }, + errorMsg: "value out of range", + }, + { + TestName: "fails to create loadbalancer port with duplicate port number", + Input: graphclient.CreateLoadBalancerPortInput{ + Name: "lb-port", + LoadBalancerID: lb.ID, + Number: 80, + }, + errorMsg: "port number already in use", + }, + } + + for _, tt := range testCases { + t.Run(tt.TestName, func(t *testing.T) { + tt := tt + t.Parallel() + + resp, err := graphTestClient().LoadBalancerPortCreate(ctx, tt.Input) + + if tt.errorMsg != "" { + require.Error(t, err) + assert.ErrorContains(t, err, tt.errorMsg) + assert.Nil(t, resp) + + return + } + + require.NoError(t, err) + require.NotNil(t, resp) + require.NotNil(t, resp.LoadBalancerPortCreate) + + createdPort := resp.LoadBalancerPortCreate.LoadBalancerPort + require.NotNil(t, createdPort.ID) + require.Equal(t, tt.Expected.Name, createdPort.Name) + require.Equal(t, tt.Expected.Number, createdPort.Number) + require.Equal(t, "loadprt", createdPort.ID.Prefix()) + require.Equal(t, lb.ID, createdPort.LoadBalancer.ID) + }) + } +} + +func TestUpdate_LoadbalancerPort(t *testing.T) { + ctx := context.Background() + + // Permit request + ctx = context.WithValue(ctx, permissions.CheckerCtxKey, permissions.DefaultAllowChecker) + + lb := (&LoadBalancerBuilder{}).MustNew(ctx) + port := (&PortBuilder{Name: "port80", LoadBalancerID: lb.ID, Number: 80}).MustNew(ctx) + _ = (&PortBuilder{Name: "dupeport8080", LoadBalancerID: lb.ID, Number: 8080}).MustNew(ctx) + + testCases := []struct { + TestName string + Input graphclient.UpdateLoadBalancerPortInput + Expected *graphclient.LoadBalancerPort + errorMsg string + }{ + { + TestName: "fails to update loadbalancer port number to duplicate of another port", + Input: graphclient.UpdateLoadBalancerPortInput{ + Number: newInt64(8080), + }, + errorMsg: "port number already in use", + }, + { + TestName: "updates loadbalancer port name", + Input: graphclient.UpdateLoadBalancerPortInput{ + Name: newString("lb-port"), + }, + Expected: &graphclient.LoadBalancerPort{ + Name: "lb-port", + Number: 80, + }, + }, + { + TestName: "updates loadbalancer port number", + Input: graphclient.UpdateLoadBalancerPortInput{ + Number: newInt64(22), + }, + Expected: &graphclient.LoadBalancerPort{ + Name: "lb-port", + Number: 22, + }, + }, + { + TestName: "fails to update loadbalancer port name to empty", + Input: graphclient.UpdateLoadBalancerPortInput{ + Name: newString(""), + }, + errorMsg: "value is less than the required length", + }, + { + TestName: "fails to update loadbalancer port number < min", + Input: graphclient.UpdateLoadBalancerPortInput{ + Number: newInt64(0), + }, + errorMsg: "value out of range", + }, + { + TestName: "fails to update loadbalancer port number > max", + Input: graphclient.UpdateLoadBalancerPortInput{ + Number: newInt64(65536), + }, + errorMsg: "value out of range", + }, + } + + for _, tt := range testCases { + t.Run(tt.TestName, func(t *testing.T) { + resp, err := graphTestClient().LoadBalancerPortUpdate(ctx, port.ID, tt.Input) + + if tt.errorMsg != "" { + require.Error(t, err) + assert.ErrorContains(t, err, tt.errorMsg) + assert.Nil(t, resp) + + return + } + + require.NoError(t, err) + require.NotNil(t, resp) + require.NotNil(t, resp.LoadBalancerPortUpdate) + + updatedPort := resp.LoadBalancerPortUpdate.LoadBalancerPort + require.NotNil(t, updatedPort.ID) + require.Equal(t, tt.Expected.Name, updatedPort.Name) + require.Equal(t, tt.Expected.Number, updatedPort.Number) + require.Equal(t, "loadprt", updatedPort.ID.Prefix()) + }) + } +} + +func TestDelete_LoadbalancerPort(t *testing.T) { + ctx := context.Background() + + // Permit request + ctx = context.WithValue(ctx, permissions.CheckerCtxKey, permissions.DefaultAllowChecker) + + lb := (&LoadBalancerBuilder{}).MustNew(ctx) + port := (&PortBuilder{Name: "port80", LoadBalancerID: lb.ID, Number: 80}).MustNew(ctx) + + testCases := []struct { + TestName string + Input gidx.PrefixedID + errorMsg string + }{ + { + TestName: "deletes loadbalancer port", + Input: port.ID, + }, + { + TestName: "fails to delete loadbalancer port that does not exist", + Input: gidx.PrefixedID("loadprt-dne"), + errorMsg: "port not found", + }, + { + TestName: "fails to delete empty loadbalancer port ID", + Input: gidx.PrefixedID(""), + errorMsg: "port not found", + }, + } + + for _, tt := range testCases { + t.Run(tt.TestName, func(t *testing.T) { + tt := tt + t.Parallel() + resp, err := graphTestClient().LoadBalancerPortDelete(ctx, tt.Input) + + if tt.errorMsg != "" { + require.Error(t, err) + assert.ErrorContains(t, err, tt.errorMsg) + assert.Nil(t, resp) + + return + } + + require.NoError(t, err) + require.NotNil(t, resp) + require.NotNil(t, resp.LoadBalancerPortDelete) + + deletedPortID := resp.LoadBalancerPortDelete.DeletedID + require.NotNil(t, deletedPortID) + require.Equal(t, tt.Input, deletedPortID) + }) + } +} + func TestFullLoadBalancerPortLifecycle(t *testing.T) { ctx := context.Background() diff --git a/internal/graphapi/provider_test.go b/internal/graphapi/provider_test.go index 8dd4388c3..e60f727f1 100644 --- a/internal/graphapi/provider_test.go +++ b/internal/graphapi/provider_test.go @@ -48,6 +48,9 @@ func TestQuery_loadBalancerProvider(t *testing.T) { for _, tt := range testCases { t.Run(tt.TestName, func(t *testing.T) { + tt := tt + t.Parallel() + resp, err := graphTestClient().GetLoadBalancerProvider(ctx, tt.QueryID) if tt.errorMsg != "" { @@ -66,6 +69,176 @@ func TestQuery_loadBalancerProvider(t *testing.T) { } } +func TestCreate_Provider(t *testing.T) { + ctx := context.Background() + + // Permit request + ctx = context.WithValue(ctx, permissions.CheckerCtxKey, permissions.DefaultAllowChecker) + + ownerID := gidx.MustNewID(ownerPrefix) + name := gofakeit.DomainName() + + testCases := []struct { + TestName string + Input graphclient.CreateLoadBalancerProviderInput + ExpectedLB *ent.LoadBalancerProvider + errorMsg string + }{ + { + TestName: "creates provider", + Input: graphclient.CreateLoadBalancerProviderInput{Name: name, OwnerID: ownerID}, + ExpectedLB: &ent.LoadBalancerProvider{ + Name: name, + OwnerID: ownerID, + }, + }, + { + TestName: "fails to create provider with empty name", + Input: graphclient.CreateLoadBalancerProviderInput{Name: "", OwnerID: ownerID}, + errorMsg: "value is less than the required length", + }, + { + TestName: "fails to create provider with empty ownerID", + Input: graphclient.CreateLoadBalancerProviderInput{Name: name, OwnerID: ""}, + errorMsg: "value is less than the required length", + }, + } + + for _, tt := range testCases { + t.Run(tt.TestName, func(t *testing.T) { + tt := tt + t.Parallel() + + resp, err := graphTestClient().LoadBalancerProviderCreate(ctx, tt.Input) + + if tt.errorMsg != "" { + require.Error(t, err) + assert.ErrorContains(t, err, tt.errorMsg) + assert.Nil(t, resp) + + return + } + + require.NoError(t, err) + require.NotNil(t, resp) + require.NotNil(t, resp.LoadBalancerProviderCreate) + + createdProvider := resp.LoadBalancerProviderCreate.LoadBalancerProvider + assert.Equal(t, tt.ExpectedLB.Name, createdProvider.Name) + assert.Equal(t, "loadpvd", createdProvider.ID.Prefix()) + assert.Equal(t, ownerID, createdProvider.Owner.ID) + }) + } +} + +func TestUpdate_Provider(t *testing.T) { + ctx := context.Background() + + // Permit request + ctx = context.WithValue(ctx, permissions.CheckerCtxKey, permissions.DefaultAllowChecker) + + prov := ProviderBuilder{}.MustNew(ctx) + updateName := gofakeit.DomainName() + emptyName := "" + + testCases := []struct { + TestName string + Input graphclient.UpdateLoadBalancerProviderInput + ExpectedProvider *ent.LoadBalancerProvider + errorMsg string + }{ + { + TestName: "updates provider", + Input: graphclient.UpdateLoadBalancerProviderInput{Name: &updateName}, + ExpectedProvider: &ent.LoadBalancerProvider{ + Name: updateName, + ID: prov.ID, + OwnerID: prov.OwnerID, + }, + }, + { + TestName: "fails to update name to empty", + Input: graphclient.UpdateLoadBalancerProviderInput{Name: &emptyName}, + errorMsg: "value is less than the required length", + }, + } + + for _, tt := range testCases { + t.Run(tt.TestName, func(t *testing.T) { + resp, err := graphTestClient().LoadBalancerProviderUpdate(ctx, prov.ID, tt.Input) + + if tt.errorMsg != "" { + require.Error(t, err) + assert.ErrorContains(t, err, tt.errorMsg) + assert.Nil(t, resp) + + return + } + + require.NoError(t, err) + require.NotNil(t, resp) + require.NotNil(t, resp.LoadBalancerProviderUpdate) + + updatedProvider := resp.LoadBalancerProviderUpdate.LoadBalancerProvider + assert.Equal(t, tt.ExpectedProvider.Name, updatedProvider.Name) + assert.Equal(t, prov.ID, updatedProvider.ID) + }) + } +} + +func TestDelete_Provider(t *testing.T) { + ctx := context.Background() + + // Permit request + ctx = context.WithValue(ctx, permissions.CheckerCtxKey, permissions.DefaultAllowChecker) + + prov := ProviderBuilder{}.MustNew(ctx) + + testCases := []struct { + TestName string + Input gidx.PrefixedID + ExpectedID gidx.PrefixedID + errorMsg string + }{ + { + TestName: "deletes provider", + Input: prov.ID, + ExpectedID: prov.ID, + }, + { + TestName: "fails to delete provider that does not exist", + Input: gidx.PrefixedID("loadpvd-dne"), + errorMsg: "provider not found", + }, + { + TestName: "fails to delete empty provider ID", + Input: gidx.PrefixedID(""), + errorMsg: "provider not found", + }, + } + + for _, tt := range testCases { + t.Run(tt.TestName, func(t *testing.T) { + resp, err := graphTestClient().LoadBalancerProviderDelete(ctx, prov.ID) + + if tt.errorMsg != "" { + require.Error(t, err) + assert.ErrorContains(t, err, tt.errorMsg) + assert.Nil(t, resp) + + return + } + + require.NoError(t, err) + require.NotNil(t, resp) + require.NotNil(t, resp.LoadBalancerProviderDelete) + + deletedProvider := resp.LoadBalancerProviderDelete + assert.Equal(t, tt.Input, deletedProvider.DeletedID) + }) + } +} + func TestFullProviderLifecycle(t *testing.T) { ctx := context.Background() diff --git a/internal/graphapi/tools_test.go b/internal/graphapi/tools_test.go index e0f726ad0..c49384318 100644 --- a/internal/graphapi/tools_test.go +++ b/internal/graphapi/tools_test.go @@ -259,6 +259,7 @@ func newBool(b bool) *bool { return &b } -func newInt64(i int64) *int64 { - return &i +func newInt64(i int) *int64 { + r := int64(i) + return &r }