@spinnaker/google 2026.3.2 → 2026.4.1

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 (108) hide show
  1. package/dist/autoscalingPolicy/GceAutoScalingFieldLayout.d.ts +2 -2
  2. package/dist/domain/loadBalancer.d.ts +6 -3
  3. package/dist/image/ImageSelect.d.ts +1 -1
  4. package/dist/index.js +159 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/loadBalancer/configure/common/gceLoadBalancerModels.d.ts +13 -2
  7. package/dist/loadBalancer/configure/http/GceHttpLoadBalancerEditor.d.ts +3 -0
  8. package/dist/loadBalancer/configure/http/GceHttpLoadBalancerModal.d.ts +1 -1
  9. package/dist/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerEditor.d.ts +30 -0
  10. package/dist/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerModal.d.ts +53 -0
  11. package/dist/loadBalancer/httpLoadBalancerUtils.service.d.ts +3 -1
  12. package/dist/serverGroup/configure/wizard/GceCloneServerGroupModal.d.ts +3 -0
  13. package/dist/serverGroup/configure/wizard/GceServerGroupWizard.types.d.ts +1 -0
  14. package/dist/serverGroup/configure/wizard/customInstance/CustomInstanceConfigurer.d.ts +1 -1
  15. package/dist/serverGroup/configure/wizard/customInstance/GceCustomInstanceBuilder.d.ts +2 -2
  16. package/dist/serverGroup/configure/wizard/pages/AdvancedSettings.d.ts +3 -0
  17. package/dist/serverGroup/configure/wizard/pages/GceServerGroupLoadBalancers.d.ts +1 -0
  18. package/dist/serverGroup/configure/wizard/pages/Policies.d.ts +21 -3
  19. package/dist/serverGroup/configure/wizard/pages/ServerGroupBasicSettings.d.ts +1 -0
  20. package/dist/serverGroup/configure/wizard/pages/ServerGroupCapacity.d.ts +1 -0
  21. package/dist/serverGroup/configure/wizard/pages/ServerGroupImageSettings.d.ts +1 -0
  22. package/dist/serverGroup/configure/wizard/pages/ServerGroupInstanceType.d.ts +1 -0
  23. package/dist/serverGroup/configure/wizard/zones/GceInstanceFlexibilityConfigurer.d.ts +2 -2
  24. package/dist/serverGroup/details/GceInstanceFlexibilityPolicyDetails.d.ts +2 -2
  25. package/dist/serverGroup/details/stateful/MarkDiskStatefulButton.d.ts +2 -2
  26. package/dist/serverGroup/details/stateful/UpdateBootImageButton.d.ts +2 -2
  27. package/package.json +5 -5
  28. package/src/autoHealingPolicy/GceAutoHealingPolicyEditor.spec.tsx +68 -52
  29. package/src/autoscalingPolicy/GceAutoscalingPolicyEditor.spec.tsx +43 -50
  30. package/src/autoscalingPolicy/GceAutoscalingPolicyEditor.tsx +1 -1
  31. package/src/autoscalingPolicy/GceAutoscalingPolicyWriter.spec.ts +1 -1
  32. package/src/domain/loadBalancer.ts +6 -3
  33. package/src/gce.module.spec.ts +132 -153
  34. package/src/instance/details/GceInstanceDetails.spec.tsx +86 -83
  35. package/src/loadBalancer/configure/choice/GceLoadBalancerChoiceModal.spec.tsx +162 -121
  36. package/src/loadBalancer/configure/choice/GceLoadBalancerChoiceModal.tsx +13 -1
  37. package/src/loadBalancer/configure/common/GceProxyLoadBalancerEditor.spec.tsx +47 -30
  38. package/src/loadBalancer/configure/common/GceProxyLoadBalancerEditor.tsx +2 -1
  39. package/src/loadBalancer/configure/common/GceProxyLoadBalancerModal.spec.tsx +85 -57
  40. package/src/loadBalancer/configure/common/gceLoadBalancerData.spec.ts +17 -16
  41. package/src/loadBalancer/configure/common/gceLoadBalancerModels.spec.ts +230 -5
  42. package/src/loadBalancer/configure/common/gceLoadBalancerModels.ts +186 -22
  43. package/src/loadBalancer/configure/common/gceLoadBalancerSubmission.spec.ts +298 -2
  44. package/src/loadBalancer/configure/common/gceLoadBalancerSubmission.ts +35 -5
  45. package/src/loadBalancer/configure/common/useGceLoadBalancerData.spec.tsx +37 -46
  46. package/src/loadBalancer/configure/http/GceHttpLoadBalancerEditor.spec.tsx +399 -58
  47. package/src/loadBalancer/configure/http/GceHttpLoadBalancerEditor.tsx +148 -41
  48. package/src/loadBalancer/configure/http/GceHttpLoadBalancerListenerEditor.spec.tsx +169 -36
  49. package/src/loadBalancer/configure/http/GceHttpLoadBalancerListenerEditor.tsx +80 -12
  50. package/src/loadBalancer/configure/http/GceHttpLoadBalancerModal.spec.tsx +224 -124
  51. package/src/loadBalancer/configure/http/GceHttpLoadBalancerModal.tsx +8 -2
  52. package/src/loadBalancer/configure/http/GceHttpLoadBalancerResourceEditors.spec.tsx +57 -22
  53. package/src/loadBalancer/configure/http/GceHttpLoadBalancerResourceEditors.tsx +21 -0
  54. package/src/loadBalancer/configure/http/GceHttpLoadBalancerRoutingEditor.spec.tsx +24 -20
  55. package/src/loadBalancer/configure/network/GceNetworkLoadBalancerEditor.spec.tsx +41 -32
  56. package/src/loadBalancer/configure/network/GceNetworkLoadBalancerModal.spec.tsx +35 -30
  57. package/src/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerEditor.spec.tsx +443 -0
  58. package/src/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerEditor.tsx +401 -0
  59. package/src/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerModal.spec.tsx +330 -0
  60. package/src/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerModal.tsx +348 -0
  61. package/src/loadBalancer/details/gceLoadBalancerDetails.spec.tsx +439 -35
  62. package/src/loadBalancer/details/gceLoadBalancerDetails.tsx +104 -21
  63. package/src/loadBalancer/httpLoadBalancerUtils.service.spec.ts +126 -0
  64. package/src/loadBalancer/httpLoadBalancerUtils.service.ts +27 -11
  65. package/src/loadBalancer/loadBalancer.setTransformer.spec.ts +165 -0
  66. package/src/loadBalancer/loadBalancer.setTransformer.ts +15 -2
  67. package/src/pipeline/stages/googleStageConfig.spec.js +22 -16
  68. package/src/securityGroup/configure/GceSecurityGroupModal.spec.tsx +177 -193
  69. package/src/securityGroup/configure/GceSecurityGroupModal.tsx +14 -2
  70. package/src/securityGroup/details/GceSecurityGroupDetails.spec.tsx +69 -78
  71. package/src/serverGroup/configure/serverGroupCommandBuilder.service.spec.js +35 -15
  72. package/src/serverGroup/configure/serverGroupConfiguration.service.js +7 -1
  73. package/src/serverGroup/configure/serverGroupConfiguration.service.spec.js +74 -4
  74. package/src/serverGroup/configure/wizard/GceCloneServerGroupModal.spec.tsx +593 -855
  75. package/src/serverGroup/configure/wizard/GceCloneServerGroupModal.tsx +120 -38
  76. package/src/serverGroup/configure/wizard/GceServerGroupWizard.types.ts +1 -0
  77. package/src/serverGroup/configure/wizard/GceServerGroupWizardPage.spec.tsx +53 -50
  78. package/src/serverGroup/configure/wizard/customInstance/GceCustomInstanceBuilder.spec.tsx +65 -49
  79. package/src/serverGroup/configure/wizard/hiddenMetadataKeys.value.js +1 -0
  80. package/src/serverGroup/configure/wizard/pages/AdvancedSettings.spec.tsx +101 -108
  81. package/src/serverGroup/configure/wizard/pages/AdvancedSettings.tsx +32 -28
  82. package/src/serverGroup/configure/wizard/pages/GceServerGroupFirewalls.spec.tsx +60 -59
  83. package/src/serverGroup/configure/wizard/pages/GceServerGroupLoadBalancers.spec.tsx +240 -114
  84. package/src/serverGroup/configure/wizard/pages/GceServerGroupLoadBalancers.tsx +152 -63
  85. package/src/serverGroup/configure/wizard/pages/Policies.spec.tsx +93 -48
  86. package/src/serverGroup/configure/wizard/pages/Policies.tsx +80 -55
  87. package/src/serverGroup/configure/wizard/pages/ServerGroupBasicSettings.spec.tsx +123 -94
  88. package/src/serverGroup/configure/wizard/pages/ServerGroupBasicSettings.tsx +24 -18
  89. package/src/serverGroup/configure/wizard/pages/ServerGroupCapacity.spec.tsx +152 -133
  90. package/src/serverGroup/configure/wizard/pages/ServerGroupCapacity.tsx +40 -37
  91. package/src/serverGroup/configure/wizard/pages/ServerGroupImageSettings.spec.tsx +66 -60
  92. package/src/serverGroup/configure/wizard/pages/ServerGroupImageSettings.tsx +8 -2
  93. package/src/serverGroup/configure/wizard/pages/ServerGroupInstanceType.spec.tsx +131 -101
  94. package/src/serverGroup/configure/wizard/pages/ServerGroupInstanceType.tsx +29 -28
  95. package/src/serverGroup/configure/wizard/zones/GceInstanceFlexibilityConfigurer.spec.tsx +104 -81
  96. package/src/serverGroup/details/GceInstanceFlexibilityPolicyDetails.spec.tsx +10 -11
  97. package/src/serverGroup/details/autoHealingPolicy/GceAutoHealingPolicyDetails.spec.tsx +51 -59
  98. package/src/serverGroup/details/autoHealingPolicy/modal/GceUpsertAutoHealingPolicyModal.spec.tsx +3 -3
  99. package/src/serverGroup/details/autoscalingPolicy/GceAutoscalingPolicyDetails.spec.tsx +49 -58
  100. package/src/serverGroup/details/autoscalingPolicy/modal/GceUpsertAutoscalingPolicyModal.spec.tsx +3 -3
  101. package/src/serverGroup/details/gceServerGroupDetails.spec.tsx +162 -141
  102. package/src/serverGroup/details/resize/GceResizeServerGroupModal.spec.tsx +102 -86
  103. package/src/serverGroup/details/resize/GceResizeServerGroupModal.tsx +4 -0
  104. package/src/serverGroup/details/rollback/GceRollbackServerGroupModal.spec.tsx +40 -31
  105. package/src/serverGroup/serverGroup.transformer.js +1 -0
  106. package/src/serverGroup/serverGroup.transformer.spec.js +48 -0
  107. package/src/validation/ApplicationNameValidator.spec.ts +1 -1
  108. /package/dist/{gce.logo2ae85d8704b1d6b8.png → gce.logoChI3w_A5.png} +0 -0
@@ -1,7 +1,7 @@
1
- import { shallow } from 'enzyme';
1
+ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
2
2
  import React from 'react';
3
3
 
4
- import { PlatformHealthOverride, ReactModal, TaskReason, UserVerification, ValidationMessage } from '@spinnaker/core';
4
+ import { AccountService, ReactModal } from '@spinnaker/core';
5
5
 
6
6
  import {
7
7
  buildGceAutoscalerResizeRequest,
@@ -10,7 +10,23 @@ import {
10
10
  validateGceResizeValues,
11
11
  } from './GceResizeServerGroupModal';
12
12
 
13
+ vi.mock('@spinnaker/core', async (importOriginal) => {
14
+ const actual = await importOriginal<typeof import('@spinnaker/core')>();
15
+ const ReactModule = await import('react');
16
+ return {
17
+ ...actual,
18
+ TaskMonitorWrapper: ({ monitor }: any) =>
19
+ ReactModule.createElement(
20
+ 'button',
21
+ { onClick: () => monitor.closeModal(), type: 'button' },
22
+ 'Close monitored task',
23
+ ),
24
+ };
25
+ });
26
+
13
27
  describe('GceResizeServerGroupModal', () => {
28
+ beforeEach(() => vi.spyOn(AccountService, 'challengeDestructiveActions').mockResolvedValue(true));
29
+
14
30
  it('rejects autoscaler minimum capacity greater than maximum capacity', () => {
15
31
  const serverGroup = {
16
32
  autoscalingPolicy: { minNumReplicas: 1, maxNumReplicas: 10 },
@@ -125,11 +141,11 @@ describe('GceResizeServerGroupModal', () => {
125
141
  });
126
142
 
127
143
  it('renders fixed-capacity mode for a server group without an autoscaler', () => {
128
- const wrapper = shallow(
144
+ render(
129
145
  <GceResizeServerGroupModal
130
- application={{ name: 'fnord', serverGroups: { refresh: jasmine.createSpy('refresh') } } as any}
131
- autoscalingPolicyWriter={{ upsertAutoscalingPolicy: jasmine.createSpy('upsertAutoscalingPolicy') } as any}
132
- dismissModal={jasmine.createSpy('dismissModal')}
146
+ application={{ name: 'fnord', serverGroups: { refresh: vi.fn() } } as any}
147
+ autoscalingPolicyWriter={{ upsertAutoscalingPolicy: vi.fn() } as any}
148
+ dismissModal={vi.fn()}
133
149
  serverGroup={
134
150
  {
135
151
  account: 'prod',
@@ -138,20 +154,20 @@ describe('GceResizeServerGroupModal', () => {
138
154
  region: 'us-central1',
139
155
  } as any
140
156
  }
141
- serverGroupWriter={{ resizeServerGroup: jasmine.createSpy('resizeServerGroup') } as any}
157
+ serverGroupWriter={{ resizeServerGroup: vi.fn() } as any}
142
158
  />,
143
159
  );
144
160
 
145
- expect(wrapper.find('input[name="newSize"]').exists()).toBe(true);
146
- expect(wrapper.find('p').text()).toBe('Sets desired instance count for this server group.');
161
+ expect(screen.getByRole('spinbutton', { name: 'Resize to' })).toBeInTheDocument();
162
+ expect(screen.getByText('Sets desired instance count for this server group.')).toBeInTheDocument();
147
163
  });
148
164
 
149
165
  it('renders min/max mode for a server group with an autoscaler', () => {
150
- const wrapper = shallow(
166
+ render(
151
167
  <GceResizeServerGroupModal
152
- application={{ name: 'fnord', serverGroups: { refresh: jasmine.createSpy('refresh') } } as any}
153
- autoscalingPolicyWriter={{ upsertAutoscalingPolicy: jasmine.createSpy('upsertAutoscalingPolicy') } as any}
154
- dismissModal={jasmine.createSpy('dismissModal')}
168
+ application={{ name: 'fnord', serverGroups: { refresh: vi.fn() } } as any}
169
+ autoscalingPolicyWriter={{ upsertAutoscalingPolicy: vi.fn() } as any}
170
+ dismissModal={vi.fn()}
155
171
  serverGroup={
156
172
  {
157
173
  account: 'prod',
@@ -160,27 +176,27 @@ describe('GceResizeServerGroupModal', () => {
160
176
  region: 'us-central1',
161
177
  } as any
162
178
  }
163
- serverGroupWriter={{ resizeServerGroup: jasmine.createSpy('resizeServerGroup') } as any}
179
+ serverGroupWriter={{ resizeServerGroup: vi.fn() } as any}
164
180
  />,
165
181
  );
166
182
 
167
- expect(wrapper.find('input[name="newMinNumReplicas"]').exists()).toBe(true);
168
- expect(wrapper.find('input[name="newMaxNumReplicas"]').exists()).toBe(true);
169
- expect(wrapper.find('input[name="newSize"]').exists()).toBe(false);
183
+ expect(screen.getByRole('spinbutton', { name: 'Minimum replicas' })).toBeInTheDocument();
184
+ expect(screen.getByRole('spinbutton', { name: 'Maximum replicas' })).toBeInTheDocument();
185
+ expect(screen.queryByRole('spinbutton', { name: 'Resize to' })).not.toBeInTheDocument();
170
186
  });
171
187
 
172
188
  it('renders reason, account verification, and the Google platform-health override', () => {
173
- const wrapper = shallow(
189
+ render(
174
190
  <GceResizeServerGroupModal
175
191
  application={
176
192
  {
177
193
  attributes: { platformHealthOnly: true, platformHealthOnlyShowOverride: true },
178
194
  name: 'fnord',
179
- serverGroups: { refresh: jasmine.createSpy('refresh') },
195
+ serverGroups: { refresh: vi.fn() },
180
196
  } as any
181
197
  }
182
- autoscalingPolicyWriter={{ upsertAutoscalingPolicy: jasmine.createSpy('upsertAutoscalingPolicy') } as any}
183
- dismissModal={jasmine.createSpy('dismissModal')}
198
+ autoscalingPolicyWriter={{ upsertAutoscalingPolicy: vi.fn() } as any}
199
+ dismissModal={vi.fn()}
184
200
  serverGroup={
185
201
  {
186
202
  account: 'prod',
@@ -189,20 +205,20 @@ describe('GceResizeServerGroupModal', () => {
189
205
  region: 'us-central1',
190
206
  } as any
191
207
  }
192
- serverGroupWriter={{ resizeServerGroup: jasmine.createSpy('resizeServerGroup') } as any}
208
+ serverGroupWriter={{ resizeServerGroup: vi.fn() } as any}
193
209
  />,
194
210
  );
195
211
 
196
- expect(wrapper.find(TaskReason).exists()).toBe(true);
197
- expect(wrapper.find(UserVerification).prop('account')).toBe('prod');
198
- expect(wrapper.find(PlatformHealthOverride).prop('interestingHealthProviderNames')).toEqual(['Google']);
212
+ expect(screen.getByRole('textbox', { name: 'Reason' })).toBeInTheDocument();
213
+ expect(screen.getByText(/Type the name of the account/)).toHaveTextContent('prod');
214
+ expect(screen.getByRole('checkbox', { name: 'Consider only Google health' })).toBeChecked();
199
215
  });
200
216
 
201
- it('submits fixed capacity through the server group writer after verification', () => {
217
+ it('submits fixed capacity through the server group writer after verification', async () => {
202
218
  const application = {
203
219
  attributes: { platformHealthOnly: true, platformHealthOnlyShowOverride: true },
204
220
  name: 'fnord',
205
- serverGroups: { refresh: jasmine.createSpy('refresh') },
221
+ serverGroups: { refresh: vi.fn() },
206
222
  } as any;
207
223
  const serverGroup = {
208
224
  account: 'prod',
@@ -210,24 +226,23 @@ describe('GceResizeServerGroupModal', () => {
210
226
  name: 'fnord-main-v004',
211
227
  region: 'us-central1',
212
228
  } as any;
213
- const resizeServerGroup = jasmine.createSpy('resizeServerGroup').and.returnValue(Promise.resolve({}));
214
- const wrapper = shallow(
229
+ const resizeServerGroup = vi.fn().mockReturnValue(new Promise(() => undefined));
230
+ render(
215
231
  <GceResizeServerGroupModal
216
232
  application={application}
217
- autoscalingPolicyWriter={{ upsertAutoscalingPolicy: jasmine.createSpy('upsertAutoscalingPolicy') } as any}
218
- dismissModal={jasmine.createSpy('dismissModal')}
233
+ autoscalingPolicyWriter={{ upsertAutoscalingPolicy: vi.fn() } as any}
234
+ dismissModal={vi.fn()}
219
235
  serverGroup={serverGroup}
220
236
  serverGroupWriter={{ resizeServerGroup }}
221
237
  />,
222
238
  );
223
239
 
224
- expect(wrapper.find('button.btn-primary').prop('disabled')).toBe(true);
225
- wrapper.find('input[name="newSize"]').simulate('change', { target: { value: '6' } });
226
- wrapper.find(TaskReason).prop('onChange')('capacity adjustment');
227
- wrapper.find(UserVerification).prop('onValidChange')(true);
228
- const monitor = (wrapper.state() as any).taskMonitor;
229
- spyOn(monitor, 'submit').and.callFake((submitMethod: () => PromiseLike<any>) => submitMethod());
230
- wrapper.find('button.btn-primary').simulate('click');
240
+ expect(screen.getByRole('button', { name: 'Submit' })).toBeDisabled();
241
+ fireEvent.change(screen.getByRole('spinbutton', { name: 'Resize to' }), { target: { value: '6' } });
242
+ fireEvent.change(screen.getByRole('textbox', { name: 'Reason' }), { target: { value: 'capacity adjustment' } });
243
+ fireEvent.change(screen.getByRole('textbox', { name: 'Confirm account prod' }), { target: { value: 'prod' } });
244
+ await waitFor(() => expect(screen.getByRole('button', { name: 'Submit' })).toBeEnabled());
245
+ fireEvent.click(screen.getByRole('button', { name: 'Submit' }));
231
246
 
232
247
  expect(resizeServerGroup).toHaveBeenCalledWith(serverGroup, application, {
233
248
  capacity: { desired: 6, max: 6, min: 6 },
@@ -239,11 +254,11 @@ describe('GceResizeServerGroupModal', () => {
239
254
  });
240
255
  });
241
256
 
242
- it('submits autoscaler bounds through the injected policy writer after verification', () => {
257
+ it('submits autoscaler bounds through the injected policy writer after verification', async () => {
243
258
  const application = {
244
259
  attributes: { platformHealthOnly: true, platformHealthOnlyShowOverride: true },
245
260
  name: 'fnord',
246
- serverGroups: { refresh: jasmine.createSpy('refresh') },
261
+ serverGroups: { refresh: vi.fn() },
247
262
  } as any;
248
263
  const serverGroup = {
249
264
  account: 'prod',
@@ -251,25 +266,26 @@ describe('GceResizeServerGroupModal', () => {
251
266
  name: 'fnord-main-v004',
252
267
  region: 'us-central1',
253
268
  } as any;
254
- const upsertAutoscalingPolicy = jasmine.createSpy('upsertAutoscalingPolicy').and.returnValue(Promise.resolve({}));
255
- const resizeServerGroup = jasmine.createSpy('resizeServerGroup');
256
- const wrapper = shallow(
269
+ const upsertAutoscalingPolicy = vi.fn().mockReturnValue(new Promise(() => undefined));
270
+ const resizeServerGroup = vi.fn();
271
+ render(
257
272
  <GceResizeServerGroupModal
258
273
  application={application}
259
274
  autoscalingPolicyWriter={{ upsertAutoscalingPolicy }}
260
- dismissModal={jasmine.createSpy('dismissModal')}
275
+ dismissModal={vi.fn()}
261
276
  serverGroup={serverGroup}
262
277
  serverGroupWriter={{ resizeServerGroup } as any}
263
278
  />,
264
279
  );
265
280
 
266
- wrapper.find('input[name="newMinNumReplicas"]').simulate('change', { target: { value: '3' } });
267
- wrapper.find('input[name="newMaxNumReplicas"]').simulate('change', { target: { value: '12' } });
268
- wrapper.find(TaskReason).prop('onChange')('raise autoscaling ceiling');
269
- wrapper.find(UserVerification).prop('onValidChange')(true);
270
- const monitor = (wrapper.state() as any).taskMonitor;
271
- spyOn(monitor, 'submit').and.callFake((submitMethod: () => PromiseLike<any>) => submitMethod());
272
- wrapper.find('button.btn-primary').simulate('click');
281
+ fireEvent.change(screen.getByRole('spinbutton', { name: 'Minimum replicas' }), { target: { value: '3' } });
282
+ fireEvent.change(screen.getByRole('spinbutton', { name: 'Maximum replicas' }), { target: { value: '12' } });
283
+ fireEvent.change(screen.getByRole('textbox', { name: 'Reason' }), {
284
+ target: { value: 'raise autoscaling ceiling' },
285
+ });
286
+ fireEvent.change(screen.getByRole('textbox', { name: 'Confirm account prod' }), { target: { value: 'prod' } });
287
+ await waitFor(() => expect(screen.getByRole('button', { name: 'Submit' })).toBeEnabled());
288
+ fireEvent.click(screen.getByRole('button', { name: 'Submit' }));
273
289
 
274
290
  expect(resizeServerGroup).not.toHaveBeenCalled();
275
291
  expect(upsertAutoscalingPolicy).toHaveBeenCalledWith(
@@ -284,11 +300,11 @@ describe('GceResizeServerGroupModal', () => {
284
300
  });
285
301
 
286
302
  it('surfaces invalid autoscaler min/max validation', () => {
287
- const wrapper = shallow(
303
+ render(
288
304
  <GceResizeServerGroupModal
289
- application={{ name: 'fnord', serverGroups: { refresh: jasmine.createSpy('refresh') } } as any}
290
- autoscalingPolicyWriter={{ upsertAutoscalingPolicy: jasmine.createSpy('upsertAutoscalingPolicy') } as any}
291
- dismissModal={jasmine.createSpy('dismissModal')}
305
+ application={{ name: 'fnord', serverGroups: { refresh: vi.fn() } } as any}
306
+ autoscalingPolicyWriter={{ upsertAutoscalingPolicy: vi.fn() } as any}
307
+ dismissModal={vi.fn()}
292
308
  serverGroup={
293
309
  {
294
310
  account: 'prod',
@@ -297,24 +313,24 @@ describe('GceResizeServerGroupModal', () => {
297
313
  region: 'us-central1',
298
314
  } as any
299
315
  }
300
- serverGroupWriter={{ resizeServerGroup: jasmine.createSpy('resizeServerGroup') } as any}
316
+ serverGroupWriter={{ resizeServerGroup: vi.fn() } as any}
301
317
  />,
302
318
  );
303
319
 
304
- wrapper.find('input[name="newMinNumReplicas"]').simulate('change', { target: { value: '8' } });
305
- wrapper.find('input[name="newMaxNumReplicas"]').simulate('change', { target: { value: '4' } });
320
+ fireEvent.change(screen.getByRole('spinbutton', { name: 'Minimum replicas' }), { target: { value: '8' } });
321
+ fireEvent.change(screen.getByRole('spinbutton', { name: 'Maximum replicas' }), { target: { value: '4' } });
306
322
 
307
- expect(wrapper.find(ValidationMessage).prop('message')).toBe('Min cannot be larger than Max');
323
+ expect(screen.getByText('Min cannot be larger than Max')).toBeInTheDocument();
308
324
  });
309
325
 
310
326
  it('opens as a standalone React modal', () => {
311
327
  const props = {
312
328
  application: { name: 'fnord' },
313
- autoscalingPolicyWriter: { upsertAutoscalingPolicy: jasmine.createSpy('upsertAutoscalingPolicy') },
329
+ autoscalingPolicyWriter: { upsertAutoscalingPolicy: vi.fn() },
314
330
  serverGroup: { account: 'prod', name: 'fnord-main-v004', region: 'us-central1' },
315
- serverGroupWriter: { resizeServerGroup: jasmine.createSpy('resizeServerGroup') },
331
+ serverGroupWriter: { resizeServerGroup: vi.fn() },
316
332
  } as any;
317
- const show = spyOn(ReactModal, 'show').and.returnValue(Promise.resolve({}) as any);
333
+ const show = vi.spyOn(ReactModal, 'show').mockReturnValue(Promise.resolve({}) as any);
318
334
 
319
335
  GceResizeServerGroupModal.show(props);
320
336
 
@@ -322,11 +338,11 @@ describe('GceResizeServerGroupModal', () => {
322
338
  });
323
339
 
324
340
  it('keeps submit disabled when fixed capacity is cleared', () => {
325
- const wrapper = shallow(
341
+ render(
326
342
  <GceResizeServerGroupModal
327
- application={{ name: 'fnord', serverGroups: { refresh: jasmine.createSpy('refresh') } } as any}
328
- autoscalingPolicyWriter={{ upsertAutoscalingPolicy: jasmine.createSpy('upsertAutoscalingPolicy') } as any}
329
- dismissModal={jasmine.createSpy('dismissModal')}
343
+ application={{ name: 'fnord', serverGroups: { refresh: vi.fn() } } as any}
344
+ autoscalingPolicyWriter={{ upsertAutoscalingPolicy: vi.fn() } as any}
345
+ dismissModal={vi.fn()}
330
346
  serverGroup={
331
347
  {
332
348
  account: 'prod',
@@ -335,23 +351,23 @@ describe('GceResizeServerGroupModal', () => {
335
351
  region: 'us-central1',
336
352
  } as any
337
353
  }
338
- serverGroupWriter={{ resizeServerGroup: jasmine.createSpy('resizeServerGroup') } as any}
354
+ serverGroupWriter={{ resizeServerGroup: vi.fn() } as any}
339
355
  />,
340
356
  );
341
357
 
342
- wrapper.find('input[name="newSize"]').simulate('change', { target: { value: '6' } });
343
- wrapper.find('input[name="newSize"]').simulate('change', { target: { value: '' } });
344
- wrapper.find(UserVerification).prop('onValidChange')(true);
358
+ fireEvent.change(screen.getByRole('spinbutton', { name: 'Resize to' }), { target: { value: '6' } });
359
+ fireEvent.change(screen.getByRole('spinbutton', { name: 'Resize to' }), { target: { value: '' } });
360
+ fireEvent.change(screen.getByRole('textbox', { name: 'Confirm account prod' }), { target: { value: 'prod' } });
345
361
 
346
- expect(wrapper.find('button.btn-primary').prop('disabled')).toBe(true);
362
+ expect(screen.getByRole('button', { name: 'Submit' })).toBeDisabled();
347
363
  });
348
364
 
349
365
  it('requires autoscaler bounds after an input is cleared', () => {
350
- const wrapper = shallow(
366
+ render(
351
367
  <GceResizeServerGroupModal
352
- application={{ name: 'fnord', serverGroups: { refresh: jasmine.createSpy('refresh') } } as any}
353
- autoscalingPolicyWriter={{ upsertAutoscalingPolicy: jasmine.createSpy('upsertAutoscalingPolicy') } as any}
354
- dismissModal={jasmine.createSpy('dismissModal')}
368
+ application={{ name: 'fnord', serverGroups: { refresh: vi.fn() } } as any}
369
+ autoscalingPolicyWriter={{ upsertAutoscalingPolicy: vi.fn() } as any}
370
+ dismissModal={vi.fn()}
355
371
  serverGroup={
356
372
  {
357
373
  account: 'prod',
@@ -360,30 +376,30 @@ describe('GceResizeServerGroupModal', () => {
360
376
  region: 'us-central1',
361
377
  } as any
362
378
  }
363
- serverGroupWriter={{ resizeServerGroup: jasmine.createSpy('resizeServerGroup') } as any}
379
+ serverGroupWriter={{ resizeServerGroup: vi.fn() } as any}
364
380
  />,
365
381
  );
366
382
 
367
- wrapper.find('input[name="newMinNumReplicas"]').simulate('change', { target: { value: '3' } });
368
- wrapper.find('input[name="newMaxNumReplicas"]').simulate('change', { target: { value: '12' } });
369
- wrapper.find('input[name="newMinNumReplicas"]').simulate('change', { target: { value: '' } });
383
+ fireEvent.change(screen.getByRole('spinbutton', { name: 'Minimum replicas' }), { target: { value: '3' } });
384
+ fireEvent.change(screen.getByRole('spinbutton', { name: 'Maximum replicas' }), { target: { value: '12' } });
385
+ fireEvent.change(screen.getByRole('spinbutton', { name: 'Minimum replicas' }), { target: { value: '' } });
370
386
 
371
- expect(wrapper.find(ValidationMessage).prop('message')).toBe('Min is required');
387
+ expect(screen.getByText('Min is required')).toBeInTheDocument();
372
388
  });
373
389
 
374
390
  it('dismisses the modal from the task monitor', () => {
375
- const dismissModal = jasmine.createSpy('dismissModal');
376
- const wrapper = shallow(
391
+ const dismissModal = vi.fn();
392
+ render(
377
393
  <GceResizeServerGroupModal
378
- application={{ name: 'fnord', serverGroups: { refresh: jasmine.createSpy('refresh') } } as any}
379
- autoscalingPolicyWriter={{ upsertAutoscalingPolicy: jasmine.createSpy('upsertAutoscalingPolicy') } as any}
394
+ application={{ name: 'fnord', serverGroups: { refresh: vi.fn() } } as any}
395
+ autoscalingPolicyWriter={{ upsertAutoscalingPolicy: vi.fn() } as any}
380
396
  dismissModal={dismissModal}
381
397
  serverGroup={{ account: 'prod', name: 'fnord-main-v004', region: 'us-central1' } as any}
382
- serverGroupWriter={{ resizeServerGroup: jasmine.createSpy('resizeServerGroup') } as any}
398
+ serverGroupWriter={{ resizeServerGroup: vi.fn() } as any}
383
399
  />,
384
400
  );
385
401
 
386
- (wrapper.state() as any).taskMonitor.closeModal();
402
+ fireEvent.click(screen.getByRole('button', { name: 'Close monitored task' }));
387
403
 
388
404
  expect(dismissModal).toHaveBeenCalled();
389
405
  });
@@ -227,6 +227,7 @@ export class GceResizeServerGroupModal extends React.Component<
227
227
  <label className="col-md-3 sm-label-right">Current</label>
228
228
  <div className="col-md-2">
229
229
  <input
230
+ aria-label="Current minimum replicas"
230
231
  className="form-control input-sm"
231
232
  disabled={true}
232
233
  type="number"
@@ -235,6 +236,7 @@ export class GceResizeServerGroupModal extends React.Component<
235
236
  </div>
236
237
  <div className="col-md-2">
237
238
  <input
239
+ aria-label="Current maximum replicas"
238
240
  className="form-control input-sm"
239
241
  disabled={true}
240
242
  type="number"
@@ -246,6 +248,7 @@ export class GceResizeServerGroupModal extends React.Component<
246
248
  <label className="col-md-3 sm-label-right">Resize to</label>
247
249
  <div className="col-md-2">
248
250
  <input
251
+ aria-label="Minimum replicas"
249
252
  className="form-control input-sm"
250
253
  min={0}
251
254
  name="newMinNumReplicas"
@@ -263,6 +266,7 @@ export class GceResizeServerGroupModal extends React.Component<
263
266
  </div>
264
267
  <div className="col-md-2">
265
268
  <input
269
+ aria-label="Maximum replicas"
266
270
  className="form-control input-sm"
267
271
  min={0}
268
272
  name="newMaxNumReplicas"
@@ -1,7 +1,7 @@
1
- import { shallow } from 'enzyme';
1
+ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
2
2
  import React from 'react';
3
3
 
4
- import { PlatformHealthOverride, TaskReason, UserVerification } from '@spinnaker/core';
4
+ import { AccountService } from '@spinnaker/core';
5
5
 
6
6
  import {
7
7
  buildGceRollbackJob,
@@ -9,6 +9,20 @@ import {
9
9
  getGceRollbackCandidates,
10
10
  } from './GceRollbackServerGroupModal';
11
11
 
12
+ vi.mock('@spinnaker/core', async (importOriginal) => {
13
+ const actual = await importOriginal<typeof import('@spinnaker/core')>();
14
+ const ReactModule = await import('react');
15
+ return {
16
+ ...actual,
17
+ TaskMonitorWrapper: ({ monitor }: any) =>
18
+ ReactModule.createElement(
19
+ 'button',
20
+ { onClick: () => monitor.closeModal(), type: 'button' },
21
+ 'Close monitored task',
22
+ ),
23
+ };
24
+ });
25
+
12
26
  describe('GceRollbackServerGroupModal', () => {
13
27
  const application = { name: 'fnord' } as any;
14
28
  const serverGroup = {
@@ -19,6 +33,8 @@ describe('GceRollbackServerGroupModal', () => {
19
33
  region: 'us-central1',
20
34
  } as any;
21
35
 
36
+ beforeEach(() => vi.spyOn(AccountService, 'challengeDestructiveActions').mockResolvedValue(true));
37
+
22
38
  it('keeps only disabled candidates in the same application, cluster, account, and region', () => {
23
39
  const eligible = {
24
40
  account: 'prod',
@@ -78,7 +94,7 @@ describe('GceRollbackServerGroupModal', () => {
78
94
  });
79
95
 
80
96
  it('renders reason, account verification, and the Google platform-health override', () => {
81
- const wrapper = shallow(
97
+ render(
82
98
  <GceRollbackServerGroupModal
83
99
  application={
84
100
  {
@@ -86,28 +102,23 @@ describe('GceRollbackServerGroupModal', () => {
86
102
  name: 'fnord',
87
103
  } as any
88
104
  }
89
- dismissModal={jasmine.createSpy('dismissModal')}
105
+ dismissModal={vi.fn()}
90
106
  serverGroup={serverGroup}
91
107
  serverGroups={[]}
92
- serverGroupWriter={{ rollbackServerGroup: jasmine.createSpy('rollbackServerGroup') } as any}
108
+ serverGroupWriter={{ rollbackServerGroup: vi.fn() } as any}
93
109
  />,
94
110
  );
95
111
 
96
- expect(wrapper.find(TaskReason).exists()).toBe(true);
97
- expect(wrapper.find(UserVerification).prop('account')).toBe('prod');
98
- expect(wrapper.find(PlatformHealthOverride).props()).toEqual(
99
- jasmine.objectContaining({
100
- interestingHealthProviderNames: ['Google'],
101
- platformHealthType: 'Google',
102
- }),
103
- );
112
+ expect(screen.getByRole('textbox', { name: 'Reason' })).toBeInTheDocument();
113
+ expect(screen.getByText(/Type the name of the account/)).toHaveTextContent('prod');
114
+ expect(screen.getByRole('checkbox', { name: 'Consider only Google health' })).toBeChecked();
104
115
  });
105
116
 
106
- it('submits the exact rollback writer job only after account verification', () => {
117
+ it('submits the exact rollback writer job only after account verification', async () => {
107
118
  const applicationWithHealthOverride = {
108
119
  attributes: { platformHealthOnly: true, platformHealthOnlyShowOverride: true },
109
120
  name: 'fnord',
110
- serverGroups: { refresh: jasmine.createSpy('refresh') },
121
+ serverGroups: { refresh: vi.fn() },
111
122
  } as any;
112
123
  const candidate = {
113
124
  account: 'prod',
@@ -117,25 +128,23 @@ describe('GceRollbackServerGroupModal', () => {
117
128
  name: 'fnord-main-v003',
118
129
  region: 'us-central1',
119
130
  } as any;
120
- const rollbackServerGroup = jasmine.createSpy('rollbackServerGroup').and.returnValue(Promise.resolve({}));
121
- const wrapper = shallow(
131
+ const rollbackServerGroup = vi.fn().mockReturnValue(new Promise(() => undefined));
132
+ render(
122
133
  <GceRollbackServerGroupModal
123
134
  application={applicationWithHealthOverride}
124
- dismissModal={jasmine.createSpy('dismissModal')}
135
+ dismissModal={vi.fn()}
125
136
  serverGroup={serverGroup}
126
137
  serverGroups={[candidate]}
127
138
  serverGroupWriter={{ rollbackServerGroup }}
128
139
  />,
129
140
  );
130
- const monitor = (wrapper.state() as any).taskMonitor;
131
- spyOn(monitor, 'submit').and.callFake((submitMethod: () => PromiseLike<any>) => submitMethod());
132
-
133
- wrapper.find('select').simulate('change', { target: { value: candidate.name } });
134
- wrapper.find(TaskReason).prop('onChange')('bad release');
135
- expect(wrapper.find('button.btn-primary').prop('disabled')).toBe(true);
141
+ fireEvent.change(screen.getByRole('combobox', { name: 'Restore to' }), { target: { value: candidate.name } });
142
+ fireEvent.change(screen.getByRole('textbox', { name: 'Reason' }), { target: { value: 'bad release' } });
143
+ expect(screen.getByRole('button', { name: 'Submit' })).toBeDisabled();
136
144
 
137
- wrapper.find(UserVerification).prop('onValidChange')(true);
138
- wrapper.find('button.btn-primary').simulate('click');
145
+ fireEvent.change(screen.getByRole('textbox', { name: 'Confirm account prod' }), { target: { value: 'prod' } });
146
+ await waitFor(() => expect(screen.getByRole('button', { name: 'Submit' })).toBeEnabled());
147
+ fireEvent.click(screen.getByRole('button', { name: 'Submit' }));
139
148
 
140
149
  expect(rollbackServerGroup).toHaveBeenCalledWith(serverGroup, applicationWithHealthOverride, {
141
150
  interestingHealthProviderNames: ['Google'],
@@ -150,18 +159,18 @@ describe('GceRollbackServerGroupModal', () => {
150
159
  });
151
160
 
152
161
  it('dismisses the modal from the task monitor', () => {
153
- const dismissModal = jasmine.createSpy('dismissModal');
154
- const wrapper = shallow(
162
+ const dismissModal = vi.fn();
163
+ render(
155
164
  <GceRollbackServerGroupModal
156
- application={{ name: 'fnord', serverGroups: { refresh: jasmine.createSpy('refresh') } } as any}
165
+ application={{ name: 'fnord', serverGroups: { refresh: vi.fn() } } as any}
157
166
  dismissModal={dismissModal}
158
167
  serverGroup={serverGroup}
159
168
  serverGroups={[]}
160
- serverGroupWriter={{ rollbackServerGroup: jasmine.createSpy('rollbackServerGroup') } as any}
169
+ serverGroupWriter={{ rollbackServerGroup: vi.fn() } as any}
161
170
  />,
162
171
  );
163
172
 
164
- (wrapper.state() as any).taskMonitor.closeModal();
173
+ fireEvent.click(screen.getByRole('button', { name: 'Close monitored task' }));
165
174
 
166
175
  expect(dismissModal).toHaveBeenCalled();
167
176
  });
@@ -19,6 +19,7 @@ export class GceServerGroupTransformer {
19
19
  serverGroup.loadBalancers,
20
20
  serverGroup.account,
21
21
  application.getDataSource('loadBalancers').data,
22
+ serverGroup.region,
22
23
  );
23
24
  }
24
25
  return serverGroup;
@@ -47,6 +47,54 @@ describe('gceServerGroupTransformer', () => {
47
47
  expect(normalizedServerGroup.loadBalancers.includes('network-load-balancer')).toEqual(true);
48
48
  expect(normalizedServerGroup.loadBalancers.includes('internal-load-balancer')).toEqual(true);
49
49
  });
50
+
51
+ it('scopes EXTERNAL_MANAGED listener normalization by server group region', async function () {
52
+ app = {
53
+ getDataSource: () => ({
54
+ ready: () => Promise.resolve(),
55
+ data: [
56
+ {
57
+ account: 'my-google-account',
58
+ listeners: [{ name: 'external-listener' }],
59
+ loadBalancerType: 'EXTERNAL_MANAGED',
60
+ name: 'app-main (my-google-account/us-central1/EXTERNAL_MANAGED)',
61
+ provider: 'gce',
62
+ region: 'us-central1',
63
+ urlMapName: 'app-main',
64
+ },
65
+ {
66
+ account: 'my-google-account',
67
+ listeners: [{ name: 'external-listener' }],
68
+ loadBalancerType: 'EXTERNAL_MANAGED',
69
+ name: 'app-main (my-google-account/europe-west1/EXTERNAL_MANAGED)',
70
+ provider: 'gce',
71
+ region: 'europe-west1',
72
+ urlMapName: 'app-main',
73
+ },
74
+ ],
75
+ }),
76
+ };
77
+
78
+ const centralServerGroup = await transformer.normalizeServerGroup(
79
+ {
80
+ account: 'my-google-account',
81
+ region: 'us-central1',
82
+ loadBalancers: ['external-listener'],
83
+ },
84
+ app,
85
+ );
86
+ const europeServerGroup = await transformer.normalizeServerGroup(
87
+ {
88
+ account: 'my-google-account',
89
+ region: 'europe-west1',
90
+ loadBalancers: ['external-listener'],
91
+ },
92
+ app,
93
+ );
94
+
95
+ expect(centralServerGroup.loadBalancers).toEqual(['app-main (my-google-account/us-central1/EXTERNAL_MANAGED)']);
96
+ expect(europeServerGroup.loadBalancers).toEqual(['app-main (my-google-account/europe-west1/EXTERNAL_MANAGED)']);
97
+ });
50
98
  });
51
99
 
52
100
  describe('convert server group command to deploy configuration', () => {
@@ -32,7 +32,7 @@ describe('Google application name validator', () => {
32
32
  });
33
33
 
34
34
  it('is registered directly when the Google module is imported', async () => {
35
- spyOn(AccountService, 'listProviders').and.returnValue(Promise.resolve(['gce']));
35
+ vi.spyOn(AccountService, 'listProviders').mockReturnValue(Promise.resolve(['gce']));
36
36
 
37
37
  const result = await ApplicationNameValidator.validate('invalid-name', ['gce']);
38
38