Skip to content

Commit 7e903d9

Browse files
committed
feat(azure): add WithClient variants and azfake unit tests for all modules
Add exported WithClient variants for Azure module functions that accept a pre-built SDK client, enabling unit testing with fake servers. Each existing ContextE function now delegates its SDK call to the corresponding WithClient variant. Pattern established across all modules: - GetDiskContextE(ctx, name, rg, sub) -- convenience, creates its own client - GetDiskWithClient(ctx, client, rg, name) -- testable, accepts injected client Functions that extract fields from response objects (not clients) drop the WithClient suffix since they don't take a client: - GetAvailabilitySetFaultDomainCount(avs) - GetLoadBalancerFrontendIPConfigNames(lb) - GetNetworkInterfacePrivateIPs(nic) - GetIPOfPublicIPAddressByName(pip) - GetVirtualNetworkDNSServerIPs(vnet) Nil-deref guards added on all newly-exported helpers that dereference response properties (avs.Properties, lb.Properties, nic.Properties, feConfig.Properties, vnet.Properties, pip.Name). Modules updated: disk, availabilityset, loadbalancer, networkinterface, publicaddress, virtualnetwork, cosmosdb, servicebus All WithClient functions are tested using Azure SDK's azfake framework (httptest for servicebus beta SDK), running in CI without credentials.
1 parent 9565e65 commit 7e903d9

16 files changed

Lines changed: 1578 additions & 96 deletions

modules/azure/availabilityset.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package azure
22

33
import (
44
"context"
5+
"errors"
56
"strings"
67

78
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v6"
@@ -56,12 +57,21 @@ func CheckAvailabilitySetContainsVMContextE(t testing.TestingT, ctx context.Cont
5657
return false, err
5758
}
5859

60+
return CheckAvailabilitySetContainsVMWithClient(ctx, client, resGroupName, avsName, vmName)
61+
}
62+
63+
// CheckAvailabilitySetContainsVMWithClient checks if the Virtual Machine is contained in the Availability Set VMs
64+
// using the provided AvailabilitySetsClient.
65+
func CheckAvailabilitySetContainsVMWithClient(ctx context.Context, client *armcompute.AvailabilitySetsClient, resGroupName string, avsName string, vmName string) (bool, error) {
5966
resp, err := client.Get(ctx, resGroupName, avsName, nil)
6067
if err != nil {
6168
return false, err
6269
}
6370

6471
for _, vm := range resp.Properties.VirtualMachines {
72+
if vm.ID == nil {
73+
continue
74+
}
6575
// VM IDs are always ALL CAPS in this property so ignoring case
6676
if strings.EqualFold(vmName, GetNameFromResourceID(*vm.ID)) {
6777
return true, nil
@@ -91,6 +101,12 @@ func GetAvailabilitySetVMNamesInCapsContextE(t testing.TestingT, ctx context.Con
91101
return nil, err
92102
}
93103

104+
return GetAvailabilitySetVMNamesInCapsWithClient(ctx, client, resGroupName, avsName)
105+
}
106+
107+
// GetAvailabilitySetVMNamesInCapsWithClient gets a list of VM names in the specified Azure Availability Set
108+
// using the provided AvailabilitySetsClient.
109+
func GetAvailabilitySetVMNamesInCapsWithClient(ctx context.Context, client *armcompute.AvailabilitySetsClient, resGroupName string, avsName string) ([]string, error) {
94110
resp, err := client.Get(ctx, resGroupName, avsName, nil)
95111
if err != nil {
96112
return nil, err
@@ -99,6 +115,9 @@ func GetAvailabilitySetVMNamesInCapsContextE(t testing.TestingT, ctx context.Con
99115
vms := []string{}
100116

101117
for _, vm := range resp.Properties.VirtualMachines {
118+
if vm.ID == nil {
119+
continue
120+
}
102121
// IDs are returned in ALL CAPS for this property
103122
if vmName := GetNameFromResourceID(*vm.ID); len(vmName) > 0 {
104123
vms = append(vms, vmName)
@@ -128,6 +147,15 @@ func GetAvailabilitySetFaultDomainCountContextE(t testing.TestingT, ctx context.
128147
return -1, err
129148
}
130149

150+
return GetAvailabilitySetFaultDomainCount(avs)
151+
}
152+
153+
// GetAvailabilitySetFaultDomainCount gets the Fault Domain Count from the provided AvailabilitySet.
154+
func GetAvailabilitySetFaultDomainCount(avs *armcompute.AvailabilitySet) (int32, error) {
155+
if avs.Properties == nil || avs.Properties.PlatformFaultDomainCount == nil {
156+
return -1, errors.New("availability set has no fault domain count")
157+
}
158+
131159
return *avs.Properties.PlatformFaultDomainCount, nil
132160
}
133161

@@ -144,6 +172,11 @@ func GetAvailabilitySetContextE(t testing.TestingT, ctx context.Context, avsName
144172
return nil, err
145173
}
146174

175+
return GetAvailabilitySetWithClient(ctx, client, resGroupName, avsName)
176+
}
177+
178+
// GetAvailabilitySetWithClient gets an Availability Set using the provided AvailabilitySetsClient.
179+
func GetAvailabilitySetWithClient(ctx context.Context, client *armcompute.AvailabilitySetsClient, resGroupName string, avsName string) (*armcompute.AvailabilitySet, error) {
147180
resp, err := client.Get(ctx, resGroupName, avsName, nil)
148181
if err != nil {
149182
return nil, err
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package azure_test
2+
3+
import (
4+
"context"
5+
"net/http"
6+
"testing"
7+
8+
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
9+
azfake "github.com/Azure/azure-sdk-for-go/sdk/azcore/fake"
10+
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
11+
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
12+
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v6"
13+
computefake "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v6/fake"
14+
"github.com/gruntwork-io/terratest/modules/azure"
15+
"github.com/stretchr/testify/assert"
16+
"github.com/stretchr/testify/require"
17+
)
18+
19+
func newFakeAvailabilitySetsClient(t *testing.T, srv *computefake.AvailabilitySetsServer) *armcompute.AvailabilitySetsClient {
20+
t.Helper()
21+
22+
transport := computefake.NewAvailabilitySetsServerTransport(srv)
23+
client, err := armcompute.NewAvailabilitySetsClient("fake-sub", &azfake.TokenCredential{}, &arm.ClientOptions{
24+
ClientOptions: policy.ClientOptions{Transport: transport},
25+
})
26+
require.NoError(t, err)
27+
28+
return client
29+
}
30+
31+
func fakeAvsGetHandler(avsName string, vmIDs []string, faultDomainCount int32) func(context.Context, string, string, *armcompute.AvailabilitySetsClientGetOptions) (azfake.Responder[armcompute.AvailabilitySetsClientGetResponse], azfake.ErrorResponder) {
32+
return func(_ context.Context, _ string, _ string, _ *armcompute.AvailabilitySetsClientGetOptions) (resp azfake.Responder[armcompute.AvailabilitySetsClientGetResponse], errResp azfake.ErrorResponder) {
33+
vms := make([]*armcompute.SubResource, len(vmIDs))
34+
for i, id := range vmIDs {
35+
vms[i] = &armcompute.SubResource{ID: to.Ptr(id)}
36+
}
37+
38+
resp.SetResponse(http.StatusOK, armcompute.AvailabilitySetsClientGetResponse{
39+
AvailabilitySet: armcompute.AvailabilitySet{
40+
Name: to.Ptr(avsName),
41+
Properties: &armcompute.AvailabilitySetProperties{
42+
VirtualMachines: vms,
43+
PlatformFaultDomainCount: to.Ptr(faultDomainCount),
44+
},
45+
},
46+
}, nil)
47+
48+
return
49+
}
50+
}
51+
52+
func TestGetAvailabilitySetWithClient(t *testing.T) {
53+
t.Parallel()
54+
55+
srv := &computefake.AvailabilitySetsServer{
56+
Get: fakeAvsGetHandler("my-avs", nil, 2),
57+
}
58+
client := newFakeAvailabilitySetsClient(t, srv)
59+
60+
avs, err := azure.GetAvailabilitySetWithClient(t.Context(), client, "rg", "my-avs")
61+
require.NoError(t, err)
62+
assert.Equal(t, "my-avs", *avs.Name)
63+
}
64+
65+
func TestCheckAvailabilitySetContainsVMWithClient(t *testing.T) {
66+
t.Parallel()
67+
68+
vmIDs := []string{
69+
"/subscriptions/sub/resourceGroups/RG/providers/Microsoft.Compute/virtualMachines/VM-ONE",
70+
"/subscriptions/sub/resourceGroups/RG/providers/Microsoft.Compute/virtualMachines/VM-TWO",
71+
}
72+
73+
tests := []struct {
74+
name string
75+
vmName string
76+
found bool
77+
wantErr bool
78+
}{
79+
{name: "exact case match", vmName: "VM-ONE", found: true},
80+
{name: "case insensitive match", vmName: "vm-one", found: true},
81+
{name: "not found", vmName: "vm-three", found: false, wantErr: true},
82+
}
83+
84+
for _, tc := range tests {
85+
t.Run(tc.name, func(t *testing.T) {
86+
t.Parallel()
87+
88+
srv := &computefake.AvailabilitySetsServer{
89+
Get: fakeAvsGetHandler("avs", vmIDs, 2),
90+
}
91+
client := newFakeAvailabilitySetsClient(t, srv)
92+
93+
found, err := azure.CheckAvailabilitySetContainsVMWithClient(t.Context(), client, "rg", "avs", tc.vmName)
94+
95+
if tc.wantErr {
96+
require.Error(t, err)
97+
} else {
98+
require.NoError(t, err)
99+
}
100+
101+
assert.Equal(t, tc.found, found)
102+
})
103+
}
104+
}
105+
106+
func TestGetAvailabilitySetVMNamesInCapsWithClient(t *testing.T) {
107+
t.Parallel()
108+
109+
vmIDs := []string{
110+
"/subscriptions/sub/resourceGroups/RG/providers/Microsoft.Compute/virtualMachines/VM-ALPHA",
111+
"/subscriptions/sub/resourceGroups/RG/providers/Microsoft.Compute/virtualMachines/VM-BETA",
112+
}
113+
114+
srv := &computefake.AvailabilitySetsServer{
115+
Get: fakeAvsGetHandler("avs", vmIDs, 3),
116+
}
117+
client := newFakeAvailabilitySetsClient(t, srv)
118+
119+
names, err := azure.GetAvailabilitySetVMNamesInCapsWithClient(t.Context(), client, "rg", "avs")
120+
require.NoError(t, err)
121+
assert.Equal(t, []string{"VM-ALPHA", "VM-BETA"}, names)
122+
}
123+
124+
func TestGetAvailabilitySetFaultDomainCount(t *testing.T) {
125+
t.Parallel()
126+
127+
t.Run("valid", func(t *testing.T) {
128+
t.Parallel()
129+
130+
avs := &armcompute.AvailabilitySet{
131+
Properties: &armcompute.AvailabilitySetProperties{
132+
PlatformFaultDomainCount: to.Ptr[int32](3),
133+
},
134+
}
135+
136+
count, err := azure.GetAvailabilitySetFaultDomainCount(avs)
137+
require.NoError(t, err)
138+
assert.Equal(t, int32(3), count)
139+
})
140+
141+
t.Run("nil properties", func(t *testing.T) {
142+
t.Parallel()
143+
144+
avs := &armcompute.AvailabilitySet{}
145+
146+
_, err := azure.GetAvailabilitySetFaultDomainCount(avs)
147+
require.Error(t, err)
148+
})
149+
}

modules/azure/cosmosdb.go

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,16 @@ func GetCosmosDBAccountContextE(ctx context.Context, subscriptionID string, reso
5858
return nil, err
5959
}
6060

61-
// Get the corresponding database account
62-
resp, err := cosmosClient.Get(ctx, resourceGroupName, accountName, nil)
61+
return GetCosmosDBAccountWithClient(ctx, cosmosClient, resourceGroupName, accountName)
62+
}
63+
64+
// GetCosmosDBAccountWithClient gets a database account using the provided DatabaseAccountsClient.
65+
func GetCosmosDBAccountWithClient(ctx context.Context, client *armcosmos.DatabaseAccountsClient, resourceGroupName string, accountName string) (*armcosmos.DatabaseAccountGetResults, error) {
66+
resp, err := client.Get(ctx, resourceGroupName, accountName, nil)
6367
if err != nil {
6468
return nil, err
6569
}
6670

67-
// Return DB
6871
return &resp.DatabaseAccountGetResults, nil
6972
}
7073

@@ -118,13 +121,16 @@ func GetCosmosDBSQLDatabaseContextE(ctx context.Context, subscriptionID string,
118121
return nil, err
119122
}
120123

121-
// Get the corresponding database
122-
resp, err := cosmosClient.GetSQLDatabase(ctx, resourceGroupName, accountName, databaseName, nil)
124+
return GetCosmosDBSQLDatabaseWithClient(ctx, cosmosClient, resourceGroupName, accountName, databaseName)
125+
}
126+
127+
// GetCosmosDBSQLDatabaseWithClient gets a SQL database using the provided SQLResourcesClient.
128+
func GetCosmosDBSQLDatabaseWithClient(ctx context.Context, client *armcosmos.SQLResourcesClient, resourceGroupName string, accountName string, databaseName string) (*armcosmos.SQLDatabaseGetResults, error) {
129+
resp, err := client.GetSQLDatabase(ctx, resourceGroupName, accountName, databaseName, nil)
123130
if err != nil {
124131
return nil, err
125132
}
126133

127-
// Return DB
128134
return &resp.SQLDatabaseGetResults, nil
129135
}
130136

@@ -147,13 +153,16 @@ func GetCosmosDBSQLContainerContextE(ctx context.Context, subscriptionID string,
147153
return nil, err
148154
}
149155

150-
// Get the corresponding SQL container
151-
resp, err := cosmosClient.GetSQLContainer(ctx, resourceGroupName, accountName, databaseName, containerName, nil)
156+
return GetCosmosDBSQLContainerWithClient(ctx, cosmosClient, resourceGroupName, accountName, databaseName, containerName)
157+
}
158+
159+
// GetCosmosDBSQLContainerWithClient gets a SQL container using the provided SQLResourcesClient.
160+
func GetCosmosDBSQLContainerWithClient(ctx context.Context, client *armcosmos.SQLResourcesClient, resourceGroupName string, accountName string, databaseName string, containerName string) (*armcosmos.SQLContainerGetResults, error) {
161+
resp, err := client.GetSQLContainer(ctx, resourceGroupName, accountName, databaseName, containerName, nil)
152162
if err != nil {
153163
return nil, err
154164
}
155165

156-
// Return container
157166
return &resp.SQLContainerGetResults, nil
158167
}
159168

@@ -176,13 +185,17 @@ func GetCosmosDBSQLDatabaseThroughputContextE(ctx context.Context, subscriptionI
176185
return nil, err
177186
}
178187

179-
// Get the corresponding database throughput config
180-
resp, err := cosmosClient.GetSQLDatabaseThroughput(ctx, resourceGroupName, accountName, databaseName, nil)
188+
return GetCosmosDBSQLDatabaseThroughputWithClient(ctx, cosmosClient, resourceGroupName, accountName, databaseName)
189+
}
190+
191+
// GetCosmosDBSQLDatabaseThroughputWithClient gets a SQL database throughput configuration
192+
// using the provided SQLResourcesClient.
193+
func GetCosmosDBSQLDatabaseThroughputWithClient(ctx context.Context, client *armcosmos.SQLResourcesClient, resourceGroupName string, accountName string, databaseName string) (*armcosmos.ThroughputSettingsGetResults, error) {
194+
resp, err := client.GetSQLDatabaseThroughput(ctx, resourceGroupName, accountName, databaseName, nil)
181195
if err != nil {
182196
return nil, err
183197
}
184198

185-
// Return throughput config
186199
return &resp.ThroughputSettingsGetResults, nil
187200
}
188201

@@ -205,12 +218,16 @@ func GetCosmosDBSQLContainerThroughputContextE(ctx context.Context, subscription
205218
return nil, err
206219
}
207220

208-
// Get the corresponding container throughput config
209-
resp, err := cosmosClient.GetSQLContainerThroughput(ctx, resourceGroupName, accountName, databaseName, containerName, nil)
221+
return GetCosmosDBSQLContainerThroughputWithClient(ctx, cosmosClient, resourceGroupName, accountName, databaseName, containerName)
222+
}
223+
224+
// GetCosmosDBSQLContainerThroughputWithClient gets a SQL container throughput configuration
225+
// using the provided SQLResourcesClient.
226+
func GetCosmosDBSQLContainerThroughputWithClient(ctx context.Context, client *armcosmos.SQLResourcesClient, resourceGroupName string, accountName string, databaseName string, containerName string) (*armcosmos.ThroughputSettingsGetResults, error) {
227+
resp, err := client.GetSQLContainerThroughput(ctx, resourceGroupName, accountName, databaseName, containerName, nil)
210228
if err != nil {
211229
return nil, err
212230
}
213231

214-
// Return throughput config
215232
return &resp.ThroughputSettingsGetResults, nil
216233
}

0 commit comments

Comments
 (0)