|
| 1 | +// Copyright 2025 NetApp, Inc. All Rights Reserved. |
| 2 | + |
| 3 | +package api |
| 4 | + |
| 5 | +import ( |
| 6 | + "reflect" |
| 7 | + "strings" |
| 8 | + "testing" |
| 9 | + "time" |
| 10 | + |
| 11 | + "github.com/stretchr/testify/assert" |
| 12 | + "github.com/stretchr/testify/require" |
| 13 | +) |
| 14 | + |
| 15 | +// TestSnapmirrorTypes tests all snapmirror type methods and constants |
| 16 | +func TestSnapmirrorTypes(t *testing.T) { |
| 17 | + // Test data for all snapmirror types |
| 18 | + states := map[SnapmirrorState]bool{ |
| 19 | + SnapmirrorStateUninitialized: true, // Only this should be uninitialized |
| 20 | + SnapmirrorStateSnapmirrored: false, |
| 21 | + SnapmirrorStateBrokenOffZapi: false, |
| 22 | + SnapmirrorStateBrokenOffRest: false, |
| 23 | + SnapmirrorStateSynchronizing: false, |
| 24 | + SnapmirrorStateInSync: false, |
| 25 | + SnapmirrorState("invalid"): false, |
| 26 | + } |
| 27 | + |
| 28 | + statuses := map[SnapmirrorStatus]map[string]bool{ |
| 29 | + SnapmirrorStatusIdle: {"IsIdle": true}, |
| 30 | + SnapmirrorStatusAborting: {"IsAborting": true}, |
| 31 | + SnapmirrorStatusBreaking: {"IsBreaking": true}, |
| 32 | + SnapmirrorStatusTransferring: {"IsTransferring": true}, |
| 33 | + SnapmirrorStatusQuiescing: {}, |
| 34 | + SnapmirrorStatusFinalizing: {}, |
| 35 | + SnapmirrorStatusAborted: {}, |
| 36 | + SnapmirrorStatusFailed: {}, |
| 37 | + SnapmirrorStatusHardAborted: {}, |
| 38 | + SnapmirrorStatusQueued: {}, |
| 39 | + SnapmirrorStatusSuccess: {}, |
| 40 | + SnapmirrorStatus("invalid"): {}, |
| 41 | + } |
| 42 | + |
| 43 | + policyTypes := []struct { |
| 44 | + policyType SnapmirrorPolicyType |
| 45 | + isSync bool |
| 46 | + isAsync bool |
| 47 | + }{ |
| 48 | + {SnapmirrorPolicyZAPITypeSync, true, false}, |
| 49 | + {SnapmirrorPolicyRESTTypeSync, true, false}, |
| 50 | + {SnapmirrorPolicyZAPITypeAsync, false, true}, |
| 51 | + {SnapmirrorPolicyRESTTypeAsync, false, true}, |
| 52 | + {SnapmirrorPolicyType("invalid"), false, false}, |
| 53 | + } |
| 54 | + |
| 55 | + // Test state methods |
| 56 | + for state, expectUninitialized := range states { |
| 57 | + assert.Equal(t, expectUninitialized, state.IsUninitialized(), |
| 58 | + "State %s IsUninitialized should return %v", state, expectUninitialized) |
| 59 | + } |
| 60 | + |
| 61 | + // Test status methods |
| 62 | + for status, methods := range statuses { |
| 63 | + assert.Equal(t, methods["IsIdle"], status.IsIdle()) |
| 64 | + assert.Equal(t, methods["IsAborting"], status.IsAborting()) |
| 65 | + assert.Equal(t, methods["IsBreaking"], status.IsBreaking()) |
| 66 | + assert.Equal(t, methods["IsTransferring"], status.IsTransferring()) |
| 67 | + } |
| 68 | + |
| 69 | + // Test policy type methods |
| 70 | + for _, tc := range policyTypes { |
| 71 | + assert.Equal(t, tc.isSync, tc.policyType.IsSnapmirrorPolicyTypeSync()) |
| 72 | + assert.Equal(t, tc.isAsync, tc.policyType.IsSnapmirrorPolicyTypeAsync()) |
| 73 | + if tc.policyType != "invalid" { |
| 74 | + assert.NotEqual(t, tc.isSync, tc.isAsync, "Cannot be both sync and async") |
| 75 | + } |
| 76 | + } |
| 77 | +} |
| 78 | + |
| 79 | +// TestConstants validates all snapmirror constants and type definitions |
| 80 | +func TestConstants(t *testing.T) { |
| 81 | + // Test constant values and slice types in a single table |
| 82 | + tests := []struct { |
| 83 | + name string |
| 84 | + testFunc func(t *testing.T) |
| 85 | + }{ |
| 86 | + {"PolicyRule", func(t *testing.T) { |
| 87 | + assert.Equal(t, "all_source_snapshots", SnapmirrorPolicyRuleAll) |
| 88 | + }}, |
| 89 | + {"StateConstants", func(t *testing.T) { |
| 90 | + expected := map[SnapmirrorState]string{ |
| 91 | + SnapmirrorStateUninitialized: "uninitialized", |
| 92 | + SnapmirrorStateSnapmirrored: "snapmirrored", |
| 93 | + SnapmirrorStateBrokenOffZapi: "broken-off", |
| 94 | + SnapmirrorStateBrokenOffRest: "broken_off", |
| 95 | + SnapmirrorStateSynchronizing: "synchronizing", |
| 96 | + SnapmirrorStateInSync: "in_sync", |
| 97 | + } |
| 98 | + for state, expectedValue := range expected { |
| 99 | + assert.Equal(t, expectedValue, string(state)) |
| 100 | + } |
| 101 | + assert.NotEqual(t, SnapmirrorStateBrokenOffZapi, SnapmirrorStateBrokenOffRest) |
| 102 | + }}, |
| 103 | + {"StatusConstants", func(t *testing.T) { |
| 104 | + statuses := []SnapmirrorStatus{ |
| 105 | + SnapmirrorStatusIdle, SnapmirrorStatusAborting, SnapmirrorStatusBreaking, |
| 106 | + SnapmirrorStatusQuiescing, SnapmirrorStatusTransferring, SnapmirrorStatusFinalizing, |
| 107 | + SnapmirrorStatusAborted, SnapmirrorStatusFailed, SnapmirrorStatusHardAborted, |
| 108 | + SnapmirrorStatusQueued, SnapmirrorStatusSuccess, |
| 109 | + } |
| 110 | + uniqueValues := make(map[SnapmirrorStatus]bool) |
| 111 | + for _, status := range statuses { |
| 112 | + assert.NotEmpty(t, string(status)) |
| 113 | + assert.False(t, uniqueValues[status], "Status should be unique") |
| 114 | + uniqueValues[status] = true |
| 115 | + } |
| 116 | + }}, |
| 117 | + {"PolicyTypeConstants", func(t *testing.T) { |
| 118 | + expected := map[SnapmirrorPolicyType]string{ |
| 119 | + SnapmirrorPolicyZAPITypeSync: "sync_mirror", |
| 120 | + SnapmirrorPolicyZAPITypeAsync: "async_mirror", |
| 121 | + SnapmirrorPolicyRESTTypeSync: "sync", |
| 122 | + SnapmirrorPolicyRESTTypeAsync: "async", |
| 123 | + } |
| 124 | + for policyType, expectedValue := range expected { |
| 125 | + assert.Equal(t, expectedValue, string(policyType)) |
| 126 | + } |
| 127 | + }}, |
| 128 | + {"SliceTypes", func(t *testing.T) { |
| 129 | + // Test all slice type definitions |
| 130 | + volumes := Volumes{&Volume{Name: "vol1"}} |
| 131 | + luns := Luns{Lun{Name: "lun1"}} |
| 132 | + snapshots := Snapshots{Snapshot{Name: "snap1"}} |
| 133 | + qtrees := Qtrees{&Qtree{Name: "qtree1"}} |
| 134 | + quotas := QuotaEntries{&QuotaEntry{Target: "quota1"}} |
| 135 | + namespaces := NVMeNamespaces{&NVMeNamespace{Name: "ns1"}} |
| 136 | + volumeNames := VolumeNameList{"vol1", "vol2"} |
| 137 | + qtreeNames := QtreeNameList{"qtree1", "qtree2"} |
| 138 | + |
| 139 | + assert.Len(t, volumes, 1) |
| 140 | + assert.Len(t, luns, 1) |
| 141 | + assert.Len(t, snapshots, 1) |
| 142 | + assert.Len(t, qtrees, 1) |
| 143 | + assert.Len(t, quotas, 1) |
| 144 | + assert.Len(t, namespaces, 1) |
| 145 | + assert.Len(t, volumeNames, 2) |
| 146 | + assert.Len(t, qtreeNames, 2) |
| 147 | + }}, |
| 148 | + } |
| 149 | + |
| 150 | + for _, tt := range tests { |
| 151 | + t.Run(tt.name, tt.testFunc) |
| 152 | + } |
| 153 | +} |
| 154 | + |
| 155 | +// TestStructValidation validates business logic and edge cases for all structs |
| 156 | +func TestStructValidation(t *testing.T) { |
| 157 | + tests := []struct { |
| 158 | + name string |
| 159 | + testFunc func(t *testing.T) |
| 160 | + }{ |
| 161 | + {"VolumeLogic", func(t *testing.T) { |
| 162 | + encryptTrue, snapshotDirFalse := true, false |
| 163 | + vol := Volume{ |
| 164 | + Aggregates: []string{"aggr1", "aggr2"}, |
| 165 | + Encrypt: &encryptTrue, |
| 166 | + SnapshotDir: &snapshotDirFalse, |
| 167 | + SnapshotReserve: 5, |
| 168 | + } |
| 169 | + require.NotNil(t, vol.Encrypt) |
| 170 | + assert.True(t, *vol.Encrypt) |
| 171 | + require.NotNil(t, vol.SnapshotDir) |
| 172 | + assert.False(t, *vol.SnapshotDir) |
| 173 | + assert.Len(t, vol.Aggregates, 2) |
| 174 | + assert.True(t, vol.SnapshotReserve >= 0 && vol.SnapshotReserve <= 100) |
| 175 | + }}, |
| 176 | + {"SnapmirrorLogic", func(t *testing.T) { |
| 177 | + endTime := time.Now() |
| 178 | + sm := Snapmirror{ |
| 179 | + State: SnapmirrorStateSnapmirrored, |
| 180 | + RelationshipStatus: SnapmirrorStatusIdle, |
| 181 | + IsHealthy: true, |
| 182 | + UnhealthyReason: "", |
| 183 | + EndTransferTime: &endTime, |
| 184 | + } |
| 185 | + assert.False(t, sm.State.IsUninitialized()) |
| 186 | + assert.True(t, sm.RelationshipStatus.IsIdle()) |
| 187 | + assert.Empty(t, sm.UnhealthyReason) |
| 188 | + require.NotNil(t, sm.EndTransferTime) |
| 189 | + }}, |
| 190 | + {"LunMappingLogic", func(t *testing.T) { |
| 191 | + spaceReserved, spaceAllocated := true, false |
| 192 | + lun := Lun{ |
| 193 | + LunMaps: []LunMap{{IgroupName: "ig1", LunID: 0}, {IgroupName: "ig2", LunID: 1}}, |
| 194 | + Mapped: true, |
| 195 | + SpaceReserved: &spaceReserved, |
| 196 | + SpaceAllocated: &spaceAllocated, |
| 197 | + } |
| 198 | + assert.Equal(t, len(lun.LunMaps) > 0, lun.Mapped) |
| 199 | + lunIDs := make(map[int]bool) |
| 200 | + for _, lunMap := range lun.LunMaps { |
| 201 | + assert.False(t, lunIDs[lunMap.LunID], "LUN IDs should be unique") |
| 202 | + lunIDs[lunMap.LunID] = true |
| 203 | + assert.NotEmpty(t, lunMap.IgroupName) |
| 204 | + } |
| 205 | + require.NotNil(t, lun.SpaceReserved) |
| 206 | + assert.True(t, *lun.SpaceReserved) |
| 207 | + }}, |
| 208 | + {"QuotaLogic", func(t *testing.T) { |
| 209 | + quota := QuotaEntry{Target: "/vol/test/qtree1", DiskLimitBytes: 1073741824} |
| 210 | + assert.NotEmpty(t, quota.Target) |
| 211 | + assert.True(t, quota.DiskLimitBytes > 0) |
| 212 | + assert.True(t, strings.HasPrefix(quota.Target, "/")) |
| 213 | + }}, |
| 214 | + {"EdgeCases", func(t *testing.T) { |
| 215 | + // Test empty structs and nil pointers |
| 216 | + var volume Volume |
| 217 | + var lun Lun |
| 218 | + assert.Empty(t, volume.Name) |
| 219 | + assert.Nil(t, volume.Encrypt) |
| 220 | + assert.Empty(t, lun.Name) |
| 221 | + |
| 222 | + // Test pointer field behavior |
| 223 | + encrypt := true |
| 224 | + volume.Encrypt = &encrypt |
| 225 | + require.NotNil(t, volume.Encrypt) |
| 226 | + assert.True(t, *volume.Encrypt) |
| 227 | + |
| 228 | + // Test slice boundaries |
| 229 | + lun.LunMaps = []LunMap{} |
| 230 | + assert.NotNil(t, lun.LunMaps) |
| 231 | + assert.Len(t, lun.LunMaps, 0) |
| 232 | + }}, |
| 233 | + {"TypeCompatibility", func(t *testing.T) { |
| 234 | + // Test reflection and string conversion |
| 235 | + volume := Volume{Name: "test"} |
| 236 | + volumeType := reflect.TypeOf(volume) |
| 237 | + assert.Equal(t, "Volume", volumeType.Name()) |
| 238 | + assert.Equal(t, reflect.Struct, volumeType.Kind()) |
| 239 | + |
| 240 | + nameField, exists := volumeType.FieldByName("Name") |
| 241 | + assert.True(t, exists) |
| 242 | + assert.Equal(t, "string", nameField.Type.String()) |
| 243 | + |
| 244 | + // Test enum string conversion |
| 245 | + state := SnapmirrorStateUninitialized |
| 246 | + assert.Equal(t, "uninitialized", string(state)) |
| 247 | + customState := SnapmirrorState("custom_state") |
| 248 | + assert.Equal(t, "custom_state", string(customState)) |
| 249 | + }}, |
| 250 | + } |
| 251 | + |
| 252 | + for _, tt := range tests { |
| 253 | + t.Run(tt.name, tt.testFunc) |
| 254 | + } |
| 255 | +} |
0 commit comments