@spinnaker/azure 2026.2.3 → 2026.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/dist/azure.module.d.ts +3 -1
  2. package/dist/image/image.reader.d.ts +4 -2
  3. package/dist/index.js +1 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/instance/azureInstanceType.service.d.ts +6 -2
  6. package/dist/instance/details/AzureInstanceDetails.d.ts +67 -0
  7. package/dist/loadBalancer/configure/AzureLoadBalancerChoiceModal.d.ts +10 -5
  8. package/dist/loadBalancer/configure/AzureLoadBalancerModal.d.ts +102 -0
  9. package/dist/loadBalancer/details/azureLoadBalancerDetails.d.ts +26 -0
  10. package/dist/loadBalancer/loadBalancer.transformer.d.ts +45 -2
  11. package/dist/pipeline/stages/AzureAccountRegionClusterSelector.d.ts +32 -0
  12. package/dist/pipeline/stages/bake/azureBakeStage.d.ts +46 -2
  13. package/dist/pipeline/stages/destroyAsg/azureDestroyAsgStage.d.ts +14 -2
  14. package/dist/pipeline/stages/disableAsg/azureDisableAsgStage.d.ts +4 -2
  15. package/dist/pipeline/stages/enableAsg/azureEnableAsgStage.d.ts +4 -2
  16. package/dist/securityGroup/configure/AzureSecurityGroupModal.d.ts +70 -0
  17. package/dist/securityGroup/details/AzureSecurityGroupDetails.d.ts +51 -0
  18. package/dist/securityGroup/securityGroup.reader.d.ts +3 -2
  19. package/dist/securityGroup/securityGroup.transformer.d.ts +3 -2
  20. package/dist/securityGroup/securityGroup.write.service.d.ts +9 -2
  21. package/dist/serverGroup/configure/serverGroupCommandBuilder.service.d.ts +88 -2
  22. package/dist/serverGroup/configure/serverGroupConfiguration.service.d.ts +30 -2
  23. package/dist/serverGroup/configure/wizard/AzureCloneServerGroupModal.d.ts +44 -0
  24. package/dist/serverGroup/configure/wizard/pages/ServerGroupAdvancedSettings.d.ts +8 -0
  25. package/dist/serverGroup/configure/wizard/pages/ServerGroupBasicSettings.d.ts +12 -0
  26. package/dist/serverGroup/configure/wizard/pages/ServerGroupCapacityAndZones.d.ts +11 -0
  27. package/dist/serverGroup/configure/wizard/pages/ServerGroupHealthSettings.d.ts +10 -0
  28. package/dist/serverGroup/configure/wizard/pages/ServerGroupImageSettings.d.ts +12 -0
  29. package/dist/serverGroup/configure/wizard/pages/ServerGroupInstanceType.d.ts +9 -0
  30. package/dist/serverGroup/configure/wizard/pages/ServerGroupLoadBalancers.d.ts +14 -0
  31. package/dist/serverGroup/configure/wizard/pages/ServerGroupNetworkSettings.d.ts +10 -0
  32. package/dist/serverGroup/configure/wizard/pages/ServerGroupSecurityGroups.d.ts +6 -0
  33. package/dist/serverGroup/configure/wizard/pages/ServerGroupTags.d.ts +9 -0
  34. package/dist/serverGroup/configure/wizard/pages/common.d.ts +14 -0
  35. package/dist/serverGroup/configure/wizard/pages/index.d.ts +10 -0
  36. package/dist/serverGroup/details/azureServerGroupDetails.d.ts +13 -0
  37. package/dist/serverGroup/details/rollback/RollbackServerGroupModal.d.ts +6 -4
  38. package/dist/serverGroup/serverGroup.transformer.d.ts +61 -2
  39. package/dist/validation/applicationName.validator.d.ts +6 -2
  40. package/package.json +5 -8
  41. package/src/azure.module.spec.ts +921 -0
  42. package/src/azure.module.ts +56 -68
  43. package/src/image/image.reader.js +4 -15
  44. package/src/image/image.reader.spec.js +4 -7
  45. package/src/instance/azureInstanceType.service.js +8 -16
  46. package/src/instance/azureInstanceType.service.spec.js +5 -13
  47. package/src/instance/details/AzureInstanceDetails.spec.tsx +364 -0
  48. package/src/instance/details/AzureInstanceDetails.tsx +533 -0
  49. package/src/loadBalancer/configure/AzureLoadBalancerChoiceModal.spec.tsx +125 -0
  50. package/src/loadBalancer/configure/AzureLoadBalancerChoiceModal.tsx +46 -26
  51. package/src/loadBalancer/configure/AzureLoadBalancerModal.spec.tsx +658 -0
  52. package/src/loadBalancer/configure/AzureLoadBalancerModal.tsx +960 -0
  53. package/src/loadBalancer/details/azureLoadBalancerDetails.spec.tsx +470 -0
  54. package/src/loadBalancer/details/azureLoadBalancerDetails.tsx +412 -0
  55. package/src/loadBalancer/loadBalancer.transformer.js +83 -93
  56. package/src/pipeline/stages/AzureAccountRegionClusterSelector.tsx +238 -0
  57. package/src/pipeline/stages/bake/azureBakeStage.tsx +723 -0
  58. package/src/pipeline/stages/destroyAsg/azureDestroyAsgStage.tsx +128 -0
  59. package/src/pipeline/stages/disableAsg/azureDisableAsgStage.tsx +92 -0
  60. package/src/pipeline/stages/enableAsg/azureEnableAsgStage.tsx +87 -0
  61. package/src/routerConsumers.spec.tsx +69 -0
  62. package/src/securityGroup/configure/AzureSecurityGroupModal.spec.tsx +558 -0
  63. package/src/securityGroup/configure/AzureSecurityGroupModal.tsx +782 -0
  64. package/src/securityGroup/details/AzureSecurityGroupDetails.spec.tsx +291 -0
  65. package/src/securityGroup/details/AzureSecurityGroupDetails.tsx +354 -0
  66. package/src/securityGroup/securityGroup.reader.js +3 -13
  67. package/src/securityGroup/securityGroup.transformer.js +3 -13
  68. package/src/securityGroup/securityGroup.write.service.spec.ts +105 -0
  69. package/src/securityGroup/securityGroup.write.service.ts +73 -0
  70. package/src/serverGroup/configure/serverGroupCommandBuilder.service.js +167 -166
  71. package/src/serverGroup/configure/serverGroupCommandBuilder.service.spec.js +63 -0
  72. package/src/serverGroup/configure/serverGroupConfiguration.service.js +325 -309
  73. package/src/serverGroup/configure/serverGroupConfiguration.service.spec.js +57 -53
  74. package/src/serverGroup/configure/wizard/AzureCloneServerGroupModal.spec.tsx +758 -0
  75. package/src/serverGroup/configure/wizard/AzureCloneServerGroupModal.tsx +349 -0
  76. package/src/serverGroup/configure/wizard/pages/ServerGroupAdvancedSettings.tsx +208 -0
  77. package/src/serverGroup/configure/wizard/pages/ServerGroupBasicSettings.tsx +122 -0
  78. package/src/serverGroup/configure/wizard/pages/ServerGroupCapacityAndZones.tsx +80 -0
  79. package/src/serverGroup/configure/wizard/pages/ServerGroupHealthSettings.tsx +97 -0
  80. package/src/serverGroup/configure/wizard/pages/ServerGroupImageSettings.tsx +151 -0
  81. package/src/serverGroup/configure/wizard/pages/ServerGroupInstanceType.tsx +41 -0
  82. package/src/serverGroup/configure/wizard/pages/ServerGroupLoadBalancers.tsx +214 -0
  83. package/src/serverGroup/configure/wizard/pages/ServerGroupNetworkSettings.tsx +119 -0
  84. package/src/serverGroup/configure/wizard/pages/ServerGroupSecurityGroups.tsx +54 -0
  85. package/src/serverGroup/configure/wizard/pages/ServerGroupTags.tsx +40 -0
  86. package/src/serverGroup/configure/wizard/pages/common.tsx +72 -0
  87. package/src/serverGroup/configure/wizard/pages/index.ts +10 -0
  88. package/src/serverGroup/details/azureServerGroupDetails.spec.tsx +263 -0
  89. package/src/serverGroup/details/azureServerGroupDetails.tsx +318 -0
  90. package/src/serverGroup/details/rollback/RollbackServerGroupModal.tsx +24 -30
  91. package/src/serverGroup/serverGroup.transformer.js +11 -19
  92. package/src/serverGroup/serverGroup.transformer.spec.js +2 -6
  93. package/src/validation/applicationName.validator.js +14 -29
  94. package/dist/instance/details/instance.details.controller.d.ts +0 -2
  95. package/dist/loadBalancer/configure/createLoadBalancer.controller.d.ts +0 -2
  96. package/dist/loadBalancer/details/loadBalancerDetail.controller.d.ts +0 -2
  97. package/dist/pipeline/stages/bake/bakeExecutionDetails.controller.d.ts +0 -2
  98. package/dist/securityGroup/clone/cloneSecurityGroup.controller.d.ts +0 -2
  99. package/dist/securityGroup/configure/CreateSecurityGroupCtrl.d.ts +0 -2
  100. package/dist/securityGroup/configure/EditSecurityGroupCtrl.d.ts +0 -2
  101. package/dist/securityGroup/details/securityGroupDetail.controller.d.ts +0 -2
  102. package/dist/serverGroup/configure/serverGroup.configure.azure.module.d.ts +0 -2
  103. package/dist/serverGroup/configure/wizard/CloneServerGroup.azure.controller.d.ts +0 -2
  104. package/dist/serverGroup/configure/wizard/ServerGroupInstanceArchetype.controller.d.ts +0 -2
  105. package/dist/serverGroup/configure/wizard/ServerGroupInstanceType.controller.d.ts +0 -2
  106. package/dist/serverGroup/configure/wizard/advancedSettings/ServerGroupAdvancedSettings.controller.d.ts +0 -2
  107. package/dist/serverGroup/configure/wizard/advancedSettings/advancedSettingsSelector.directive.d.ts +0 -2
  108. package/dist/serverGroup/configure/wizard/basicSettings/ServerGroupBasicSettings.controller.d.ts +0 -2
  109. package/dist/serverGroup/configure/wizard/basicSettings/image.regional.filter.d.ts +0 -2
  110. package/dist/serverGroup/configure/wizard/capacity/capacitySelector.directive.d.ts +0 -2
  111. package/dist/serverGroup/configure/wizard/healthSettings/ServerGroupHealthSettings.controller.d.ts +0 -2
  112. package/dist/serverGroup/configure/wizard/healthSettings/healthSettingsSelector.directive.d.ts +0 -2
  113. package/dist/serverGroup/configure/wizard/image/ServerGroupImageSettings.controller.d.ts +0 -2
  114. package/dist/serverGroup/configure/wizard/image/imageSettingsSelector.directive.d.ts +0 -2
  115. package/dist/serverGroup/configure/wizard/loadBalancers/ServerGroupLoadBalancers.controller.d.ts +0 -2
  116. package/dist/serverGroup/configure/wizard/loadBalancers/serverGroupLoadBalancersSelector.directive.d.ts +0 -2
  117. package/dist/serverGroup/configure/wizard/networkSettings/ServerGroupNetworkSettings.controller.d.ts +0 -2
  118. package/dist/serverGroup/configure/wizard/networkSettings/ServerGroupNetworkSettingsSelector.directive.d.ts +0 -2
  119. package/dist/serverGroup/configure/wizard/securityGroup/ServerGroupSecurityGroups.controller.d.ts +0 -2
  120. package/dist/serverGroup/configure/wizard/securityGroup/serverGroupSecurityGroupsSelector.directive.d.ts +0 -2
  121. package/dist/serverGroup/configure/wizard/tags/tagsSelector.directive.d.ts +0 -2
  122. package/dist/serverGroup/configure/wizard/zones/zoneSelector.directive.d.ts +0 -2
  123. package/dist/serverGroup/details/serverGroup.details.module.d.ts +0 -2
  124. package/dist/serverGroup/details/serverGroupDetails.azure.controller.d.ts +0 -2
  125. package/src/instance/details/instance.details.controller.js +0 -371
  126. package/src/instance/details/instance.details.controller.spec.js +0 -129
  127. package/src/instance/details/instanceDetails.html +0 -43
  128. package/src/loadBalancer/configure/advancedSettings.html +0 -95
  129. package/src/loadBalancer/configure/createLoadBalancer.controller.js +0 -275
  130. package/src/loadBalancer/configure/createLoadBalancer.controller.spec.js +0 -39
  131. package/src/loadBalancer/configure/createLoadBalancer.html +0 -25
  132. package/src/loadBalancer/configure/createLoadBalancerProperties.html +0 -128
  133. package/src/loadBalancer/configure/editLoadBalancer.html +0 -26
  134. package/src/loadBalancer/configure/healthCheck.html +0 -74
  135. package/src/loadBalancer/configure/listeners.html +0 -50
  136. package/src/loadBalancer/details/loadBalancerDetail.controller.js +0 -181
  137. package/src/loadBalancer/details/loadBalancerDetail.controller.spec.js +0 -39
  138. package/src/loadBalancer/details/loadBalancerDetail.html +0 -128
  139. package/src/pipeline/stages/bake/azureBakeStage.js +0 -276
  140. package/src/pipeline/stages/bake/bakeExecutionDetails.controller.js +0 -40
  141. package/src/pipeline/stages/bake/bakeExecutionDetails.html +0 -50
  142. package/src/pipeline/stages/bake/bakeStage.html +0 -187
  143. package/src/pipeline/stages/destroyAsg/azureDestroyAsgStage.js +0 -73
  144. package/src/pipeline/stages/destroyAsg/destroyAsgStage.html +0 -9
  145. package/src/pipeline/stages/destroyAsg/destroyAsgStepLabel.html +0 -1
  146. package/src/pipeline/stages/disableAsg/azureDisableAsgStage.js +0 -65
  147. package/src/pipeline/stages/disableAsg/disableAsgStage.html +0 -11
  148. package/src/pipeline/stages/disableAsg/disableAsgStepLabel.html +0 -1
  149. package/src/pipeline/stages/enableAsg/azureEnableAsgStage.js +0 -70
  150. package/src/pipeline/stages/enableAsg/enableAsgStage.html +0 -11
  151. package/src/pipeline/stages/enableAsg/enableAsgStepLabel.html +0 -1
  152. package/src/securityGroup/clone/cloneSecurityGroup.controller.js +0 -162
  153. package/src/securityGroup/clone/cloneSecurityGroup.html +0 -8
  154. package/src/securityGroup/configure/CreateSecurityGroup.controller.spec.js +0 -78
  155. package/src/securityGroup/configure/CreateSecurityGroupCtrl.js +0 -265
  156. package/src/securityGroup/configure/EditSecurityGroupCtrl.js +0 -222
  157. package/src/securityGroup/configure/configSecurityGroup.mixin.controller.js +0 -262
  158. package/src/securityGroup/configure/createSecurityGroup.html +0 -20
  159. package/src/securityGroup/configure/createSecurityGroupIngress.html +0 -75
  160. package/src/securityGroup/configure/createSecurityGroupProperties.html +0 -88
  161. package/src/securityGroup/configure/editSecurityGroup.html +0 -19
  162. package/src/securityGroup/details/securityGroupDetail.controller.js +0 -142
  163. package/src/securityGroup/details/securityGroupDetail.html +0 -78
  164. package/src/securityGroup/securityGroup.write.service.js +0 -57
  165. package/src/serverGroup/configure/serverGroup.configure.azure.module.js +0 -47
  166. package/src/serverGroup/configure/wizard/CloneServerGroup.azure.controller.js +0 -247
  167. package/src/serverGroup/configure/wizard/ServerGroupInstanceArchetype.controller.js +0 -35
  168. package/src/serverGroup/configure/wizard/ServerGroupInstanceType.controller.js +0 -15
  169. package/src/serverGroup/configure/wizard/advancedSettings/ServerGroupAdvancedSettings.controller.js +0 -25
  170. package/src/serverGroup/configure/wizard/advancedSettings/advancedSettings.html +0 -5
  171. package/src/serverGroup/configure/wizard/advancedSettings/advancedSettingsSelector.directive.html +0 -138
  172. package/src/serverGroup/configure/wizard/advancedSettings/advancedSettingsSelector.directive.js +0 -43
  173. package/src/serverGroup/configure/wizard/basicSettings/ServerGroupBasicSettings.controller.js +0 -71
  174. package/src/serverGroup/configure/wizard/basicSettings/basicSettings.html +0 -131
  175. package/src/serverGroup/configure/wizard/basicSettings/image.regional.filter.js +0 -15
  176. package/src/serverGroup/configure/wizard/basicSettings/image.regional.filter.spec.js +0 -42
  177. package/src/serverGroup/configure/wizard/capacity/capacity.html +0 -5
  178. package/src/serverGroup/configure/wizard/capacity/capacitySelector.directive.html +0 -20
  179. package/src/serverGroup/configure/wizard/capacity/capacitySelector.directive.js +0 -21
  180. package/src/serverGroup/configure/wizard/capacity/zones.html +0 -5
  181. package/src/serverGroup/configure/wizard/healthSettings/ServerGroupHealthSettings.controller.js +0 -38
  182. package/src/serverGroup/configure/wizard/healthSettings/healthSettings.html +0 -5
  183. package/src/serverGroup/configure/wizard/healthSettings/healthSettingsSelector.directive.html +0 -32
  184. package/src/serverGroup/configure/wizard/healthSettings/healthSettingsSelector.directive.js +0 -22
  185. package/src/serverGroup/configure/wizard/image/ServerGroupImageSettings.controller.js +0 -34
  186. package/src/serverGroup/configure/wizard/image/imageSettings.html +0 -5
  187. package/src/serverGroup/configure/wizard/image/imageSettingsSelector.directive.html +0 -33
  188. package/src/serverGroup/configure/wizard/image/imageSettingsSelector.directive.js +0 -22
  189. package/src/serverGroup/configure/wizard/instanceArchetype.html +0 -33
  190. package/src/serverGroup/configure/wizard/instanceType/instanceType.html +0 -10
  191. package/src/serverGroup/configure/wizard/instanceType.html +0 -33
  192. package/src/serverGroup/configure/wizard/loadBalancers/ServerGroupLoadBalancers.controller.js +0 -137
  193. package/src/serverGroup/configure/wizard/loadBalancers/loadBalancers.html +0 -3
  194. package/src/serverGroup/configure/wizard/loadBalancers/serverGroupLoadBalancersSelector.directive.html +0 -41
  195. package/src/serverGroup/configure/wizard/loadBalancers/serverGroupLoadBalancersSelector.directive.js +0 -22
  196. package/src/serverGroup/configure/wizard/networkSettings/ServerGroupNetworkSettings.controller.js +0 -44
  197. package/src/serverGroup/configure/wizard/networkSettings/ServerGroupNetworkSettingsSelector.directive.html +0 -54
  198. package/src/serverGroup/configure/wizard/networkSettings/ServerGroupNetworkSettingsSelector.directive.js +0 -19
  199. package/src/serverGroup/configure/wizard/networkSettings/networkSettings.html +0 -3
  200. package/src/serverGroup/configure/wizard/securityGroup/ServerGroupSecurityGroups.controller.js +0 -28
  201. package/src/serverGroup/configure/wizard/securityGroup/securityGroups.html +0 -3
  202. package/src/serverGroup/configure/wizard/securityGroup/serverGroupSecurityGroupsSelector.directive.html +0 -40
  203. package/src/serverGroup/configure/wizard/securityGroup/serverGroupSecurityGroupsSelector.directive.js +0 -38
  204. package/src/serverGroup/configure/wizard/serverGroupWizard.html +0 -61
  205. package/src/serverGroup/configure/wizard/tags/tags.html +0 -5
  206. package/src/serverGroup/configure/wizard/tags/tagsSelector.directive.html +0 -16
  207. package/src/serverGroup/configure/wizard/tags/tagsSelector.directive.js +0 -30
  208. package/src/serverGroup/configure/wizard/templateSelection.html +0 -9
  209. package/src/serverGroup/configure/wizard/zones/zoneSelector.directive.html +0 -21
  210. package/src/serverGroup/configure/wizard/zones/zoneSelector.directive.js +0 -28
  211. package/src/serverGroup/details/serverGroup.details.module.js +0 -11
  212. package/src/serverGroup/details/serverGroupDetails.azure.controller.js +0 -260
  213. package/src/serverGroup/details/serverGroupDetails.html +0 -98
  214. package/src/subnet/subnetSelectField.directive.html +0 -28
  215. package/src/subnet/subnetSelectField.directive.js +0 -35
@@ -0,0 +1,758 @@
1
+ import {
2
+ CloudProviderRegistry,
3
+ DeployInitializer,
4
+ DeploymentStrategySelector,
5
+ MapEditor,
6
+ NetworkReader,
7
+ nativePromiseService,
8
+ ReactModal,
9
+ WizardModal,
10
+ WizardPage,
11
+ } from '@spinnaker/core';
12
+ import { shallow } from 'enzyme';
13
+ import React from 'react';
14
+
15
+ import { registerAzureProvider } from '../../../azure.module';
16
+ import { AzureServerGroupTransformer } from '../../serverGroup.transformer';
17
+ import { AzureServerGroupConfigurationService } from '../serverGroupConfiguration.service';
18
+ import {
19
+ AzureCloneServerGroupModal as RoutedAzureCloneServerGroupModal,
20
+ AzureCloneServerGroupModalComponent as AzureCloneServerGroupModal,
21
+ } from './AzureCloneServerGroupModal';
22
+ import {
23
+ ServerGroupAdvancedSettings,
24
+ ServerGroupBasicSettings,
25
+ ServerGroupHealthSettings,
26
+ ServerGroupImageSettings,
27
+ ServerGroupLoadBalancers,
28
+ ServerGroupNetworkSettings,
29
+ ServerGroupTags,
30
+ } from './pages';
31
+
32
+ describe('AzureCloneServerGroupModal', () => {
33
+ let runtimeServices: any;
34
+ const application = {
35
+ name: 'fnord',
36
+ serverGroups: {
37
+ refresh: jasmine.createSpy('refresh'),
38
+ onNextRefresh: jasmine.createSpy('onNextRefresh'),
39
+ },
40
+ } as any;
41
+
42
+ function command(overrides: any = {}): any {
43
+ return {
44
+ application: 'fnord',
45
+ credentials: 'test',
46
+ region: 'westus',
47
+ selectedProvider: 'azure',
48
+ stack: 'web',
49
+ freeFormDetails: 'main',
50
+ instanceType: 'Standard_DS1_v2',
51
+ sku: { capacity: 2 },
52
+ selectedVnet: { name: 'vnet-a', resourceGroup: 'rg-a' },
53
+ subnet: 'subnet-a',
54
+ loadBalancerName: 'lb-a',
55
+ loadBalancerType: 'Azure Load Balancer',
56
+ securityGroupName: 'sg-a',
57
+ instanceTags: {},
58
+ dataDisks: [],
59
+ zonesEnabled: true,
60
+ zones: ['1'],
61
+ viewState: { mode: 'createPipeline', submitButtonLabel: 'Done' },
62
+ backingData: {
63
+ accounts: ['test'],
64
+ filtered: { regions: [{ name: 'westus' }], loadBalancers: [], securityGroups: [], instanceTypes: [] },
65
+ },
66
+ credentialsChanged: jasmine.createSpy('credentialsChanged').and.returnValue({ dirty: {} }),
67
+ regionChanged: jasmine.createSpy('regionChanged').and.returnValue({ dirty: {} }),
68
+ ...overrides,
69
+ };
70
+ }
71
+
72
+ beforeEach(() => {
73
+ runtimeServices = {
74
+ cacheInitializer: {},
75
+ loadBalancerReader: { getLoadBalancerDetails: jasmine.createSpy('getLoadBalancerDetails') },
76
+ securityGroupReader: {},
77
+ };
78
+ const configurationService = new AzureServerGroupConfigurationService(nativePromiseService, runtimeServices);
79
+ runtimeServices.providerServiceDelegate = {
80
+ getDelegate: jasmine.createSpy('getDelegate').and.returnValue(configurationService),
81
+ };
82
+ });
83
+
84
+ function shallowModal(serverGroupCommand: any): any {
85
+ const wrapper = shallow(
86
+ <AzureCloneServerGroupModal
87
+ title="Configure"
88
+ application={application}
89
+ command={serverGroupCommand}
90
+ closeModal={jasmine.createSpy('closeModal')}
91
+ dismissModal={jasmine.createSpy('dismissModal')}
92
+ />,
93
+ { disableLifecycleMethods: true },
94
+ );
95
+ const modal = wrapper.instance() as any;
96
+ modal.context = { services: runtimeServices };
97
+ modal.componentDidMount();
98
+ wrapper.update();
99
+ return wrapper;
100
+ }
101
+
102
+ function loadBalancerPage(serverGroupCommand: any): any {
103
+ const page = new ServerGroupLoadBalancers({ formik: formik(serverGroupCommand) } as any) as any;
104
+ page.context = { services: runtimeServices };
105
+ return page;
106
+ }
107
+
108
+ function formik(values: any): any {
109
+ return {
110
+ values,
111
+ setFieldValue: jasmine.createSpy('setFieldValue').and.callFake((field: string, value: any) => {
112
+ const path = field.split('.');
113
+ const leaf = path.pop();
114
+ const target = path.reduce((acc: any, key) => {
115
+ acc[key] = acc[key] || {};
116
+ return acc[key];
117
+ }, values);
118
+ target[leaf] = value;
119
+ }),
120
+ };
121
+ }
122
+
123
+ it('registers as the Azure clone server group modal', () => {
124
+ registerAzureProvider();
125
+
126
+ expect(CloudProviderRegistry.getValue('azure', 'serverGroup.CloneServerGroupModal').render).toBe(
127
+ (RoutedAzureCloneServerGroupModal as any).render,
128
+ );
129
+ });
130
+
131
+ it('show opens the React wizard and resolves with the submitted pipeline command', async () => {
132
+ const serverGroupCommand = command();
133
+ const runtimeServices = {} as any;
134
+ spyOn(ReactModal, 'show').and.returnValue(Promise.resolve(serverGroupCommand));
135
+
136
+ const result = await RoutedAzureCloneServerGroupModal.show(
137
+ {
138
+ title: 'Configure',
139
+ application,
140
+ command: serverGroupCommand,
141
+ } as any,
142
+ runtimeServices,
143
+ );
144
+
145
+ expect(result).toBe(serverGroupCommand);
146
+ expect(ReactModal.show).toHaveBeenCalledWith(
147
+ RoutedAzureCloneServerGroupModal,
148
+ jasmine.objectContaining({ title: 'Configure', application, command: serverGroupCommand }),
149
+ { dialogClassName: 'wizard-modal modal-lg' },
150
+ runtimeServices,
151
+ );
152
+ });
153
+
154
+ it('owns its refresh subscription across replacement and unmount', () => {
155
+ const firstUnsubscribe = jasmine.createSpy('firstUnsubscribe');
156
+ const secondUnsubscribe = jasmine.createSpy('secondUnsubscribe');
157
+ const callbacks: Array<() => void> = [];
158
+ const onNextRefresh = jasmine.createSpy('onNextRefresh').and.callFake((callback: () => void) => {
159
+ callbacks.push(callback);
160
+ return callbacks.length === 1 ? firstUnsubscribe : secondUnsubscribe;
161
+ });
162
+ const refresh = jasmine.createSpy('refresh');
163
+ const stateService = { go: jasmine.createSpy('go'), includes: jasmine.createSpy('includes') };
164
+ const modal = new AzureCloneServerGroupModal({
165
+ application: { name: 'fnord', serverGroups: { onNextRefresh, refresh } },
166
+ command: command(),
167
+ dismissModal: jasmine.createSpy('dismissModal'),
168
+ stateService,
169
+ title: 'Create server group',
170
+ } as any) as any;
171
+
172
+ modal.onTaskComplete();
173
+
174
+ expect(onNextRefresh.calls.first().invocationOrder).toBeLessThan(refresh.calls.first().invocationOrder);
175
+
176
+ modal.onTaskComplete();
177
+
178
+ expect(firstUnsubscribe).toHaveBeenCalledTimes(1);
179
+
180
+ modal.componentWillUnmount();
181
+ callbacks[1]();
182
+
183
+ expect(secondUnsubscribe).toHaveBeenCalledTimes(1);
184
+ expect(modal.applicationRefreshUnsubscribe).toBeUndefined();
185
+ expect(stateService.go).not.toHaveBeenCalled();
186
+ });
187
+
188
+ it('cancel and dismiss reject without submitting or mutating the command', async () => {
189
+ const serverGroupCommand = command({ viewState: { mode: 'clone' } });
190
+ const original = JSON.stringify(serverGroupCommand);
191
+ spyOn(ReactModal, 'show').and.returnValue(Promise.reject('cancelled'));
192
+
193
+ await expectAsync(
194
+ RoutedAzureCloneServerGroupModal.show(
195
+ { title: 'Clone', application, command: serverGroupCommand } as any,
196
+ {} as any,
197
+ ),
198
+ ).toBeRejectedWith('cancelled');
199
+
200
+ expect(JSON.stringify(serverGroupCommand)).toBe(original);
201
+ });
202
+
203
+ it('returns a pipeline command with fields consumed by Azure deploy configuration conversion', () => {
204
+ const serverGroupCommand = command();
205
+ const closeModal = jasmine.createSpy('closeModal').and.callFake((result: any) => result);
206
+ const modal = new AzureCloneServerGroupModal({
207
+ title: 'Configure',
208
+ application,
209
+ command: serverGroupCommand,
210
+ closeModal,
211
+ dismissModal: jasmine.createSpy('dismissModal'),
212
+ } as any) as any;
213
+
214
+ const submitted = modal.submit(serverGroupCommand);
215
+ const deployConfig = new AzureServerGroupTransformer().convertServerGroupCommandToDeployConfiguration(submitted);
216
+
217
+ expect(closeModal).toHaveBeenCalledWith(serverGroupCommand);
218
+ expect(deployConfig.account).toBe('test');
219
+ expect(deployConfig.region).toBe('westus');
220
+ expect(deployConfig.vnet).toBe('vnet-a');
221
+ expect(deployConfig.vnetResourceGroup).toBe('rg-a');
222
+ expect(deployConfig.subnet).toBe('subnet-a');
223
+ expect(deployConfig.loadBalancerName).toBe('lb-a');
224
+ expect(deployConfig.securityGroupName).toBe('sg-a');
225
+ expect(deployConfig.sku.capacity).toBe(2);
226
+ expect(deployConfig.sku.name).toBe('Standard_DS1_v2');
227
+ expect(deployConfig.zones).toEqual(['1']);
228
+ });
229
+
230
+ it('renders the React wizard with Azure pages in parity order', () => {
231
+ const serverGroupCommand = command();
232
+ const wrapper = shallowModal(serverGroupCommand);
233
+
234
+ wrapper.setState({ loaded: true });
235
+ const wizard = wrapper.find(WizardModal);
236
+ const pages = wizard.prop('render')({
237
+ formik: { values: serverGroupCommand } as any,
238
+ nextIdx: () => 0,
239
+ wizard: {} as any,
240
+ });
241
+ const labels = React.Children.toArray((pages as any).props.children)
242
+ .filter((page: any) => page.type === WizardPage)
243
+ .map((page: any) => page.props.label);
244
+
245
+ expect(labels).toEqual([
246
+ 'Basic Settings',
247
+ 'Image',
248
+ 'Instance Type',
249
+ 'Capacity and Zones',
250
+ 'Load Balancers',
251
+ 'Network Settings',
252
+ 'Firewalls',
253
+ 'Health',
254
+ 'Advanced Settings',
255
+ 'Tags',
256
+ ]);
257
+ });
258
+
259
+ it('renders the deployment strategy selector and forwards strategy changes through Formik without a provider callback', () => {
260
+ const serverGroupCommand = command({
261
+ viewState: { mode: 'createPipeline', disableStrategySelection: false },
262
+ });
263
+ const formikProps = formik(serverGroupCommand);
264
+ const wrapper = shallow(<ServerGroupBasicSettings app={application} formik={formikProps} />);
265
+ const selector = wrapper.find(DeploymentStrategySelector);
266
+
267
+ expect(selector.exists()).toBe(true);
268
+
269
+ const onFieldChange = selector.exists() ? selector.prop('onFieldChange') : undefined;
270
+ const onSelectorStrategyChange = selector.exists() ? selector.prop('onStrategyChange') : undefined;
271
+ const strategy = { key: 'redblack' } as any;
272
+ onFieldChange?.('scaleDown', true);
273
+
274
+ expect(serverGroupCommand.onStrategyChange).toBeUndefined();
275
+ expect(() => onSelectorStrategyChange?.(serverGroupCommand, strategy)).not.toThrow();
276
+ expect(formikProps.setFieldValue).toHaveBeenCalledWith('scaleDown', true);
277
+ expect(formikProps.setFieldValue).toHaveBeenCalledWith('strategy', 'redblack');
278
+ });
279
+
280
+ it('renders template selection before configuring deploy-stage commands', () => {
281
+ const serverGroupCommand = { viewState: { requiresTemplateSelection: true, disableStrategySelection: true } };
282
+ const wrapper = shallowModal(serverGroupCommand);
283
+
284
+ expect(wrapper.find(DeployInitializer).exists()).toBe(true);
285
+ expect(wrapper.find(WizardModal).exists()).toBe(false);
286
+ });
287
+
288
+ it('configures filtered images on the modal working command without mutating the caller command', () => {
289
+ const serverGroupCommand = command({
290
+ images: [
291
+ { imageName: 'ubuntu-west', amis: { westus: ['ami-west'] } },
292
+ { imageName: 'ubuntu-east', amis: { eastus: ['ami-east'] } },
293
+ ],
294
+ });
295
+
296
+ const wrapper = shallowModal(serverGroupCommand);
297
+ const workingCommand = (wrapper.state() as any).command;
298
+
299
+ expect(workingCommand.backingData.filtered.images).toEqual([{ imageName: 'ubuntu-west', ami: 'ami-west' }]);
300
+ expect(serverGroupCommand.backingData.filtered.images).toBeUndefined();
301
+ });
302
+
303
+ it('clears marketplace image selections when the selected region no longer supports them', () => {
304
+ const serverGroupCommand = command({
305
+ imageName: 'ubuntu-west',
306
+ selectedImage: { imageName: 'ubuntu-west', amis: { westus: ['ami-west'] } },
307
+ images: [
308
+ { imageName: 'ubuntu-west', amis: { westus: ['ami-west'] } },
309
+ { imageName: 'ubuntu-east', amis: { eastus: ['ami-east'] } },
310
+ ],
311
+ });
312
+ const wrapper = shallowModal(serverGroupCommand);
313
+ const workingCommand = (wrapper.state() as any).command;
314
+
315
+ workingCommand.region = 'eastus';
316
+ const result = workingCommand.regionChanged(workingCommand);
317
+ workingCommand.processCommandUpdateResult(result);
318
+
319
+ expect(workingCommand.backingData.filtered.images).toEqual([{ imageName: 'ubuntu-east', ami: 'ami-east' }]);
320
+ expect(workingCommand.imageName).toBeNull();
321
+ expect(workingCommand.selectedImage).toBeNull();
322
+ });
323
+
324
+ it('validates custom image fields and keeps them on the command image', () => {
325
+ const serverGroupCommand = command({
326
+ image: { isCustom: true, region: 'westus', imageName: 'custom-image', ostype: 'Linux', uri: 'https://image.vhd' },
327
+ selectedImage: null,
328
+ });
329
+ const page = new ServerGroupImageSettings({ formik: formik(serverGroupCommand) } as any) as any;
330
+
331
+ expect(page.validate(serverGroupCommand)).toEqual({});
332
+
333
+ page.customImageFieldChanged('uri', 'https://replacement.vhd');
334
+
335
+ expect(serverGroupCommand.image).toEqual({
336
+ isCustom: true,
337
+ region: 'westus',
338
+ imageName: 'custom-image',
339
+ ostype: 'Linux',
340
+ uri: 'https://replacement.vhd',
341
+ });
342
+ expect(page.validate({ image: { isCustom: true } })).toEqual({
343
+ imageName: 'Image name required.',
344
+ ostype: 'OS type required.',
345
+ uri: 'URI required.',
346
+ });
347
+ });
348
+
349
+ it('renders account and region filtered load balancers from the command', () => {
350
+ spyOn(NetworkReader, 'listNetworks').and.returnValue(Promise.resolve({ azure: [] }) as any);
351
+ const serverGroupCommand = command({
352
+ loadBalancers: ['lb-a'],
353
+ loadBalancerName: null,
354
+ backingData: { loadBalancers: [{ name: 'lb-a', loadBalancerType: 'LOAD_BALANCER' }], filtered: {} },
355
+ });
356
+ const wrapper = shallow(<ServerGroupLoadBalancers formik={formik(serverGroupCommand)} />);
357
+
358
+ expect(wrapper.find('option[value="lb-a"]').exists()).toBe(true);
359
+ });
360
+
361
+ it('loads VNet and subnet options when an application gateway load balancer is selected', async () => {
362
+ const serverGroupCommand = command({
363
+ loadBalancerName: null,
364
+ loadBalancerType: null,
365
+ selectedVnet: null,
366
+ selectedVnetSubnets: [],
367
+ backingData: {
368
+ loadBalancers: [{ name: 'lb-a', loadBalancerType: 'APPLICATION_GATEWAY' }],
369
+ filtered: { loadBalancers: ['lb-a'] },
370
+ },
371
+ });
372
+ runtimeServices.loadBalancerReader = {
373
+ getLoadBalancerDetails: () => Promise.resolve([{ vnet: 'vnet-a' }]),
374
+ };
375
+ spyOn(NetworkReader, 'listNetworks').and.returnValue(
376
+ Promise.resolve({
377
+ azure: [
378
+ {
379
+ account: 'test',
380
+ name: 'vnet-a',
381
+ region: 'westus',
382
+ resourceGroup: 'rg-a',
383
+ subnets: [
384
+ { name: 'subnet-a', devices: [] },
385
+ { name: 'gateway-subnet', devices: [{ type: 'applicationGateways' }] },
386
+ ],
387
+ },
388
+ ],
389
+ } as any),
390
+ );
391
+
392
+ const page = loadBalancerPage(serverGroupCommand);
393
+ await page.loadBalancerChanged('lb-a');
394
+
395
+ expect(serverGroupCommand.loadBalancerType).toBe('Azure Application Gateway');
396
+ expect(serverGroupCommand.selectedVnet.name).toBe('vnet-a');
397
+ expect(serverGroupCommand.selectedVnetSubnets).toEqual(['subnet-a']);
398
+ });
399
+
400
+ it('clears stale subnet selections after refreshing load balancer VNet subnets', async () => {
401
+ const serverGroupCommand = command({
402
+ loadBalancerName: null,
403
+ loadBalancerType: null,
404
+ selectedSubnet: 'old-subnet',
405
+ subnet: 'old-subnet',
406
+ backingData: {
407
+ loadBalancers: [{ name: 'lb-a', loadBalancerType: 'APPLICATION_GATEWAY' }],
408
+ filtered: { loadBalancers: ['lb-a'] },
409
+ },
410
+ });
411
+ runtimeServices.loadBalancerReader = {
412
+ getLoadBalancerDetails: () => Promise.resolve([{ vnet: 'vnet-a' }]),
413
+ };
414
+ spyOn(NetworkReader, 'listNetworks').and.returnValue(
415
+ Promise.resolve({
416
+ azure: [
417
+ {
418
+ account: 'test',
419
+ name: 'vnet-a',
420
+ region: 'westus',
421
+ resourceGroup: 'rg-a',
422
+ subnets: [{ name: 'subnet-new', devices: [] }],
423
+ },
424
+ ],
425
+ } as any),
426
+ );
427
+
428
+ const page = loadBalancerPage(serverGroupCommand);
429
+ await page.loadBalancerChanged('lb-a');
430
+
431
+ expect(serverGroupCommand.selectedVnet.name).toBe('vnet-a');
432
+ expect(serverGroupCommand.selectedVnetSubnets).toEqual(['subnet-new']);
433
+ expect(serverGroupCommand.selectedSubnet).toBeNull();
434
+ expect(serverGroupCommand.subnet).toBeNull();
435
+ });
436
+
437
+ it('uses refreshed VNet metadata for existing VNet selections', async () => {
438
+ const serverGroupCommand = command({
439
+ loadBalancerName: null,
440
+ loadBalancerType: null,
441
+ selectedVnet: { name: 'vnet-a', resourceGroup: 'rg-a', subnets: [{ name: 'old-subnet', devices: [] }] },
442
+ selectedSubnet: 'old-subnet',
443
+ subnet: 'old-subnet',
444
+ selectedVnetSubnets: ['old-subnet'],
445
+ backingData: { loadBalancers: [], filtered: { loadBalancers: [] } },
446
+ });
447
+ runtimeServices.loadBalancerReader = {
448
+ getLoadBalancerDetails: jasmine.createSpy('getLoadBalancerDetails'),
449
+ };
450
+ spyOn(NetworkReader, 'listNetworks').and.returnValue(
451
+ Promise.resolve({
452
+ azure: [
453
+ {
454
+ account: 'test',
455
+ name: 'vnet-a',
456
+ region: 'westus',
457
+ resourceGroup: 'rg-a',
458
+ subnets: [{ name: 'subnet-new', devices: [] }],
459
+ },
460
+ ],
461
+ } as any),
462
+ );
463
+
464
+ const page = loadBalancerPage(serverGroupCommand);
465
+ await page.loadVnetSubnets(null, null, 0);
466
+
467
+ expect(serverGroupCommand.selectedVnet.subnets).toEqual([{ name: 'subnet-new', devices: [] }]);
468
+ expect(serverGroupCommand.selectedVnetSubnets).toEqual(['subnet-new']);
469
+ expect(serverGroupCommand.selectedSubnet).toBeNull();
470
+ expect(serverGroupCommand.subnet).toBeNull();
471
+ });
472
+
473
+ it('loads VNet and subnet options on mount when no load balancer is selected', async () => {
474
+ const serverGroupCommand = command({
475
+ loadBalancerName: null,
476
+ loadBalancerType: null,
477
+ selectedVnet: null,
478
+ selectedVnetSubnets: [],
479
+ backingData: { loadBalancers: [], filtered: { loadBalancers: [] } },
480
+ });
481
+ runtimeServices.loadBalancerReader = {
482
+ getLoadBalancerDetails: jasmine.createSpy('getLoadBalancerDetails'),
483
+ };
484
+ spyOn(NetworkReader, 'listNetworks').and.returnValue(
485
+ Promise.resolve({
486
+ azure: [
487
+ {
488
+ account: 'test',
489
+ name: 'vnet-a',
490
+ region: 'westus',
491
+ resourceGroup: 'rg-a',
492
+ subnets: [{ name: 'subnet-a', devices: [] }],
493
+ },
494
+ ],
495
+ } as any),
496
+ );
497
+
498
+ const page = loadBalancerPage(serverGroupCommand);
499
+ page.componentDidMount();
500
+ await Promise.resolve();
501
+ await Promise.resolve();
502
+
503
+ expect(NetworkReader.listNetworks).toHaveBeenCalled();
504
+ expect(serverGroupCommand.allVnets).toEqual([
505
+ {
506
+ account: 'test',
507
+ name: 'vnet-a',
508
+ region: 'westus',
509
+ resourceGroup: 'rg-a',
510
+ subnets: [{ name: 'subnet-a', devices: [] }],
511
+ },
512
+ ]);
513
+ expect(serverGroupCommand.selectedVnetSubnets).toEqual(['subnet-a']);
514
+ });
515
+
516
+ it('requires virtual network and subnet selections when network settings are configured', () => {
517
+ const page = new ServerGroupNetworkSettings({ formik: formik(command()) } as any) as any;
518
+
519
+ expect(
520
+ page.validate({
521
+ loadBalancerType: 'Azure Load Balancer',
522
+ selectedSubnet: null,
523
+ selectedVnet: null,
524
+ subnet: null,
525
+ viewState: { networkSettingsConfigured: true },
526
+ }),
527
+ ).toEqual({
528
+ selectedSubnet: 'Subnet required.',
529
+ selectedVnet: 'Virtual network required.',
530
+ });
531
+
532
+ expect(
533
+ page.validate({
534
+ loadBalancerType: null,
535
+ selectedSubnet: null,
536
+ selectedVnet: null,
537
+ subnet: null,
538
+ viewState: { networkSettingsConfigured: true },
539
+ }),
540
+ ).toEqual({
541
+ selectedSubnet: 'Subnet required.',
542
+ selectedVnet: 'Virtual network required.',
543
+ });
544
+
545
+ expect(
546
+ page.validate({
547
+ loadBalancerType: 'Azure Load Balancer',
548
+ selectedVnet: null,
549
+ subnet: 'subnet-a',
550
+ vnet: 'vnet-a',
551
+ viewState: { networkSettingsConfigured: true },
552
+ }),
553
+ ).toEqual({ selectedVnet: 'Virtual network required.' });
554
+ });
555
+
556
+ it('requires subnet and load-balancer-supplied VNet for application gateway commands', () => {
557
+ const page = new ServerGroupNetworkSettings({ formik: formik(command()) } as any) as any;
558
+
559
+ expect(
560
+ page.validate({
561
+ loadBalancerType: 'Azure Application Gateway',
562
+ selectedSubnet: null,
563
+ selectedVnet: null,
564
+ subnet: null,
565
+ viewState: { networkSettingsConfigured: true },
566
+ }),
567
+ ).toEqual({
568
+ selectedSubnet: 'Subnet required.',
569
+ selectedVnet: 'Virtual network required.',
570
+ });
571
+
572
+ expect(
573
+ page.validate({
574
+ loadBalancerType: 'Azure Application Gateway',
575
+ selectedSubnet: 'subnet-a',
576
+ selectedVnet: { name: 'vnet-a', resourceGroup: 'rg-a' },
577
+ subnet: null,
578
+ viewState: { networkSettingsConfigured: true },
579
+ }),
580
+ ).toEqual({ selectedSubnet: 'Subnet required.' });
581
+
582
+ expect(
583
+ page.validate({
584
+ loadBalancerType: 'Azure Application Gateway',
585
+ selectedSubnet: 'subnet-a',
586
+ selectedVnet: { name: 'vnet-a', resourceGroup: 'rg-a' },
587
+ subnet: 'subnet-a',
588
+ viewState: { networkSettingsConfigured: true },
589
+ }),
590
+ ).toEqual({});
591
+ });
592
+
593
+ it('handles VNet and subnet loading failures without throwing', async () => {
594
+ const serverGroupCommand = command({
595
+ loadBalancerName: null,
596
+ loadBalancerType: null,
597
+ selectedVnet: null,
598
+ selectedVnetSubnets: [],
599
+ backingData: { loadBalancers: [], filtered: { loadBalancers: [] } },
600
+ });
601
+ runtimeServices.loadBalancerReader = {
602
+ getLoadBalancerDetails: jasmine.createSpy('getLoadBalancerDetails'),
603
+ };
604
+ spyOn(NetworkReader, 'listNetworks').and.returnValue(Promise.reject(new Error('boom')));
605
+
606
+ const page = loadBalancerPage(serverGroupCommand);
607
+
608
+ await expectAsync(page.loadVnetSubnets(null, null, 0)).toBeResolved();
609
+
610
+ expect(serverGroupCommand.selectedVnetSubnets).toEqual([]);
611
+ });
612
+
613
+ it('clears stale network selections when the load balancer changes', async () => {
614
+ const serverGroupCommand = command({
615
+ backingData: {
616
+ loadBalancers: [{ name: 'lb-b', loadBalancerType: 'APPLICATION_GATEWAY' }],
617
+ filtered: { loadBalancers: ['lb-b'] },
618
+ },
619
+ selectedVnet: { name: 'old-vnet', resourceGroup: 'old-rg' },
620
+ selectedVnetSubnets: ['old-subnet'],
621
+ selectedSubnet: 'old-subnet',
622
+ subnet: 'old-subnet',
623
+ vnet: 'old-vnet',
624
+ vnetResourceGroup: 'old-rg',
625
+ });
626
+ runtimeServices.loadBalancerReader = {
627
+ getLoadBalancerDetails: jasmine.createSpy('getLoadBalancerDetails'),
628
+ };
629
+ spyOn(NetworkReader, 'listNetworks').and.returnValue(Promise.reject(new Error('boom')));
630
+
631
+ const page = loadBalancerPage(serverGroupCommand);
632
+
633
+ await page.loadBalancerChanged('lb-b', true);
634
+
635
+ expect(serverGroupCommand.selectedVnet).toBeNull();
636
+ expect(serverGroupCommand.vnet).toBeNull();
637
+ expect(serverGroupCommand.vnetResourceGroup).toBeNull();
638
+ expect(serverGroupCommand.selectedSubnet).toBeNull();
639
+ expect(serverGroupCommand.subnet).toBeNull();
640
+ expect(serverGroupCommand.selectedVnetSubnets).toEqual([]);
641
+ });
642
+
643
+ it('uses the account and region matched load balancer metadata when names collide', async () => {
644
+ const serverGroupCommand = command({
645
+ loadBalancerName: null,
646
+ loadBalancerType: null,
647
+ selectedVnet: { name: 'vnet-a', resourceGroup: 'rg-a' },
648
+ selectedVnetSubnets: [],
649
+ backingData: {
650
+ loadBalancers: [
651
+ { name: 'lb-a', account: 'other', region: 'eastus', loadBalancerType: 'APPLICATION_GATEWAY' },
652
+ { name: 'lb-a', account: 'test', region: 'westus', loadBalancerType: 'LOAD_BALANCER' },
653
+ ],
654
+ filtered: { loadBalancers: ['lb-a'] },
655
+ },
656
+ });
657
+ runtimeServices.loadBalancerReader = {
658
+ getLoadBalancerDetails: () => Promise.resolve([]),
659
+ };
660
+ spyOn(NetworkReader, 'listNetworks').and.returnValue(
661
+ Promise.resolve({
662
+ azure: [
663
+ { account: 'test', name: 'vnet-a', region: 'westus', resourceGroup: 'rg-a', subnets: [{ name: 'subnet-a' }] },
664
+ ],
665
+ } as any),
666
+ );
667
+
668
+ const page = loadBalancerPage(serverGroupCommand);
669
+ await page.loadBalancerChanged('lb-a');
670
+
671
+ expect(serverGroupCommand.loadBalancerType).toBe('Azure Load Balancer');
672
+ expect(serverGroupCommand.selectedVnet.name).toBe('vnet-a');
673
+ });
674
+
675
+ it('ignores stale load balancer subnet responses', async () => {
676
+ const serverGroupCommand = command({
677
+ loadBalancerName: null,
678
+ loadBalancerType: null,
679
+ selectedVnet: null,
680
+ selectedVnetSubnets: [],
681
+ backingData: {
682
+ loadBalancers: [
683
+ { name: 'lb-a', loadBalancerType: 'APPLICATION_GATEWAY' },
684
+ { name: 'lb-b', loadBalancerType: 'APPLICATION_GATEWAY' },
685
+ ],
686
+ filtered: { loadBalancers: ['lb-a', 'lb-b'] },
687
+ },
688
+ });
689
+ let resolveFirstRequest: (details: any[]) => void;
690
+ runtimeServices.loadBalancerReader = {
691
+ getLoadBalancerDetails: (_provider: string, _account: string, _region: string, loadBalancerName: string) => {
692
+ if (loadBalancerName === 'lb-a') {
693
+ return new Promise((resolve) => {
694
+ resolveFirstRequest = resolve;
695
+ });
696
+ }
697
+ return Promise.resolve([{ vnet: 'vnet-b' }]);
698
+ },
699
+ };
700
+ spyOn(NetworkReader, 'listNetworks').and.returnValue(
701
+ Promise.resolve({
702
+ azure: [
703
+ { account: 'test', name: 'vnet-a', region: 'westus', resourceGroup: 'rg-a', subnets: [{ name: 'subnet-a' }] },
704
+ { account: 'test', name: 'vnet-b', region: 'westus', resourceGroup: 'rg-b', subnets: [{ name: 'subnet-b' }] },
705
+ ],
706
+ } as any),
707
+ );
708
+
709
+ const page = loadBalancerPage(serverGroupCommand);
710
+ const firstRequest = page.loadBalancerChanged('lb-a');
711
+ const secondRequest = page.loadBalancerChanged('lb-b');
712
+ await secondRequest;
713
+ resolveFirstRequest([{ vnet: 'vnet-a' }]);
714
+ await firstRequest;
715
+
716
+ expect(serverGroupCommand.loadBalancerName).toBe('lb-b');
717
+ expect(serverGroupCommand.selectedVnet.name).toBe('vnet-b');
718
+ expect(serverGroupCommand.selectedVnetSubnets).toEqual(['subnet-b']);
719
+ });
720
+
721
+ it('renders health settings protocol, port, and HTTP path fields', () => {
722
+ const wrapper = shallow(
723
+ <ServerGroupHealthSettings
724
+ formik={formik({ healthSettings: { protocol: 'http', port: '80', requestPath: '/health' } })}
725
+ />,
726
+ );
727
+
728
+ expect(wrapper.find('select').exists()).toBe(true);
729
+ expect(wrapper.find('input[value="80"]').exists()).toBe(true);
730
+ expect(wrapper.find('input[value="/health"]').exists()).toBe(true);
731
+ });
732
+
733
+ it('adds Azure data disks with the legacy defaults', () => {
734
+ const serverGroupCommand = command({
735
+ dataDisks: [],
736
+ backingData: { dataDiskTypes: ['Standard_LRS'], dataDiskCachingTypes: ['None'], filtered: {} },
737
+ });
738
+ const page = new ServerGroupAdvancedSettings({ formik: formik(serverGroupCommand) } as any) as any;
739
+
740
+ page.addDataDisk();
741
+
742
+ expect(serverGroupCommand.dataDisks).toEqual([
743
+ {
744
+ lun: 0,
745
+ managedDisk: { storageAccountType: 'Standard_LRS' },
746
+ diskSizeGB: 1,
747
+ caching: 'None',
748
+ createOption: 'Empty',
749
+ },
750
+ ]);
751
+ });
752
+
753
+ it('uses the shared map editor for Azure tags', () => {
754
+ const wrapper = shallow(<ServerGroupTags formik={formik({ instanceTags: { team: 'cd' } })} />);
755
+
756
+ expect(wrapper.find(MapEditor).exists()).toBe(true);
757
+ });
758
+ });