Skip to content

Commit

Permalink
test: refactor device resource tests to mock API instead of SDK (#371)
Browse files Browse the repository at this point in the history
We have a few tests for the device resource that cover scenarios that
are impossible to reliably recreate in an end-to-end test. These tests
were originally based on a mock packngo client, but mocking the SDK
hides issues that may come in due to SDK upgrades and requires changing
tests when we switch to a different SDK.

This updates the device mock tests to use a mock API created with the
built-in `httptest` module so that we can more easily validate that the
tests continue to pass after upgrading or changing the SDK.
  • Loading branch information
ctreatma authored Sep 7, 2023
1 parent 75b9526 commit 5cf6d50
Showing 1 changed file with 38 additions and 127 deletions.
165 changes: 38 additions & 127 deletions equinix/resource_metal_device_acc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import (
"log"
"net"
"net/http"
"net/http/httptest"
"regexp"
"strings"
"testing"
"time"

"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
Expand Down Expand Up @@ -986,84 +988,10 @@ resource "equinix_metal_device" "test" {
`, confAccMetalDevice_base(preferable_plans, preferable_metros, preferable_os), projSuffix, testDeviceTerminationTime(), updateTimeout)
}

type mockDeviceService struct {
GetFn func(deviceID string, opts *packngo.GetOptions) (*packngo.Device, *packngo.Response, error)
}

func (m *mockDeviceService) Get(deviceID string, opts *packngo.GetOptions) (*packngo.Device, *packngo.Response, error) {
return m.GetFn(deviceID, opts)
}

func (m *mockDeviceService) Create(device *packngo.DeviceCreateRequest) (*packngo.Device, *packngo.Response, error) {
return nil, nil, mockFuncNotImplemented("Create")
}

func (m *mockDeviceService) Delete(deviceId string, forceDetachVolume bool) (*packngo.Response, error) {
return nil, mockFuncNotImplemented("Delete")
}

func (m *mockDeviceService) List(string, *packngo.ListOptions) ([]packngo.Device, *packngo.Response, error) {
return nil, nil, mockFuncNotImplemented("List")
}

func (m *mockDeviceService) Update(deviceId string, updateReq *packngo.DeviceUpdateRequest) (*packngo.Device, *packngo.Response, error) {
return nil, nil, mockFuncNotImplemented("Update")
}

func (m *mockDeviceService) Reboot(string) (*packngo.Response, error) {
return nil, mockFuncNotImplemented("Reboot")
}

func (m *mockDeviceService) Rescue(string) (*packngo.Response, error) {
return nil, mockFuncNotImplemented("Rescue")
}

func (m *mockDeviceService) Reinstall(string, *packngo.DeviceReinstallFields) (*packngo.Response, error) {
return nil, mockFuncNotImplemented("Reinstall")
}

func (m *mockDeviceService) PowerOff(string) (*packngo.Response, error) {
return nil, mockFuncNotImplemented("PowerOff")
}

func (m *mockDeviceService) PowerOn(string) (*packngo.Response, error) {
return nil, mockFuncNotImplemented("PowerOn")
}

func (m *mockDeviceService) Lock(string) (*packngo.Response, error) {
return nil, mockFuncNotImplemented("Lock")
}

func (m *mockDeviceService) Unlock(string) (*packngo.Response, error) {
return nil, mockFuncNotImplemented("Unlock")
}

func (m *mockDeviceService) ListBGPSessions(string, *packngo.ListOptions) ([]packngo.BGPSession, *packngo.Response, error) {
return nil, nil, mockFuncNotImplemented("ListBGPSessions")
}

func (m *mockDeviceService) ListBGPNeighbors(string, *packngo.ListOptions) ([]packngo.BGPNeighbor, *packngo.Response, error) {
return nil, nil, mockFuncNotImplemented("ListBGPNeighbors")
}

func (m *mockDeviceService) ListEvents(string, *packngo.ListOptions) ([]packngo.Event, *packngo.Response, error) {
return nil, nil, mockFuncNotImplemented("ListEvents")
}

func (m *mockDeviceService) GetBandwidth(string, *packngo.BandwidthOpts) (*packngo.BandwidthIO, *packngo.Response, error) {
return nil, nil, mockFuncNotImplemented("GetBandwidth")
}

func mockFuncNotImplemented(f string) error {
return fmt.Errorf("mockDeviceService %s function not yet implemented", f)
}

var _ packngo.DeviceService = (*mockDeviceService)(nil)

func TestAccMetalDevice_readErrorHandling(t *testing.T) {
type args struct {
newResource bool
meta *Config
handler func(w http.ResponseWriter, r *http.Request)
}

tests := []struct {
Expand All @@ -1075,15 +1003,10 @@ func TestAccMetalDevice_readErrorHandling(t *testing.T) {
name: "forbiddenAfterProvision",
args: args{
newResource: false,
meta: &Config{
metal: &packngo.Client{
Devices: &mockDeviceService{
GetFn: func(deviceID string, opts *packngo.GetOptions) (*packngo.Device, *packngo.Response, error) {
httpResp := &http.Response{Status: "403 Forbidden", StatusCode: 403}
return nil, &packngo.Response{Response: httpResp}, &packngo.ErrorResponse{Response: httpResp}
},
},
},
handler: func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "application/json")
w.Header().Add("X-Request-Id", "needed for friendlyError")
w.WriteHeader(http.StatusForbidden)
},
},
wantErr: false,
Expand All @@ -1092,19 +1015,10 @@ func TestAccMetalDevice_readErrorHandling(t *testing.T) {
name: "notFoundAfterProvision",
args: args{
newResource: false,
meta: &Config{
metal: &packngo.Client{
Devices: &mockDeviceService{
GetFn: func(deviceID string, opts *packngo.GetOptions) (*packngo.Device, *packngo.Response, error) {
httpResp := &http.Response{
Status: "404 NotFound",
StatusCode: 404,
Header: http.Header{"Content-Type": []string{"application/json"}, "X-Request-Id": []string{"12345"}},
}
return nil, &packngo.Response{Response: httpResp}, &packngo.ErrorResponse{Response: httpResp}
},
},
},
handler: func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "application/json")
w.Header().Add("X-Request-Id", "needed for friendlyError")
w.WriteHeader(http.StatusNotFound)
},
},
wantErr: false,
Expand All @@ -1113,15 +1027,10 @@ func TestAccMetalDevice_readErrorHandling(t *testing.T) {
name: "forbiddenWaitForActiveDeviceProvision",
args: args{
newResource: true,
meta: &Config{
metal: &packngo.Client{
Devices: &mockDeviceService{
GetFn: func(deviceID string, opts *packngo.GetOptions) (*packngo.Device, *packngo.Response, error) {
httpResp := &http.Response{Status: "403 Forbidden", StatusCode: 403}
return nil, &packngo.Response{Response: httpResp}, &packngo.ErrorResponse{Response: httpResp}
},
},
},
handler: func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "application/json")
w.Header().Add("X-Request-Id", "needed for friendlyError")
w.WriteHeader(http.StatusForbidden)
},
},
wantErr: true,
Expand All @@ -1130,15 +1039,10 @@ func TestAccMetalDevice_readErrorHandling(t *testing.T) {
name: "notFoundProvision",
args: args{
newResource: true,
meta: &Config{
metal: &packngo.Client{
Devices: &mockDeviceService{
GetFn: func(deviceID string, opts *packngo.GetOptions) (*packngo.Device, *packngo.Response, error) {
httpResp := &http.Response{Status: "404 NotFound", StatusCode: 404}
return nil, &packngo.Response{Response: httpResp}, &packngo.ErrorResponse{Response: httpResp}
},
},
},
handler: func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "application/json")
w.Header().Add("X-Request-Id", "needed for friendlyError")
w.WriteHeader(http.StatusNotFound)
},
},
wantErr: true,
Expand All @@ -1147,15 +1051,10 @@ func TestAccMetalDevice_readErrorHandling(t *testing.T) {
name: "errorProvision",
args: args{
newResource: true,
meta: &Config{
metal: &packngo.Client{
Devices: &mockDeviceService{
GetFn: func(deviceID string, opts *packngo.GetOptions) (*packngo.Device, *packngo.Response, error) {
httpResp := &http.Response{Status: "400 BadRequest", StatusCode: 400}
return nil, &packngo.Response{Response: httpResp}, &packngo.ErrorResponse{Response: httpResp}
},
},
},
handler: func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "application/json")
w.Header().Add("X-Request-Id", "needed for friendlyError")
w.WriteHeader(http.StatusBadRequest)
},
},
wantErr: true,
Expand All @@ -1164,14 +1063,26 @@ func TestAccMetalDevice_readErrorHandling(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var d *schema.ResourceData
d = new(schema.ResourceData)
ctx := context.Background()
d := new(schema.ResourceData)
if tt.args.newResource {
d.MarkNewResource()
} else {
d.SetId(uuid.New().String())
}

mockAPI := httptest.NewServer(http.HandlerFunc(tt.args.handler))
meta := &Config{
BaseURL: mockAPI.URL,
Token: "fakeTokenForMock",
}
if err := resourceMetalDeviceRead(context.Background(), d, tt.args.meta); (err != nil) != tt.wantErr {
meta.Load(ctx)

if err := resourceMetalDeviceRead(ctx, d, meta); (err != nil) != tt.wantErr {
t.Errorf("resourceMetalDeviceRead() error = %v, wantErr %v", err, tt.wantErr)
}

mockAPI.Close()
})
}
}
Expand Down

0 comments on commit 5cf6d50

Please sign in to comment.