diff --git a/netloadbalancer.go b/netloadbalancer.go new file mode 100644 index 000000000..32ddf7bcb --- /dev/null +++ b/netloadbalancer.go @@ -0,0 +1,125 @@ +package linodego + +import ( + "context" + "encoding/json" + "time" + + "github.com/linode/linodego/internal/parseabletime" +) + +type NetLoadBalancer struct { + // This NetLoadBalancer's unique ID. + ID int `json:"id"` + // This NetLoadBalancer's label. These must be unique on your Account. + Label string `json:"label"` + // The Region where this NetLoadBalancer is located. + Region string `json:"region"` + // The IPv4 address of this NetLoadBalancer. + AddressV4 string `json:"address_v4"` + // The IPv6 address of this NetLoadBalancer. + AddressV6 string `json:"address_v6"` + // The status of this NetLoadBalancer. + Status string `json:"status"` + // This NetLoadBalancer's date and time of creation. + Created *time.Time `json:"-"` + // This NetLoadBalancer's date and time of last update. + Updated *time.Time `json:"-"` + // This NetLoadBalancer's date and time of last composite update. + LastCompositeUpdated *time.Time `json:"-"` + // An array of listeners for this NetLoadBalancer. + Listeners []NetLoadBalancerListener `json:"listeners"` +} + +type NetLoadBalancerCreateOptions struct { + // This NetLoadBalancer's label. These must be unique on your Account. + Label string `json:"label"` + // The Region where this NetLoadBalancer is located. + Region string `json:"region"` + // An array of listeners for this NetLoadBalancer. + Listeners []NetLoadBalancerListenerCreateOptions `json:"listeners,omitempty"` +} + +type NetLoadBalancerUpdateOptions struct { + // This NetLoadBalancer's label. These must be unique on your Account. + Label string `json:"label,omitempty"` + // An array of listeners for this NetLoadBalancer. + Listeners []NetLoadBalancerListenerUpdateOptions `json:"listeners,omitempty"` +} + +func (i *NetLoadBalancer) UnmarshalJSON(b []byte) error { + type Mask NetLoadBalancer + + p := struct { + *Mask + + Created *parseabletime.ParseableTime `json:"created"` + Updated *parseabletime.ParseableTime `json:"updated"` + LastCompositeUpdated *parseabletime.ParseableTime `json:"last_composite_updated"` + }{ + Mask: (*Mask)(i), + } + + if err := json.Unmarshal(b, &p); err != nil { + return err + } + + i.Created = (*time.Time)(p.Created) + i.Updated = (*time.Time)(p.Updated) + i.LastCompositeUpdated = (*time.Time)(p.LastCompositeUpdated) + + return nil +} + +func (i *NetLoadBalancer) GetCreateOptions() NetLoadBalancerCreateOptions { + opts := make([]NetLoadBalancerListenerCreateOptions, len(i.Listeners)) + for i, listener := range i.Listeners { + opts[i] = listener.GetCreateOptions() + } + + return NetLoadBalancerCreateOptions{ + Label: i.Label, + Region: i.Region, + Listeners: opts, + } +} + +func (i *NetLoadBalancer) GetUpdateOptions() NetLoadBalancerUpdateOptions { + opts := make([]NetLoadBalancerListenerUpdateOptions, len(i.Listeners)) + for i, listener := range i.Listeners { + opts[i] = listener.GetUpdateOptions() + } + + return NetLoadBalancerUpdateOptions{ + Label: i.Label, + Listeners: opts, + } +} + +// ListNetLoadBalancers retrieves a list of NetLoadBalancers +func (c *Client) ListNetLoadBalancers(ctx context.Context, opts *ListOptions) ([]NetLoadBalancer, error) { + return getPaginatedResults[NetLoadBalancer](ctx, c, "netloadbalancers", opts) +} + +// GetNetLoadBalancer retrieves a NetLoadBalancer by ID +func (c *Client) GetNetLoadBalancer(ctx context.Context, netloadbalancerID int) (*NetLoadBalancer, error) { + e := formatAPIPath("netloadbalancers/%d", netloadbalancerID) + return doGETRequest[NetLoadBalancer](ctx, c, e) +} + +// CreateNetLoadBalancer creates a new NetLoadBalancer +func (c *Client) CreateNetLoadBalancer(ctx context.Context, opts NetLoadBalancerCreateOptions) (*NetLoadBalancer, error) { + return doPOSTRequest[NetLoadBalancer](ctx, c, "netloadbalancers", opts) +} + +// UpdateNetLoadBalancer updates a NetLoadBalancer +func (c *Client) UpdateNetLoadBalancer(ctx context.Context, netloadbalancerID int, opts NetLoadBalancerUpdateOptions) (*NetLoadBalancer, error) { + e := formatAPIPath("netloadbalancers/%d", netloadbalancerID) + return doPUTRequest[NetLoadBalancer](ctx, c, e, opts) +} + +// DeleteNetLoadBalancer deletes a NetLoadBalancer +func (c *Client) DeleteNetLoadBalancer(ctx context.Context, netloadbalancerID int) error { + e := formatAPIPath("netloadbalancers/%d", netloadbalancerID) + return doDELETERequest(ctx, c, e) +} diff --git a/netloadbalancer_listeners.go b/netloadbalancer_listeners.go new file mode 100644 index 000000000..65277ec72 --- /dev/null +++ b/netloadbalancer_listeners.go @@ -0,0 +1,148 @@ +package linodego + +import ( + "context" + "encoding/json" + "time" + + "github.com/linode/linodego/internal/parseabletime" +) + +type NetLoadBalancerListener struct { + // This NetLoadBalancerListener's unique ID. + ID int `json:"id"` + // The protocol of this NetLoadBalancerListener. + Protocol string `json:"protocol"` + // The port of this NetLoadBalancerListener. + Port int `json:"port"` + // This NetLoadBalancerListener's label. + Label string `json:"label"` + // This NetLoadBalancerListener's date and time of creation. + Created *time.Time `json:"-"` + // This NetLoadBalancerListener's date and time of last update. + Updated *time.Time `json:"-"` +} + +// NetLoadBalancerListenerCreateOptions fields are those accepted by CreateNetLoadBalancerListener +type NetLoadBalancerListenerCreateOptions struct { + // The protocol of this NetLoadBalancerListener. + Protocol string `json:"protocol,omitempty"` + // The port of this NetLoadBalancerListener. + Port int `json:"port,omitempty"` + // The label of this NetLoadBalancerListener. + Label string `json:"label,omitempty"` + // The nodes of this NetLoadBalancerListener. + Nodes []NetLoadBalancerNodeCreateOptions `json:"nodes,omitempty"` +} + +type NetLoadBalancerListenerUpdateOptions struct { + // The protocol of this NetLoadBalancerListener. + Protocol string `json:"protocol,omitempty"` + // The port of this NetLoadBalancerListener. + Port int `json:"port"` + // The label of this NetLoadBalancerListener. + Label string `json:"label,omitempty"` + // The nodes of this NetLoadBalancerListener. + Nodes []NetLoadBalancerNodeUpdateOptions `json:"nodes,omitempty"` +} + +type NetLoadBalancerListenerNodeWeightsUpdateOptions struct { + // The nodes of this NetLoadBalancerListener. + Nodes []NetLoadBalancerListenerNodeWeightUpdateOptions `json:"nodes,omitempty"` +} + +type NetLoadBalancerListenerNodeWeightUpdateOptions struct { + // The ID of the node to update. + ID int `json:"id"` + // The weight of the node. + Weight int `json:"weight"` +} + +func (i *NetLoadBalancerListener) UnmarshalJSON(b []byte) error { + type Mask NetLoadBalancerListener + + p := struct { + *Mask + + Created *parseabletime.ParseableTime `json:"created"` + Updated *parseabletime.ParseableTime `json:"updated"` + }{ + Mask: (*Mask)(i), + } + + if err := json.Unmarshal(b, &p); err != nil { + return err + } + + i.Created = (*time.Time)(p.Created) + i.Updated = (*time.Time)(p.Updated) + + return nil +} + +// GetCreateOptions returns the create options for the NetLoadBalancerListener but does not include the nodes +func (i *NetLoadBalancerListener) GetCreateOptions() NetLoadBalancerListenerCreateOptions { + return NetLoadBalancerListenerCreateOptions{ + Protocol: i.Protocol, + Port: i.Port, + Label: i.Label, + } +} + +func (i *NetLoadBalancerListener) GetUpdateOptions() NetLoadBalancerListenerUpdateOptions { + return NetLoadBalancerListenerUpdateOptions{ + Label: i.Label, + } +} + +// CreateNetLoadBalancerListener creates a new NetLoadBalancerListener +func (c *Client) CreateNetLoadBalancerListener( + ctx context.Context, + netloadbalancerID int, + opts NetLoadBalancerListenerCreateOptions, +) (*NetLoadBalancerListener, error) { + e := formatAPIPath("netloadbalancers/%d/listeners", netloadbalancerID) + return doPOSTRequest[NetLoadBalancerListener](ctx, c, e, opts) +} + +// ListNetLoadBalancerListeners retrieves a list of NetLoadBalancerListeners +func (c *Client) ListNetLoadBalancerListeners(ctx context.Context, netloadbalancerID int, opts *ListOptions) ([]NetLoadBalancerListener, error) { + e := formatAPIPath("netloadbalancers/%d/listeners", netloadbalancerID) + return getPaginatedResults[NetLoadBalancerListener](ctx, c, e, opts) +} + +// GetNetLoadBalancerListener retrieves a NetLoadBalancerListener by ID +func (c *Client) GetNetLoadBalancerListener(ctx context.Context, netloadbalancerID int, listenerID int) (*NetLoadBalancerListener, error) { + e := formatAPIPath("netloadbalancers/%d/listeners/%d", netloadbalancerID, listenerID) + return doGETRequest[NetLoadBalancerListener](ctx, c, e) +} + +// UpdateNetLoadBalancerListener updates a NetLoadBalancerListener +func (c *Client) UpdateNetLoadBalancerListener( + ctx context.Context, + netloadbalancerID int, + listenerID int, + opts NetLoadBalancerListenerUpdateOptions, +) (*NetLoadBalancerListener, error) { + e := formatAPIPath("netloadbalancers/%d/listeners/%d", netloadbalancerID, listenerID) + return doPUTRequest[NetLoadBalancerListener](ctx, c, e, opts) +} + +// DeleteNetLoadBalancerListener deletes a NetLoadBalancerListener +func (c *Client) DeleteNetLoadBalancerListener(ctx context.Context, netloadbalancerID int, listenerID int) error { + e := formatAPIPath("netloadbalancers/%d/listeners/%d", netloadbalancerID, listenerID) + return doDELETERequest(ctx, c, e) +} + +// UpdateNetLoadBalancerListenerNodeWeights updates the weights of the nodes of a NetLoadBalancerListener +// Use this to update the weights of the nodes of a NetLoadBalancerListener in case of frequent changes +// High frequency updates are allowed. No response is returned. +func (c *Client) UpdateNetLoadBalancerListenerNodeWeights( + ctx context.Context, + netloadbalancerID int, + listenerID int, + opts NetLoadBalancerListenerNodeWeightsUpdateOptions, +) error { + e := formatAPIPath("netloadbalancers/%d/listeners/%d/node-weights", netloadbalancerID, listenerID) + return doPOSTRequestNoResponseBody(ctx, c, e, opts) +} diff --git a/netloadbalancer_nodes.go b/netloadbalancer_nodes.go new file mode 100644 index 000000000..9394b6ed4 --- /dev/null +++ b/netloadbalancer_nodes.go @@ -0,0 +1,132 @@ +package linodego + +import ( + "context" + "encoding/json" + "time" + + "github.com/linode/linodego/internal/parseabletime" +) + +type NetLoadBalancerNode struct { + // This NetLoadBalancerNode's unique ID. + ID int `json:"id"` + // The ID of the Linode this NetLoadBalancerNode is associated with. + LinodeID int `json:"linode_id"` + // The IPv6 address of this NetLoadBalancerNode. + AddressV6 string `json:"address_v6"` + // This NetLoadBalancerNode's label. + Label string `json:"label"` + // The weight of this NetLoadBalancerNode. + Weight int `json:"weight"` + // This NetLoadBalancerNode's date and time of creation. + Created *time.Time `json:"-"` + // This NetLoadBalancerNode's date and time of last update. + Updated *time.Time `json:"-"` + // This NetLoadBalancerNode's date and time of last weight update. + WeightUpdated *time.Time `json:"-"` +} + +type NetLoadBalancerNodeCreateOptions struct { + // The label of the node. + Label string `json:"label"` + // The IPv6 address of the node. + AddressV6 string `json:"address_v6"` + // The weight of the node. + Weight int `json:"weight,omitempty"` +} + +type NetLoadBalancerNodeUpdateOptions struct { + // The label of the node. + Label string `json:"label"` + // The IPv6 address of the node. + AddressV6 string `json:"address_v6"` + // The weight of the node. + Weight int `json:"weight,omitempty"` +} + +type NetLoadBalancerNodeLabelUpdateOptions struct { + // The label of the node. + Label string `json:"label"` +} + +func (i *NetLoadBalancerNode) UnmarshalJSON(b []byte) error { + type Mask NetLoadBalancerNode + + p := struct { + *Mask + + Created *parseabletime.ParseableTime `json:"created"` + Updated *parseabletime.ParseableTime `json:"updated"` + WeightUpdated *parseabletime.ParseableTime `json:"weight_updated"` + }{ + Mask: (*Mask)(i), + } + + if err := json.Unmarshal(b, &p); err != nil { + return err + } + + i.Created = (*time.Time)(p.Created) + i.Updated = (*time.Time)(p.Updated) + i.WeightUpdated = (*time.Time)(p.WeightUpdated) + + return nil +} + +func (i *NetLoadBalancerNode) GetCreateOptions() NetLoadBalancerNodeCreateOptions { + return NetLoadBalancerNodeCreateOptions{ + Label: i.Label, + AddressV6: i.AddressV6, + Weight: i.Weight, + } +} + +func (i *NetLoadBalancerNode) GetUpdateOptions() NetLoadBalancerNodeUpdateOptions { + return NetLoadBalancerNodeUpdateOptions{ + Label: i.Label, + AddressV6: i.AddressV6, + Weight: i.Weight, + } +} + +// CreateNetLoadBalancerNode creates a new NetLoadBalancerNode +func (c *Client) CreateNetLoadBalancerNode( + ctx context.Context, + netloadbalancerID int, + listenerID int, + opts NetLoadBalancerNodeCreateOptions, +) (*NetLoadBalancerNode, error) { + e := formatAPIPath("netloadbalancers/%d/listeners/%d/nodes", netloadbalancerID, listenerID) + return doPOSTRequest[NetLoadBalancerNode](ctx, c, e, opts) +} + +// ListNetLoadBalancerNodes retrieves a list of NetLoadBalancerNodes +func (c *Client) ListNetLoadBalancerNodes(ctx context.Context, netloadbalancerID int, listenerID int, opts *ListOptions) ([]NetLoadBalancerNode, error) { + e := formatAPIPath("netloadbalancers/%d/listeners/%d/nodes", netloadbalancerID, listenerID) + return getPaginatedResults[NetLoadBalancerNode](ctx, c, e, opts) +} + +// GetNetLoadBalancerNode retrieves a NetLoadBalancerNode by ID +func (c *Client) GetNetLoadBalancerNode(ctx context.Context, netloadbalancerID int, listenerID int, nodeID int) (*NetLoadBalancerNode, error) { + e := formatAPIPath("netloadbalancers/%d/listeners/%d/nodes/%d", netloadbalancerID, listenerID, nodeID) + return doGETRequest[NetLoadBalancerNode](ctx, c, e) +} + +// UpdateNetLoadBalancerNode updates a NetLoadBalancerNode +func (c *Client) UpdateNetLoadBalancerNode( + ctx context.Context, + netloadbalancerID int, + listenerID int, + nodeID int, + opts NetLoadBalancerNodeLabelUpdateOptions, +) (*NetLoadBalancerNode, error) { + e := formatAPIPath("netloadbalancers/%d/listeners/%d/nodes/%d", netloadbalancerID, listenerID, nodeID) + return doPUTRequest[NetLoadBalancerNode](ctx, c, e, opts) +} + +// DeleteNetLoadBalancerNode deletes a NetLoadBalancerNode +func (c *Client) DeleteNetLoadBalancerNode(ctx context.Context, netloadbalancerID int, listenerID int, nodeID int) error { + e := formatAPIPath("netloadbalancers/%d/listeners/%d/nodes/%d", netloadbalancerID, listenerID, nodeID) + return doDELETERequest(ctx, c, e) +} diff --git a/test/unit/fixtures/netloadbalancer_create.json b/test/unit/fixtures/netloadbalancer_create.json new file mode 100644 index 000000000..5a6513657 --- /dev/null +++ b/test/unit/fixtures/netloadbalancer_create.json @@ -0,0 +1,12 @@ +{ + "id": 123, + "label": "Test NetLoadBalancer", + "region": "us-east", + "address_v4": "192.0.2.1", + "address_v6": "2600:3c03::f03c:91ff:fe24:1234", + "status": "active", + "listeners": [], + "created": "2025-01-15T08:00:00", + "updated": "2025-02-05T12:00:00", + "last_composite_updated": "2025-02-05T12:00:00" +} \ No newline at end of file diff --git a/test/unit/fixtures/netloadbalancer_create_complex.json b/test/unit/fixtures/netloadbalancer_create_complex.json new file mode 100644 index 000000000..7fd0d072e --- /dev/null +++ b/test/unit/fixtures/netloadbalancer_create_complex.json @@ -0,0 +1,29 @@ +{ + "id": 999, + "label": "Complex NetLoadBalancer", + "region": "us-central", + "address_v4": "192.0.2.99", + "address_v6": "2600:3c03::f03c:91ff:fe24:9999", + "status": "active", + "listeners": [ + { + "id": 1, + "protocol": "tcp", + "port": 80, + "label": "HTTP Listener", + "created": "2025-01-15T09:00:00", + "updated": "2025-01-15T09:00:00" + }, + { + "id": 2, + "protocol": "tcp", + "port": 443, + "label": "HTTPS Listener", + "created": "2025-01-15T09:00:00", + "updated": "2025-01-15T09:00:00" + } + ], + "created": "2025-01-15T09:00:00", + "updated": "2025-01-15T09:00:00", + "last_composite_updated": "2025-01-15T09:00:00" +} \ No newline at end of file diff --git a/test/unit/fixtures/netloadbalancer_create_with_listeners.json b/test/unit/fixtures/netloadbalancer_create_with_listeners.json new file mode 100644 index 000000000..175d60260 --- /dev/null +++ b/test/unit/fixtures/netloadbalancer_create_with_listeners.json @@ -0,0 +1,21 @@ +{ + "id": 456, + "label": "NetLoadBalancer with Listeners", + "region": "us-west", + "address_v4": "192.0.2.2", + "address_v6": "2600:3c03::f03c:91ff:fe24:5678", + "status": "active", + "listeners": [ + { + "id": 1, + "protocol": "tcp", + "port": 80, + "label": "HTTP Listener", + "created": "2025-01-15T08:30:00", + "updated": "2025-01-15T08:30:00" + } + ], + "created": "2025-01-15T08:30:00", + "updated": "2025-01-15T08:30:00", + "last_composite_updated": "2025-01-15T08:30:00" +} \ No newline at end of file diff --git a/test/unit/fixtures/netloadbalancer_get.json b/test/unit/fixtures/netloadbalancer_get.json new file mode 100644 index 000000000..5a6513657 --- /dev/null +++ b/test/unit/fixtures/netloadbalancer_get.json @@ -0,0 +1,12 @@ +{ + "id": 123, + "label": "Test NetLoadBalancer", + "region": "us-east", + "address_v4": "192.0.2.1", + "address_v6": "2600:3c03::f03c:91ff:fe24:1234", + "status": "active", + "listeners": [], + "created": "2025-01-15T08:00:00", + "updated": "2025-02-05T12:00:00", + "last_composite_updated": "2025-02-05T12:00:00" +} \ No newline at end of file diff --git a/test/unit/fixtures/netloadbalancer_listener_create.json b/test/unit/fixtures/netloadbalancer_listener_create.json new file mode 100644 index 000000000..e90a0408c --- /dev/null +++ b/test/unit/fixtures/netloadbalancer_listener_create.json @@ -0,0 +1,8 @@ +{ + "id": 456, + "protocol": "tcp", + "port": 80, + "label": "HTTP Listener", + "created": "2025-01-15T08:00:00", + "updated": "2025-01-15T08:00:00" +} \ No newline at end of file diff --git a/test/unit/fixtures/netloadbalancer_listener_create_high_port.json b/test/unit/fixtures/netloadbalancer_listener_create_high_port.json new file mode 100644 index 000000000..e9811bd93 --- /dev/null +++ b/test/unit/fixtures/netloadbalancer_listener_create_high_port.json @@ -0,0 +1,8 @@ +{ + "id": 461, + "protocol": "tcp", + "port": 65535, + "label": "High Port Listener", + "created": "2025-01-15T11:00:00", + "updated": "2025-01-15T11:00:00" +} \ No newline at end of file diff --git a/test/unit/fixtures/netloadbalancer_listener_create_https.json b/test/unit/fixtures/netloadbalancer_listener_create_https.json new file mode 100644 index 000000000..73283bdf5 --- /dev/null +++ b/test/unit/fixtures/netloadbalancer_listener_create_https.json @@ -0,0 +1,8 @@ +{ + "id": 457, + "protocol": "tcp", + "port": 443, + "label": "HTTPS Listener", + "created": "2025-01-15T08:30:00", + "updated": "2025-01-15T08:30:00" +} \ No newline at end of file diff --git a/test/unit/fixtures/netloadbalancer_listener_create_udp.json b/test/unit/fixtures/netloadbalancer_listener_create_udp.json new file mode 100644 index 000000000..bbb572c03 --- /dev/null +++ b/test/unit/fixtures/netloadbalancer_listener_create_udp.json @@ -0,0 +1,8 @@ +{ + "id": 460, + "protocol": "udp", + "port": 53, + "label": "DNS Listener", + "created": "2025-01-15T10:00:00", + "updated": "2025-01-15T10:00:00" +} \ No newline at end of file diff --git a/test/unit/fixtures/netloadbalancer_listener_create_with_nodes.json b/test/unit/fixtures/netloadbalancer_listener_create_with_nodes.json new file mode 100644 index 000000000..00c03ad16 --- /dev/null +++ b/test/unit/fixtures/netloadbalancer_listener_create_with_nodes.json @@ -0,0 +1,8 @@ +{ + "id": 458, + "protocol": "tcp", + "port": 8080, + "label": "App Listener", + "created": "2025-01-15T09:00:00", + "updated": "2025-01-15T09:00:00" +} \ No newline at end of file diff --git a/test/unit/fixtures/netloadbalancer_listener_get.json b/test/unit/fixtures/netloadbalancer_listener_get.json new file mode 100644 index 000000000..129801572 --- /dev/null +++ b/test/unit/fixtures/netloadbalancer_listener_get.json @@ -0,0 +1,8 @@ +{ + "id": 456, + "protocol": "tcp", + "port": 80, + "label": "Production HTTP Listener", + "created": "2025-01-10T08:00:00", + "updated": "2025-01-12T10:30:00" +} \ No newline at end of file diff --git a/test/unit/fixtures/netloadbalancer_listener_update.json b/test/unit/fixtures/netloadbalancer_listener_update.json new file mode 100644 index 000000000..df54ffca2 --- /dev/null +++ b/test/unit/fixtures/netloadbalancer_listener_update.json @@ -0,0 +1,8 @@ +{ + "id": 456, + "protocol": "tcp", + "port": 80, + "label": "Updated HTTP Listener", + "created": "2025-01-10T08:00:00", + "updated": "2025-02-15T16:45:00" +} \ No newline at end of file diff --git a/test/unit/fixtures/netloadbalancer_listener_update_complex.json b/test/unit/fixtures/netloadbalancer_listener_update_complex.json new file mode 100644 index 000000000..9bafe46fc --- /dev/null +++ b/test/unit/fixtures/netloadbalancer_listener_update_complex.json @@ -0,0 +1,8 @@ +{ + "id": 456, + "protocol": "tcp", + "port": 8080, + "label": "Complex Updated Listener", + "created": "2025-01-10T08:00:00", + "updated": "2025-02-20T14:20:00" +} \ No newline at end of file diff --git a/test/unit/fixtures/netloadbalancer_listeners_list.json b/test/unit/fixtures/netloadbalancer_listeners_list.json new file mode 100644 index 000000000..33d8f0b1e --- /dev/null +++ b/test/unit/fixtures/netloadbalancer_listeners_list.json @@ -0,0 +1,23 @@ +{ + "data": [ + { + "id": 456, + "protocol": "tcp", + "port": 80, + "label": "HTTP Listener", + "created": "2025-01-10T08:00:00", + "updated": "2025-01-12T10:30:00" + }, + { + "id": 457, + "protocol": "tcp", + "port": 443, + "label": "HTTPS Listener", + "created": "2025-01-10T08:30:00", + "updated": "2025-01-11T14:15:00" + } + ], + "page": 1, + "pages": 1, + "results": 2 +} \ No newline at end of file diff --git a/test/unit/fixtures/netloadbalancer_listeners_list_filtered.json b/test/unit/fixtures/netloadbalancer_listeners_list_filtered.json new file mode 100644 index 000000000..77e3b7975 --- /dev/null +++ b/test/unit/fixtures/netloadbalancer_listeners_list_filtered.json @@ -0,0 +1,15 @@ +{ + "data": [ + { + "id": 459, + "protocol": "tcp", + "port": 80, + "label": "Filtered HTTP Listener", + "created": "2025-01-10T08:00:00", + "updated": "2025-01-12T10:30:00" + } + ], + "page": 1, + "pages": 1, + "results": 1 +} \ No newline at end of file diff --git a/test/unit/fixtures/netloadbalancer_node_create.json b/test/unit/fixtures/netloadbalancer_node_create.json new file mode 100644 index 000000000..665a2a4cc --- /dev/null +++ b/test/unit/fixtures/netloadbalancer_node_create.json @@ -0,0 +1,10 @@ +{ + "id": 789, + "linode_id": 12345, + "label": "Web Server 1", + "address_v6": "2600:3c03::f03c:91ff:fe24:abcd", + "weight": 100, + "created": "2025-01-15T08:00:00", + "updated": "2025-01-15T08:00:00", + "weight_updated": "2025-01-15T08:00:00" +} \ No newline at end of file diff --git a/test/unit/fixtures/netloadbalancer_node_create_default_weight.json b/test/unit/fixtures/netloadbalancer_node_create_default_weight.json new file mode 100644 index 000000000..7a94bebdc --- /dev/null +++ b/test/unit/fixtures/netloadbalancer_node_create_default_weight.json @@ -0,0 +1,10 @@ +{ + "id": 790, + "linode_id": 67890, + "label": "Database Server", + "address_v6": "2600:3c03::f03c:91ff:fe24:1234", + "weight": 0, + "created": "2025-01-15T09:00:00", + "updated": "2025-01-15T09:00:00", + "weight_updated": "2025-01-15T09:00:00" +} \ No newline at end of file diff --git a/test/unit/fixtures/netloadbalancer_node_create_high_weight.json b/test/unit/fixtures/netloadbalancer_node_create_high_weight.json new file mode 100644 index 000000000..e33fe7fb0 --- /dev/null +++ b/test/unit/fixtures/netloadbalancer_node_create_high_weight.json @@ -0,0 +1,10 @@ +{ + "id": 792, + "linode_id": 44444, + "label": "High Weight Node", + "address_v6": "2600:3c03::f03c:91ff:fe24:high", + "weight": 999, + "created": "2025-01-15T10:00:00", + "updated": "2025-01-15T10:00:00", + "weight_updated": "2025-01-15T10:00:00" +} \ No newline at end of file diff --git a/test/unit/fixtures/netloadbalancer_node_get.json b/test/unit/fixtures/netloadbalancer_node_get.json new file mode 100644 index 000000000..6c3acd5a7 --- /dev/null +++ b/test/unit/fixtures/netloadbalancer_node_get.json @@ -0,0 +1,10 @@ +{ + "id": 789, + "linode_id": 12345, + "label": "Production Web Server", + "address_v6": "2600:3c03::f03c:91ff:fe24:prod", + "weight": 100, + "created": "2025-01-10T08:00:00", + "updated": "2025-01-12T10:30:00", + "weight_updated": "2025-01-12T10:30:00" +} \ No newline at end of file diff --git a/test/unit/fixtures/netloadbalancer_node_update.json b/test/unit/fixtures/netloadbalancer_node_update.json new file mode 100644 index 000000000..4de09d84b --- /dev/null +++ b/test/unit/fixtures/netloadbalancer_node_update.json @@ -0,0 +1,10 @@ +{ + "id": 789, + "linode_id": 12345, + "label": "Updated Web Server", + "address_v6": "2600:3c03::f03c:91ff:fe24:prod", + "weight": 100, + "created": "2025-01-10T08:00:00", + "updated": "2025-02-15T16:45:00", + "weight_updated": "2025-01-12T10:30:00" +} \ No newline at end of file diff --git a/test/unit/fixtures/netloadbalancer_nodes_list.json b/test/unit/fixtures/netloadbalancer_nodes_list.json new file mode 100644 index 000000000..598fbb79a --- /dev/null +++ b/test/unit/fixtures/netloadbalancer_nodes_list.json @@ -0,0 +1,27 @@ +{ + "data": [ + { + "id": 789, + "linode_id": 11111, + "label": "Web Server 1", + "address_v6": "2600:3c03::f03c:91ff:fe24:0001", + "weight": 100, + "created": "2025-01-10T08:00:00", + "updated": "2025-01-12T10:30:00", + "weight_updated": "2025-01-12T10:30:00" + }, + { + "id": 790, + "linode_id": 22222, + "label": "Web Server 2", + "address_v6": "2600:3c03::f03c:91ff:fe24:0002", + "weight": 50, + "created": "2025-01-10T08:30:00", + "updated": "2025-01-11T14:15:00", + "weight_updated": "2025-01-11T14:15:00" + } + ], + "page": 1, + "pages": 1, + "results": 2 +} \ No newline at end of file diff --git a/test/unit/fixtures/netloadbalancer_nodes_list_filtered.json b/test/unit/fixtures/netloadbalancer_nodes_list_filtered.json new file mode 100644 index 000000000..2e7e630f4 --- /dev/null +++ b/test/unit/fixtures/netloadbalancer_nodes_list_filtered.json @@ -0,0 +1,17 @@ +{ + "data": [ + { + "id": 791, + "linode_id": 33333, + "label": "High Weight Server", + "address_v6": "2600:3c03::f03c:91ff:fe24:high", + "weight": 75, + "created": "2025-01-10T08:00:00", + "updated": "2025-01-12T10:30:00", + "weight_updated": "2025-01-12T10:30:00" + } + ], + "page": 1, + "pages": 1, + "results": 1 +} \ No newline at end of file diff --git a/test/unit/fixtures/netloadbalancer_update.json b/test/unit/fixtures/netloadbalancer_update.json new file mode 100644 index 000000000..65eb216ca --- /dev/null +++ b/test/unit/fixtures/netloadbalancer_update.json @@ -0,0 +1,12 @@ +{ + "id": 123, + "label": "Updated NetLoadBalancer", + "region": "us-east", + "address_v4": "192.0.2.1", + "address_v6": "2600:3c03::f03c:91ff:fe24:1234", + "status": "active", + "listeners": [], + "created": "2025-01-15T08:00:00", + "updated": "2025-02-10T16:00:00", + "last_composite_updated": "2025-02-10T16:00:00" +} \ No newline at end of file diff --git a/test/unit/fixtures/netloadbalancers_list.json b/test/unit/fixtures/netloadbalancers_list.json new file mode 100644 index 000000000..f70ff1ee0 --- /dev/null +++ b/test/unit/fixtures/netloadbalancers_list.json @@ -0,0 +1,31 @@ +{ + "data": [ + { + "id": 123, + "label": "NetLoadBalancer A", + "region": "us-east", + "address_v4": "192.0.2.1", + "address_v6": "2600:3c03::f03c:91ff:fe24:1234", + "status": "active", + "listeners": [], + "created": "2025-01-10T09:00:00", + "updated": "2025-01-12T15:00:00", + "last_composite_updated": "2025-01-12T15:00:00" + }, + { + "id": 456, + "label": "NetLoadBalancer B", + "region": "us-west", + "address_v4": "192.0.2.2", + "address_v6": "2600:3c03::f03c:91ff:fe24:5678", + "status": "provisioning", + "listeners": [], + "created": "2025-01-11T10:00:00", + "updated": "2025-01-11T10:00:00", + "last_composite_updated": "2025-01-11T10:00:00" + } + ], + "page": 1, + "pages": 1, + "results": 2 +} \ No newline at end of file diff --git a/test/unit/fixtures/netloadbalancers_list_filtered.json b/test/unit/fixtures/netloadbalancers_list_filtered.json new file mode 100644 index 000000000..c5e885210 --- /dev/null +++ b/test/unit/fixtures/netloadbalancers_list_filtered.json @@ -0,0 +1,19 @@ +{ + "data": [ + { + "id": 789, + "label": "Filtered NetLoadBalancer", + "region": "us-east", + "address_v4": "192.0.2.10", + "address_v6": "2600:3c03::f03c:91ff:fe24:filt", + "status": "active", + "listeners": [], + "created": "2025-01-10T09:00:00", + "updated": "2025-01-12T15:00:00", + "last_composite_updated": "2025-01-12T15:00:00" + } + ], + "page": 1, + "pages": 1, + "results": 1 +} \ No newline at end of file diff --git a/test/unit/netloadbalancer_listeners_test.go b/test/unit/netloadbalancer_listeners_test.go new file mode 100644 index 000000000..36b685cf0 --- /dev/null +++ b/test/unit/netloadbalancer_listeners_test.go @@ -0,0 +1,488 @@ +package unit + +import ( + "context" + "testing" + + "github.com/linode/linodego" + "github.com/stretchr/testify/assert" +) + +func TestNetLoadBalancerListener_Create(t *testing.T) { + tests := []struct { + name string + netloadbalancerID int + createOpts linodego.NetLoadBalancerListenerCreateOptions + fixture string + expectedProtocol string + expectedPort int + expectedLabel string + }{ + { + name: "basic listener creation", + netloadbalancerID: 123, + createOpts: linodego.NetLoadBalancerListenerCreateOptions{ + Protocol: "tcp", + Port: 80, + Label: "HTTP Listener", + }, + fixture: "netloadbalancer_listener_create", + expectedProtocol: "tcp", + expectedPort: 80, + expectedLabel: "HTTP Listener", + }, + { + name: "HTTPS listener creation", + netloadbalancerID: 123, + createOpts: linodego.NetLoadBalancerListenerCreateOptions{ + Protocol: "tcp", + Port: 443, + Label: "HTTPS Listener", + }, + fixture: "netloadbalancer_listener_create_https", + expectedProtocol: "tcp", + expectedPort: 443, + expectedLabel: "HTTPS Listener", + }, + { + name: "listener creation with nodes", + netloadbalancerID: 456, + createOpts: linodego.NetLoadBalancerListenerCreateOptions{ + Protocol: "tcp", + Port: 8080, + Label: "App Listener", + Nodes: []linodego.NetLoadBalancerNodeCreateOptions{ + { + Label: "App Server 1", + AddressV6: "2600:3c03::f03c:91ff:fe24:app1", + Weight: 100, + }, + { + Label: "App Server 2", + AddressV6: "2600:3c03::f03c:91ff:fe24:app2", + Weight: 100, + }, + }, + }, + fixture: "netloadbalancer_listener_create_with_nodes", + expectedProtocol: "tcp", + expectedPort: 8080, + expectedLabel: "App Listener", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fixtureData, err := fixtures.GetFixture(tt.fixture) + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + expectedPath := "netloadbalancers/123/listeners" + if tt.netloadbalancerID != 123 { + expectedPath = "netloadbalancers/456/listeners" + } + base.MockPost(expectedPath, fixtureData) + + listener, err := base.Client.CreateNetLoadBalancerListener( + context.Background(), + tt.netloadbalancerID, + tt.createOpts, + ) + assert.NoError(t, err) + + assert.Equal(t, tt.expectedProtocol, listener.Protocol) + assert.Equal(t, tt.expectedPort, listener.Port) + assert.Equal(t, tt.expectedLabel, listener.Label) + }) + } +} + +func TestNetLoadBalancerListener_Get(t *testing.T) { + fixtureData, err := fixtures.GetFixture("netloadbalancer_listener_get") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockGet("netloadbalancers/123/listeners/456", fixtureData) + + listener, err := base.Client.GetNetLoadBalancerListener(context.Background(), 123, 456) + assert.NoError(t, err) + + assert.Equal(t, 456, listener.ID) + assert.Equal(t, "tcp", listener.Protocol) + assert.Equal(t, 80, listener.Port) + assert.Equal(t, "Production HTTP Listener", listener.Label) +} + +func TestNetLoadBalancerListener_List(t *testing.T) { + fixtureData, err := fixtures.GetFixture("netloadbalancer_listeners_list") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockGet("netloadbalancers/123/listeners", fixtureData) + + listeners, err := base.Client.ListNetLoadBalancerListeners(context.Background(), 123, &linodego.ListOptions{}) + assert.NoError(t, err) + + assert.Len(t, listeners, 2) + + // Verify details of the first listener + assert.Equal(t, 456, listeners[0].ID) + assert.Equal(t, "tcp", listeners[0].Protocol) + assert.Equal(t, 80, listeners[0].Port) + assert.Equal(t, "HTTP Listener", listeners[0].Label) + + // Verify details of the second listener + assert.Equal(t, 457, listeners[1].ID) + assert.Equal(t, "tcp", listeners[1].Protocol) + assert.Equal(t, 443, listeners[1].Port) + assert.Equal(t, "HTTPS Listener", listeners[1].Label) +} + +func TestNetLoadBalancerListener_Update(t *testing.T) { + fixtureData, err := fixtures.GetFixture("netloadbalancer_listener_update") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockPut("netloadbalancers/123/listeners/456", fixtureData) + + updateOpts := linodego.NetLoadBalancerListenerUpdateOptions{ + Label: "Updated HTTP Listener", + } + + listener, err := base.Client.UpdateNetLoadBalancerListener(context.Background(), 123, 456, updateOpts) + assert.NoError(t, err) + + assert.Equal(t, 456, listener.ID) + assert.Equal(t, "Updated HTTP Listener", listener.Label) +} + +func TestNetLoadBalancerListener_Delete(t *testing.T) { + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockDelete("netloadbalancers/123/listeners/456", nil) + + err := base.Client.DeleteNetLoadBalancerListener(context.Background(), 123, 456) + assert.NoError(t, err) +} + +func TestNetLoadBalancerListener_GetCreateOptions(t *testing.T) { + tests := []struct { + name string + listener linodego.NetLoadBalancerListener + expected linodego.NetLoadBalancerListenerCreateOptions + }{ + { + name: "basic conversion", + listener: linodego.NetLoadBalancerListener{ + ID: 456, + Protocol: "tcp", + Port: 80, + Label: "Test Listener", + }, + expected: linodego.NetLoadBalancerListenerCreateOptions{ + Protocol: "tcp", + Port: 80, + Label: "Test Listener", + }, + }, + { + name: "HTTPS listener conversion", + listener: linodego.NetLoadBalancerListener{ + ID: 789, + Protocol: "tcp", + Port: 443, + Label: "SSL Listener", + }, + expected: linodego.NetLoadBalancerListenerCreateOptions{ + Protocol: "tcp", + Port: 443, + Label: "SSL Listener", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.listener.GetCreateOptions() + + assert.Equal(t, tt.expected.Protocol, result.Protocol) + assert.Equal(t, tt.expected.Port, result.Port) + assert.Equal(t, tt.expected.Label, result.Label) + // Note: GetCreateOptions doesn't include nodes as per the comment in the source + assert.Nil(t, result.Nodes) + }) + } +} + +func TestNetLoadBalancerListener_GetUpdateOptions(t *testing.T) { + tests := []struct { + name string + listener linodego.NetLoadBalancerListener + expected linodego.NetLoadBalancerListenerUpdateOptions + }{ + { + name: "basic conversion", + listener: linodego.NetLoadBalancerListener{ + ID: 456, + Protocol: "tcp", + Port: 80, + Label: "Updated Listener", + }, + expected: linodego.NetLoadBalancerListenerUpdateOptions{ + Label: "Updated Listener", + }, + }, + { + name: "conversion with different label", + listener: linodego.NetLoadBalancerListener{ + ID: 789, + Protocol: "tcp", + Port: 8080, + Label: "Custom App Listener", + }, + expected: linodego.NetLoadBalancerListenerUpdateOptions{ + Label: "Custom App Listener", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.listener.GetUpdateOptions() + + assert.Equal(t, tt.expected.Label, result.Label) + // Note: GetUpdateOptions only includes the label as per the source implementation + }) + } +} + +func TestNetLoadBalancerListener_UpdateNodeWeights(t *testing.T) { + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + // Mock the POST request for node weights update (no response body expected) + base.MockPost("netloadbalancers/123/listeners/456/node-weights", nil) + + updateOpts := linodego.NetLoadBalancerListenerNodeWeightsUpdateOptions{ + Nodes: []linodego.NetLoadBalancerListenerNodeWeightUpdateOptions{ + { + ID: 789, + Weight: 150, + }, + { + ID: 790, + Weight: 75, + }, + }, + } + + err := base.Client.UpdateNetLoadBalancerListenerNodeWeights(context.Background(), 123, 456, updateOpts) + assert.NoError(t, err) +} + +func TestNetLoadBalancerListener_ListWithOptions(t *testing.T) { + fixtureData, err := fixtures.GetFixture("netloadbalancer_listeners_list_filtered") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockGet("netloadbalancers/123/listeners", fixtureData) + + listOpts := &linodego.ListOptions{ + PageOptions: &linodego.PageOptions{ + Page: 1, + Pages: 1, + Results: 1, + }, + Filter: "{\"port\":80}", + } + + listeners, err := base.Client.ListNetLoadBalancerListeners(context.Background(), 123, listOpts) + assert.NoError(t, err) + + assert.Len(t, listeners, 1) + assert.Equal(t, 80, listeners[0].Port) +} + +func TestNetLoadBalancerListener_UnmarshalJSON(t *testing.T) { + tests := []struct { + name string + jsonData string + expected linodego.NetLoadBalancerListener + wantErr bool + }{ + { + name: "valid JSON with all fields", + jsonData: `{ + "id": 999, + "protocol": "tcp", + "port": 8080, + "label": "Test JSON Listener", + "created": "2025-03-01T10:00:00", + "updated": "2025-03-15T14:30:00" + }`, + expected: linodego.NetLoadBalancerListener{ + ID: 999, + Protocol: "tcp", + Port: 8080, + Label: "Test JSON Listener", + }, + wantErr: false, + }, + { + name: "valid JSON with minimal fields", + jsonData: `{ + "id": 111, + "protocol": "udp", + "port": 53, + "label": "" + }`, + expected: linodego.NetLoadBalancerListener{ + ID: 111, + Protocol: "udp", + Port: 53, + Label: "", + }, + wantErr: false, + }, + { + name: "invalid JSON", + jsonData: `{"id": "not-a-number"}`, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var listener linodego.NetLoadBalancerListener + err := listener.UnmarshalJSON([]byte(tt.jsonData)) + + if tt.wantErr { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.expected.ID, listener.ID) + assert.Equal(t, tt.expected.Protocol, listener.Protocol) + assert.Equal(t, tt.expected.Port, listener.Port) + assert.Equal(t, tt.expected.Label, listener.Label) + + // Test time fields if they were set in the JSON + if tt.expected.ID == 999 { + assert.NotNil(t, listener.Created) + assert.NotNil(t, listener.Updated) + } + }) + } +} + +func TestNetLoadBalancerListener_EdgeCases(t *testing.T) { + t.Run("create listener with UDP protocol", func(t *testing.T) { + fixtureData, err := fixtures.GetFixture("netloadbalancer_listener_create_udp") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockPost("netloadbalancers/123/listeners", fixtureData) + + createOpts := linodego.NetLoadBalancerListenerCreateOptions{ + Protocol: "udp", + Port: 53, + Label: "DNS Listener", + } + + listener, err := base.Client.CreateNetLoadBalancerListener(context.Background(), 123, createOpts) + assert.NoError(t, err) + assert.Equal(t, "udp", listener.Protocol) + assert.Equal(t, 53, listener.Port) + }) + + t.Run("create listener with high port number", func(t *testing.T) { + fixtureData, err := fixtures.GetFixture("netloadbalancer_listener_create_high_port") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockPost("netloadbalancers/123/listeners", fixtureData) + + createOpts := linodego.NetLoadBalancerListenerCreateOptions{ + Protocol: "tcp", + Port: 65535, + Label: "High Port Listener", + } + + listener, err := base.Client.CreateNetLoadBalancerListener(context.Background(), 123, createOpts) + assert.NoError(t, err) + assert.Equal(t, 65535, listener.Port) + }) + + t.Run("update multiple node weights", func(t *testing.T) { + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockPost("netloadbalancers/123/listeners/456/node-weights", nil) + + updateOpts := linodego.NetLoadBalancerListenerNodeWeightsUpdateOptions{ + Nodes: []linodego.NetLoadBalancerListenerNodeWeightUpdateOptions{ + {ID: 1, Weight: 100}, + {ID: 2, Weight: 200}, + {ID: 3, Weight: 50}, + {ID: 4, Weight: 0}, // Zero weight to disable + }, + } + + err := base.Client.UpdateNetLoadBalancerListenerNodeWeights(context.Background(), 123, 456, updateOpts) + assert.NoError(t, err) + }) + + t.Run("update with complex listener options", func(t *testing.T) { + fixtureData, err := fixtures.GetFixture("netloadbalancer_listener_update_complex") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockPut("netloadbalancers/123/listeners/456", fixtureData) + + updateOpts := linodego.NetLoadBalancerListenerUpdateOptions{ + Protocol: "tcp", + Port: 8080, + Label: "Complex Updated Listener", + Nodes: []linodego.NetLoadBalancerNodeUpdateOptions{ + { + Label: "Updated Node 1", + AddressV6: "2600:3c03::f03c:91ff:fe24:upd1", + Weight: 150, + }, + }, + } + + listener, err := base.Client.UpdateNetLoadBalancerListener(context.Background(), 123, 456, updateOpts) + assert.NoError(t, err) + assert.Equal(t, "Complex Updated Listener", listener.Label) + }) +} diff --git a/test/unit/netloadbalancer_nodes_test.go b/test/unit/netloadbalancer_nodes_test.go new file mode 100644 index 000000000..b3f0ca24b --- /dev/null +++ b/test/unit/netloadbalancer_nodes_test.go @@ -0,0 +1,409 @@ +package unit + +import ( + "context" + "testing" + + "github.com/linode/linodego" + "github.com/stretchr/testify/assert" +) + +func TestNetLoadBalancerNode_Create(t *testing.T) { + tests := []struct { + name string + netloadbalancerID int + listenerID int + createOpts linodego.NetLoadBalancerNodeCreateOptions + fixture string + expectedLabel string + expectedAddressV6 string + expectedWeight int + }{ + { + name: "basic node creation", + netloadbalancerID: 123, + listenerID: 456, + createOpts: linodego.NetLoadBalancerNodeCreateOptions{ + Label: "Web Server 1", + AddressV6: "2600:3c03::f03c:91ff:fe24:abcd", + Weight: 100, + }, + fixture: "netloadbalancer_node_create", + expectedLabel: "Web Server 1", + expectedAddressV6: "2600:3c03::f03c:91ff:fe24:abcd", + expectedWeight: 100, + }, + { + name: "node creation with default weight", + netloadbalancerID: 123, + listenerID: 456, + createOpts: linodego.NetLoadBalancerNodeCreateOptions{ + Label: "Database Server", + AddressV6: "2600:3c03::f03c:91ff:fe24:1234", + Weight: 0, // Default weight + }, + fixture: "netloadbalancer_node_create_default_weight", + expectedLabel: "Database Server", + expectedAddressV6: "2600:3c03::f03c:91ff:fe24:1234", + expectedWeight: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fixtureData, err := fixtures.GetFixture(tt.fixture) + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + expectedPath := "netloadbalancers/123/listeners/456/nodes" + base.MockPost(expectedPath, fixtureData) + + node, err := base.Client.CreateNetLoadBalancerNode( + context.Background(), + tt.netloadbalancerID, + tt.listenerID, + tt.createOpts, + ) + assert.NoError(t, err) + + assert.Equal(t, tt.expectedLabel, node.Label) + assert.Equal(t, tt.expectedAddressV6, node.AddressV6) + assert.Equal(t, tt.expectedWeight, node.Weight) + }) + } +} + +func TestNetLoadBalancerNode_Get(t *testing.T) { + fixtureData, err := fixtures.GetFixture("netloadbalancer_node_get") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockGet("netloadbalancers/123/listeners/456/nodes/789", fixtureData) + + node, err := base.Client.GetNetLoadBalancerNode(context.Background(), 123, 456, 789) + assert.NoError(t, err) + + assert.Equal(t, 789, node.ID) + assert.Equal(t, 12345, node.LinodeID) + assert.Equal(t, "Production Web Server", node.Label) + assert.Equal(t, "2600:3c03::f03c:91ff:fe24:prod", node.AddressV6) + assert.Equal(t, 100, node.Weight) +} + +func TestNetLoadBalancerNode_List(t *testing.T) { + fixtureData, err := fixtures.GetFixture("netloadbalancer_nodes_list") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockGet("netloadbalancers/123/listeners/456/nodes", fixtureData) + + nodes, err := base.Client.ListNetLoadBalancerNodes(context.Background(), 123, 456, &linodego.ListOptions{}) + assert.NoError(t, err) + + assert.Len(t, nodes, 2) + + // Verify details of the first node + assert.Equal(t, 789, nodes[0].ID) + assert.Equal(t, 11111, nodes[0].LinodeID) + assert.Equal(t, "Web Server 1", nodes[0].Label) + assert.Equal(t, "2600:3c03::f03c:91ff:fe24:0001", nodes[0].AddressV6) + assert.Equal(t, 100, nodes[0].Weight) + + // Verify details of the second node + assert.Equal(t, 790, nodes[1].ID) + assert.Equal(t, 22222, nodes[1].LinodeID) + assert.Equal(t, "Web Server 2", nodes[1].Label) + assert.Equal(t, "2600:3c03::f03c:91ff:fe24:0002", nodes[1].AddressV6) + assert.Equal(t, 50, nodes[1].Weight) +} + +func TestNetLoadBalancerNode_Update(t *testing.T) { + fixtureData, err := fixtures.GetFixture("netloadbalancer_node_update") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockPut("netloadbalancers/123/listeners/456/nodes/789", fixtureData) + + updateOpts := linodego.NetLoadBalancerNodeLabelUpdateOptions{ + Label: "Updated Web Server", + } + + node, err := base.Client.UpdateNetLoadBalancerNode(context.Background(), 123, 456, 789, updateOpts) + assert.NoError(t, err) + + assert.Equal(t, 789, node.ID) + assert.Equal(t, "Updated Web Server", node.Label) +} + +func TestNetLoadBalancerNode_Delete(t *testing.T) { + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockDelete("netloadbalancers/123/listeners/456/nodes/789", nil) + + err := base.Client.DeleteNetLoadBalancerNode(context.Background(), 123, 456, 789) + assert.NoError(t, err) +} + +func TestNetLoadBalancerNode_GetCreateOptions(t *testing.T) { + tests := []struct { + name string + node linodego.NetLoadBalancerNode + expected linodego.NetLoadBalancerNodeCreateOptions + }{ + { + name: "basic conversion", + node: linodego.NetLoadBalancerNode{ + ID: 789, + LinodeID: 12345, + Label: "Test Node", + AddressV6: "2600:3c03::f03c:91ff:fe24:test", + Weight: 75, + }, + expected: linodego.NetLoadBalancerNodeCreateOptions{ + Label: "Test Node", + AddressV6: "2600:3c03::f03c:91ff:fe24:test", + Weight: 75, + }, + }, + { + name: "conversion with zero weight", + node: linodego.NetLoadBalancerNode{ + ID: 456, + LinodeID: 67890, + Label: "Zero Weight Node", + AddressV6: "2600:3c03::f03c:91ff:fe24:zero", + Weight: 0, + }, + expected: linodego.NetLoadBalancerNodeCreateOptions{ + Label: "Zero Weight Node", + AddressV6: "2600:3c03::f03c:91ff:fe24:zero", + Weight: 0, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.node.GetCreateOptions() + + assert.Equal(t, tt.expected.Label, result.Label) + assert.Equal(t, tt.expected.AddressV6, result.AddressV6) + assert.Equal(t, tt.expected.Weight, result.Weight) + }) + } +} + +func TestNetLoadBalancerNode_GetUpdateOptions(t *testing.T) { + tests := []struct { + name string + node linodego.NetLoadBalancerNode + expected linodego.NetLoadBalancerNodeUpdateOptions + }{ + { + name: "basic conversion", + node: linodego.NetLoadBalancerNode{ + ID: 789, + LinodeID: 12345, + Label: "Updated Node", + AddressV6: "2600:3c03::f03c:91ff:fe24:updt", + Weight: 150, + }, + expected: linodego.NetLoadBalancerNodeUpdateOptions{ + Label: "Updated Node", + AddressV6: "2600:3c03::f03c:91ff:fe24:updt", + Weight: 150, + }, + }, + { + name: "conversion with changed weight", + node: linodego.NetLoadBalancerNode{ + ID: 321, + LinodeID: 54321, + Label: "Reweighted Node", + AddressV6: "2600:3c03::f03c:91ff:fe24:rwgt", + Weight: 25, + }, + expected: linodego.NetLoadBalancerNodeUpdateOptions{ + Label: "Reweighted Node", + AddressV6: "2600:3c03::f03c:91ff:fe24:rwgt", + Weight: 25, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.node.GetUpdateOptions() + + assert.Equal(t, tt.expected.Label, result.Label) + assert.Equal(t, tt.expected.AddressV6, result.AddressV6) + assert.Equal(t, tt.expected.Weight, result.Weight) + }) + } +} + +func TestNetLoadBalancerNode_ListWithOptions(t *testing.T) { + fixtureData, err := fixtures.GetFixture("netloadbalancer_nodes_list_filtered") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockGet("netloadbalancers/123/listeners/456/nodes", fixtureData) + + listOpts := &linodego.ListOptions{ + PageOptions: &linodego.PageOptions{ + Page: 1, + Pages: 1, + Results: 1, + }, + Filter: "{\"weight\":{\"gt\":50}}", + } + + nodes, err := base.Client.ListNetLoadBalancerNodes(context.Background(), 123, 456, listOpts) + assert.NoError(t, err) + + assert.Len(t, nodes, 1) + assert.Greater(t, nodes[0].Weight, 50) +} + +func TestNetLoadBalancerNode_UnmarshalJSON(t *testing.T) { + tests := []struct { + name string + jsonData string + expected linodego.NetLoadBalancerNode + wantErr bool + }{ + { + name: "valid JSON with all fields", + jsonData: `{ + "id": 999, + "linode_id": 88888, + "label": "Test JSON Node", + "address_v6": "2600:3c03::f03c:91ff:fe24:json", + "weight": 125, + "created": "2025-03-01T10:00:00", + "updated": "2025-03-15T14:30:00", + "weight_updated": "2025-03-20T09:15:00" + }`, + expected: linodego.NetLoadBalancerNode{ + ID: 999, + LinodeID: 88888, + Label: "Test JSON Node", + AddressV6: "2600:3c03::f03c:91ff:fe24:json", + Weight: 125, + }, + wantErr: false, + }, + { + name: "valid JSON with minimal fields", + jsonData: `{ + "id": 111, + "linode_id": 0, + "label": "", + "address_v6": "", + "weight": 0 + }`, + expected: linodego.NetLoadBalancerNode{ + ID: 111, + LinodeID: 0, + Label: "", + AddressV6: "", + Weight: 0, + }, + wantErr: false, + }, + { + name: "invalid JSON", + jsonData: `{"id": "not-a-number"}`, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var node linodego.NetLoadBalancerNode + err := node.UnmarshalJSON([]byte(tt.jsonData)) + + if tt.wantErr { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.expected.ID, node.ID) + assert.Equal(t, tt.expected.LinodeID, node.LinodeID) + assert.Equal(t, tt.expected.Label, node.Label) + assert.Equal(t, tt.expected.AddressV6, node.AddressV6) + assert.Equal(t, tt.expected.Weight, node.Weight) + + // Test time fields if they were set in the JSON + if tt.expected.ID == 999 { + assert.NotNil(t, node.Created) + assert.NotNil(t, node.Updated) + assert.NotNil(t, node.WeightUpdated) + } + }) + } +} + +func TestNetLoadBalancerNode_EdgeCases(t *testing.T) { + t.Run("create node with high weight", func(t *testing.T) { + fixtureData, err := fixtures.GetFixture("netloadbalancer_node_create_high_weight") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockPost("netloadbalancers/123/listeners/456/nodes", fixtureData) + + createOpts := linodego.NetLoadBalancerNodeCreateOptions{ + Label: "High Weight Node", + AddressV6: "2600:3c03::f03c:91ff:fe24:high", + Weight: 999, + } + + node, err := base.Client.CreateNetLoadBalancerNode(context.Background(), 123, 456, createOpts) + assert.NoError(t, err) + assert.Equal(t, 999, node.Weight) + }) + + t.Run("update node with empty label should fail validation", func(t *testing.T) { + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + // This should still work at the client level, validation happens server-side + base.MockPut("netloadbalancers/123/listeners/456/nodes/789", map[string]interface{}{ + "id": 789, + "label": "", + "linode_id": 12345, + "address_v6": "2600:3c03::f03c:91ff:fe24:empty", + "weight": 100, + }) + + updateOpts := linodego.NetLoadBalancerNodeLabelUpdateOptions{ + Label: "", // Empty label + } + + node, err := base.Client.UpdateNetLoadBalancerNode(context.Background(), 123, 456, 789, updateOpts) + assert.NoError(t, err) // Client doesn't validate, server would + assert.Equal(t, "", node.Label) + }) +} diff --git a/test/unit/netloadbalancer_test.go b/test/unit/netloadbalancer_test.go new file mode 100644 index 000000000..0d705b861 --- /dev/null +++ b/test/unit/netloadbalancer_test.go @@ -0,0 +1,444 @@ +package unit + +import ( + "context" + "testing" + + "github.com/linode/linodego" + "github.com/stretchr/testify/assert" +) + +func TestNetLoadBalancer_Create(t *testing.T) { + tests := []struct { + name string + createOpts linodego.NetLoadBalancerCreateOptions + fixture string + }{ + { + name: "basic creation", + createOpts: linodego.NetLoadBalancerCreateOptions{ + Label: "Test NetLoadBalancer", + Region: "us-east", + }, + fixture: "netloadbalancer_create", + }, + { + name: "creation with listeners", + createOpts: linodego.NetLoadBalancerCreateOptions{ + Label: "NetLoadBalancer with Listeners", + Region: "us-west", + Listeners: []linodego.NetLoadBalancerListenerCreateOptions{ + { + Protocol: "tcp", + Port: 80, + Label: "HTTP Listener", + Nodes: []linodego.NetLoadBalancerNodeCreateOptions{ + { + Label: "Web Server 1", + AddressV6: "2600:3c03::f03c:91ff:fe24:abcd", + Weight: 100, + }, + }, + }, + }, + }, + fixture: "netloadbalancer_create_with_listeners", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fixtureData, err := fixtures.GetFixture(tt.fixture) + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockPost("netloadbalancers", fixtureData) + + netloadbalancer, err := base.Client.CreateNetLoadBalancer(context.Background(), tt.createOpts) + assert.NoError(t, err) + + assert.Equal(t, tt.createOpts.Label, netloadbalancer.Label) + assert.Equal(t, tt.createOpts.Region, netloadbalancer.Region) + }) + } +} + +func TestNetLoadBalancer_Get(t *testing.T) { + fixtureData, err := fixtures.GetFixture("netloadbalancer_get") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockGet("netloadbalancers/123", fixtureData) + + netloadbalancer, err := base.Client.GetNetLoadBalancer(context.Background(), 123) + assert.NoError(t, err) + + assert.Equal(t, 123, netloadbalancer.ID) + assert.Equal(t, "Test NetLoadBalancer", netloadbalancer.Label) + assert.Equal(t, "us-east", netloadbalancer.Region) + assert.Equal(t, "active", netloadbalancer.Status) + assert.Equal(t, "192.0.2.1", netloadbalancer.AddressV4) + assert.Equal(t, "2600:3c03::f03c:91ff:fe24:1234", netloadbalancer.AddressV6) +} + +func TestNetLoadBalancer_List(t *testing.T) { + fixtureData, err := fixtures.GetFixture("netloadbalancers_list") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockGet("netloadbalancers", fixtureData) + + netloadbalancers, err := base.Client.ListNetLoadBalancers(context.Background(), &linodego.ListOptions{}) + assert.NoError(t, err) + + assert.Len(t, netloadbalancers, 2) + + // Verify details of the first NetLoadBalancer + assert.Equal(t, 123, netloadbalancers[0].ID) + assert.Equal(t, "NetLoadBalancer A", netloadbalancers[0].Label) + assert.Equal(t, "us-east", netloadbalancers[0].Region) + assert.Equal(t, "active", netloadbalancers[0].Status) + + // Verify details of the second NetLoadBalancer + assert.Equal(t, 456, netloadbalancers[1].ID) + assert.Equal(t, "NetLoadBalancer B", netloadbalancers[1].Label) + assert.Equal(t, "us-west", netloadbalancers[1].Region) + assert.Equal(t, "provisioning", netloadbalancers[1].Status) +} + +func TestNetLoadBalancer_Update(t *testing.T) { + fixtureData, err := fixtures.GetFixture("netloadbalancer_update") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockPut("netloadbalancers/123", fixtureData) + + updateOpts := linodego.NetLoadBalancerUpdateOptions{ + Label: "Updated NetLoadBalancer", + } + netloadbalancer, err := base.Client.UpdateNetLoadBalancer(context.Background(), 123, updateOpts) + assert.NoError(t, err) + + assert.Equal(t, 123, netloadbalancer.ID) + assert.Equal(t, "Updated NetLoadBalancer", netloadbalancer.Label) +} + +func TestNetLoadBalancer_Delete(t *testing.T) { + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockDelete("netloadbalancers/123", nil) + + err := base.Client.DeleteNetLoadBalancer(context.Background(), 123) + assert.NoError(t, err) +} + +func TestNetLoadBalancer_GetCreateOptions(t *testing.T) { + tests := []struct { + name string + netloadbalancer linodego.NetLoadBalancer + expected linodego.NetLoadBalancerCreateOptions + }{ + { + name: "basic conversion", + netloadbalancer: linodego.NetLoadBalancer{ + ID: 123, + Label: "Test NetLoadBalancer", + Region: "us-east", + Listeners: []linodego.NetLoadBalancerListener{ + { + ID: 1, + Protocol: "tcp", + Port: 80, + Label: "HTTP Listener", + }, + }, + }, + expected: linodego.NetLoadBalancerCreateOptions{ + Label: "Test NetLoadBalancer", + Region: "us-east", + Listeners: []linodego.NetLoadBalancerListenerCreateOptions{ + { + Protocol: "tcp", + Port: 80, + Label: "HTTP Listener", + }, + }, + }, + }, + { + name: "conversion without listeners", + netloadbalancer: linodego.NetLoadBalancer{ + ID: 456, + Label: "Simple NetLoadBalancer", + Region: "us-west", + Listeners: []linodego.NetLoadBalancerListener{}, + }, + expected: linodego.NetLoadBalancerCreateOptions{ + Label: "Simple NetLoadBalancer", + Region: "us-west", + Listeners: []linodego.NetLoadBalancerListenerCreateOptions{}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.netloadbalancer.GetCreateOptions() + + assert.Equal(t, tt.expected.Label, result.Label) + assert.Equal(t, tt.expected.Region, result.Region) + assert.Len(t, result.Listeners, len(tt.expected.Listeners)) + + for i, expectedListener := range tt.expected.Listeners { + assert.Equal(t, expectedListener.Protocol, result.Listeners[i].Protocol) + assert.Equal(t, expectedListener.Port, result.Listeners[i].Port) + assert.Equal(t, expectedListener.Label, result.Listeners[i].Label) + } + }) + } +} + +func TestNetLoadBalancer_GetUpdateOptions(t *testing.T) { + tests := []struct { + name string + netloadbalancer linodego.NetLoadBalancer + expected linodego.NetLoadBalancerUpdateOptions + }{ + { + name: "basic conversion", + netloadbalancer: linodego.NetLoadBalancer{ + ID: 123, + Label: "Updated NetLoadBalancer", + Region: "us-east", + Listeners: []linodego.NetLoadBalancerListener{ + { + ID: 1, + Protocol: "tcp", + Port: 8080, + Label: "Updated Listener", + }, + }, + }, + expected: linodego.NetLoadBalancerUpdateOptions{ + Label: "Updated NetLoadBalancer", + Listeners: []linodego.NetLoadBalancerListenerUpdateOptions{ + { + Protocol: "tcp", + Port: 8080, + Label: "Updated Listener", + }, + }, + }, + }, + { + name: "conversion without listeners", + netloadbalancer: linodego.NetLoadBalancer{ + ID: 456, + Label: "Simple Updated NetLoadBalancer", + Region: "us-west", + Listeners: []linodego.NetLoadBalancerListener{}, + }, + expected: linodego.NetLoadBalancerUpdateOptions{ + Label: "Simple Updated NetLoadBalancer", + Listeners: []linodego.NetLoadBalancerListenerUpdateOptions{}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.netloadbalancer.GetUpdateOptions() + + assert.Equal(t, tt.expected.Label, result.Label) + assert.Len(t, result.Listeners, len(tt.expected.Listeners)) + + for i, expectedListener := range tt.expected.Listeners { + assert.Equal(t, expectedListener.Label, result.Listeners[i].Label) + } + }) + } +} + +func TestNetLoadBalancer_ListWithOptions(t *testing.T) { + fixtureData, err := fixtures.GetFixture("netloadbalancers_list_filtered") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockGet("netloadbalancers", fixtureData) + + listOpts := &linodego.ListOptions{ + PageOptions: &linodego.PageOptions{ + Page: 1, + Pages: 1, + Results: 1, + }, + Filter: "{\"region\":\"us-east\"}", + } + + netloadbalancers, err := base.Client.ListNetLoadBalancers(context.Background(), listOpts) + assert.NoError(t, err) + + assert.Len(t, netloadbalancers, 1) + assert.Equal(t, "us-east", netloadbalancers[0].Region) +} + +func TestNetLoadBalancer_UnmarshalJSON(t *testing.T) { + tests := []struct { + name string + jsonData string + expected linodego.NetLoadBalancer + wantErr bool + }{ + { + name: "valid JSON with all fields", + jsonData: `{ + "id": 789, + "label": "Test NetLoadBalancer", + "region": "us-southeast", + "address_v4": "192.0.2.10", + "address_v6": "2600:3c03::f03c:91ff:fe24:5678", + "status": "active", + "listeners": [], + "created": "2025-03-01T10:00:00", + "updated": "2025-03-15T14:30:00", + "last_composite_updated": "2025-03-15T15:00:00" + }`, + expected: linodego.NetLoadBalancer{ + ID: 789, + Label: "Test NetLoadBalancer", + Region: "us-southeast", + AddressV4: "192.0.2.10", + AddressV6: "2600:3c03::f03c:91ff:fe24:5678", + Status: "active", + Listeners: []linodego.NetLoadBalancerListener{}, + }, + wantErr: false, + }, + { + name: "valid JSON with minimal fields", + jsonData: `{ + "id": 456, + "label": "Minimal NetLoadBalancer", + "region": "us-west", + "address_v4": "", + "address_v6": "", + "status": "provisioning", + "listeners": [] + }`, + expected: linodego.NetLoadBalancer{ + ID: 456, + Label: "Minimal NetLoadBalancer", + Region: "us-west", + AddressV4: "", + AddressV6: "", + Status: "provisioning", + Listeners: []linodego.NetLoadBalancerListener{}, + }, + wantErr: false, + }, + { + name: "invalid JSON", + jsonData: `{"id": "not-a-number"}`, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var nlb linodego.NetLoadBalancer + err := nlb.UnmarshalJSON([]byte(tt.jsonData)) + + if tt.wantErr { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.expected.ID, nlb.ID) + assert.Equal(t, tt.expected.Label, nlb.Label) + assert.Equal(t, tt.expected.Region, nlb.Region) + assert.Equal(t, tt.expected.AddressV4, nlb.AddressV4) + assert.Equal(t, tt.expected.AddressV6, nlb.AddressV6) + assert.Equal(t, tt.expected.Status, nlb.Status) + assert.Equal(t, tt.expected.Listeners, nlb.Listeners) + + // Test time fields if they were set in the JSON + if tt.expected.ID == 789 { + assert.NotNil(t, nlb.Created) + assert.NotNil(t, nlb.Updated) + assert.NotNil(t, nlb.LastCompositeUpdated) + } + }) + } +} + +func TestNetLoadBalancer_CreateWithComplexListeners(t *testing.T) { + fixtureData, err := fixtures.GetFixture("netloadbalancer_create_complex") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockPost("netloadbalancers", fixtureData) + + createOpts := linodego.NetLoadBalancerCreateOptions{ + Label: "Complex NetLoadBalancer", + Region: "us-central", + Listeners: []linodego.NetLoadBalancerListenerCreateOptions{ + { + Protocol: "tcp", + Port: 80, + Label: "HTTP Listener", + Nodes: []linodego.NetLoadBalancerNodeCreateOptions{ + { + Label: "Web Server 1", + AddressV6: "2600:3c03::f03c:91ff:fe24:0001", + Weight: 100, + }, + { + Label: "Web Server 2", + AddressV6: "2600:3c03::f03c:91ff:fe24:0002", + Weight: 100, + }, + }, + }, + { + Protocol: "tcp", + Port: 443, + Label: "HTTPS Listener", + Nodes: []linodego.NetLoadBalancerNodeCreateOptions{ + { + Label: "SSL Server 1", + AddressV6: "2600:3c03::f03c:91ff:fe24:0003", + Weight: 50, + }, + }, + }, + }, + } + + netloadbalancer, err := base.Client.CreateNetLoadBalancer(context.Background(), createOpts) + assert.NoError(t, err) + + assert.Equal(t, "Complex NetLoadBalancer", netloadbalancer.Label) + assert.Equal(t, "us-central", netloadbalancer.Region) + assert.Len(t, netloadbalancer.Listeners, 2) +}