@spinnaker/google 2026.3.1 → 2026.4.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 (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
@@ -6,7 +6,9 @@ import type {
6
6
  DeckRuntimeServices,
7
7
  IModalComponentProps,
8
8
  IRouterInjectedProps,
9
+ IServerGroupCommand,
9
10
  IStage,
11
+ ITask,
10
12
  IWizardPageInjectedProps,
11
13
  } from '@spinnaker/core';
12
14
  import {
@@ -124,7 +126,11 @@ function loadBalancerMetadataReference(name: string, loadBalancer: any): ILoadBa
124
126
  if (!loadBalancer) {
125
127
  return undefined;
126
128
  }
127
- if (loadBalancer.loadBalancerType === 'HTTP' || loadBalancer.loadBalancerType === 'INTERNAL_MANAGED') {
129
+ if (
130
+ loadBalancer.loadBalancerType === 'HTTP' ||
131
+ loadBalancer.loadBalancerType === 'INTERNAL_MANAGED' ||
132
+ loadBalancer.loadBalancerType === 'EXTERNAL_MANAGED'
133
+ ) {
128
134
  const names = (loadBalancer.listeners || []).map((listener: any) => listener.name).filter(Boolean);
129
135
  return names.length
130
136
  ? {
@@ -180,6 +186,24 @@ function buildLoadBalancerMetadata(command: IGceServerGroupCommand): Record<stri
180
186
  return compactMetadata(metadata);
181
187
  }
182
188
 
189
+ function getSubmittedLoadBalancerNames(command: IGceServerGroupCommand): string[] {
190
+ const loadBalancerIndex = command.backingData?.filtered?.loadBalancerIndex || {};
191
+ return Array.from(
192
+ new Set(
193
+ (command.loadBalancers || []).flatMap((selection: any) => {
194
+ const name = loadBalancerName(selection);
195
+ const loadBalancer = selection.loadBalancerType ? selection : loadBalancerIndex[name];
196
+ if (loadBalancer?.loadBalancerType === 'EXTERNAL_MANAGED') {
197
+ // Clouddriver attaches regional managed LBs by forwarding-rule listener name.
198
+ const listeners = (loadBalancer.listeners || []).map((listener: any) => listener.name).filter(Boolean);
199
+ return listeners.length ? listeners : [name];
200
+ }
201
+ return [name];
202
+ }),
203
+ ),
204
+ );
205
+ }
206
+
183
207
  export function transformGceServerGroupCommand(command: IGceServerGroupCommand): IGceServerGroupCommand {
184
208
  const transformed = cloneDeep(command);
185
209
  const instanceMetadata = { ...(command.instanceMetadata || {}) };
@@ -191,6 +215,7 @@ export function transformGceServerGroupCommand(command: IGceServerGroupCommand):
191
215
  ...instanceMetadata,
192
216
  ...buildLoadBalancerMetadata(command),
193
217
  };
218
+ transformed.loadBalancers = getSubmittedLoadBalancerNames(command);
194
219
  transformed.tags = (command.tags || []).map((tag: any) => tag.value || tag);
195
220
  transformed.targetSize = command.capacity?.desired;
196
221
  if (command.autoscalingPolicy) {
@@ -214,6 +239,54 @@ export function transformGceServerGroupCommand(command: IGceServerGroupCommand):
214
239
  return transformed;
215
240
  }
216
241
 
242
+ function submitGceServerGroupCommand(
243
+ command: IGceServerGroupCommand,
244
+ options: {
245
+ application: Application;
246
+ closeModal: (command: IGceServerGroupCommand) => unknown;
247
+ forPipelineConfig?: boolean;
248
+ serverGroupWriter: {
249
+ cloneServerGroup: (command: IServerGroupCommand, application: Application) => PromiseLike<ITask>;
250
+ };
251
+ taskMonitor: TaskMonitor;
252
+ },
253
+ ): unknown {
254
+ const transformed = transformGceServerGroupCommand(command);
255
+ const mode = transformed.viewState?.mode;
256
+ if (options.forPipelineConfig || mode === 'createPipeline' || mode === 'editPipeline') {
257
+ return options.closeModal(transformed);
258
+ }
259
+ return options.taskMonitor.submit(() =>
260
+ options.serverGroupWriter.cloneServerGroup((transformed as unknown) as IServerGroupCommand, options.application),
261
+ );
262
+ }
263
+
264
+ function getCreatedServerGroupNavigation(
265
+ command: IGceServerGroupCommand,
266
+ task: ITask,
267
+ includes: (state: string) => boolean,
268
+ ): { params: Record<string, string>; state: string } | undefined {
269
+ const cloneStage = task?.execution?.stages?.find((stage: IStage) => stage.type === 'cloneServerGroup');
270
+ const newServerGroupName = cloneStage?.context?.['deploy.server.groups']?.[command.region];
271
+ if (!newServerGroupName) {
272
+ return undefined;
273
+ }
274
+
275
+ let state = '^.^.^.clusters.serverGroup';
276
+ if (includes('**.clusters.serverGroup')) state = '^.serverGroup';
277
+ if (includes('**.clusters.cluster.serverGroup')) state = '^.^.serverGroup';
278
+ if (includes('**.clusters')) state = '.serverGroup';
279
+ return {
280
+ state,
281
+ params: {
282
+ accountId: command.credentials,
283
+ provider: 'gce',
284
+ region: command.region,
285
+ serverGroup: newServerGroupName,
286
+ },
287
+ };
288
+ }
289
+
217
290
  export class GceCloneServerGroupModalComponent extends React.Component<
218
291
  IGceCloneServerGroupModalProps & IRouterInjectedProps,
219
292
  IGceCloneServerGroupModalState
@@ -268,17 +341,35 @@ export class GceCloneServerGroupModalComponent extends React.Component<
268
341
  void this.configureCommand();
269
342
  }
270
343
 
344
+ public componentDidUpdate(previousProps: IGceCloneServerGroupModalProps & IRouterInjectedProps): void {
345
+ if (previousProps.command === this.props.command) {
346
+ return;
347
+ }
348
+
349
+ const command = cloneDeep(this.props.command);
350
+ this.command = command;
351
+ this.commandState.command = command;
352
+ this.commandState.formikValues = command;
353
+ this.formik?.setValues(command);
354
+ this.setState({
355
+ command,
356
+ initializationError: false,
357
+ loaded: Boolean(command.backingData?.filtered),
358
+ });
359
+ void this.configureCommand(command);
360
+ }
361
+
271
362
  public componentWillUnmount(): void {
272
363
  this.unmounted = true;
273
364
  this.configureRequest++;
274
365
  this.clearApplicationRefreshSubscription();
275
366
  }
276
367
 
277
- private configureCommand = async (): Promise<void> => {
368
+ private configureCommand = async (requestedCommand?: IGceServerGroupCommand): Promise<void> => {
278
369
  const request = ++this.configureRequest;
279
370
  const adapter = this.getAdapter();
280
371
  try {
281
- let command = this.formik?.values || this.command;
372
+ let command = requestedCommand || this.formik?.values || this.command;
282
373
  if (command.viewState?.requiresTemplateSelection) {
283
374
  const baseCommand = await adapter.buildNewServerGroupCommand(this.props.application, {
284
375
  mode: 'createPipeline',
@@ -295,10 +386,7 @@ export class GceCloneServerGroupModalComponent extends React.Component<
295
386
  }
296
387
 
297
388
  const latestCommand = this.formik?.values || command;
298
- const persistedSelections = snapshotPersistedSelections(latestCommand);
299
- const refreshedCommand = mergeRefreshedCommand(latestCommand, configured);
300
- initializeCommand(refreshedCommand, persistedSelections);
301
- restoreUnavailableSelections(refreshedCommand, persistedSelections);
389
+ const refreshedCommand = reconcileGceServerGroupCommand(latestCommand, configured);
302
390
  this.command = refreshedCommand;
303
391
  this.commandState.command = refreshedCommand;
304
392
  this.commandState.formikValues = refreshedCommand;
@@ -331,14 +419,13 @@ export class GceCloneServerGroupModalComponent extends React.Component<
331
419
  }
332
420
 
333
421
  private submit = (command: IGceServerGroupCommand = this.formik?.values || this.command): any => {
334
- const transformed = transformGceServerGroupCommand(command);
335
- const mode = transformed.viewState?.mode;
336
- if (this.props.forPipelineConfig || mode === 'createPipeline' || mode === 'editPipeline') {
337
- return this.props.closeModal(transformed);
338
- }
339
- return this.state.taskMonitor.submit(() =>
340
- this.context.services.serverGroupWriter.cloneServerGroup(transformed as any, this.props.application),
341
- );
422
+ return submitGceServerGroupCommand(command, {
423
+ application: this.props.application,
424
+ closeModal: this.props.closeModal,
425
+ forPipelineConfig: this.props.forPipelineConfig,
426
+ serverGroupWriter: this.context.services.serverGroupWriter,
427
+ taskMonitor: this.state.taskMonitor,
428
+ });
342
429
  };
343
430
 
344
431
  private onTaskComplete = (): void => {
@@ -353,31 +440,15 @@ export class GceCloneServerGroupModalComponent extends React.Component<
353
440
  return;
354
441
  }
355
442
 
356
- const command = this.formik?.values || this.command;
357
- const cloneStage = this.state.taskMonitor.task?.execution?.stages?.find(
358
- (stage: IStage) => stage.type === 'cloneServerGroup',
443
+ const navigation = getCreatedServerGroupNavigation(
444
+ this.formik?.values || this.command,
445
+ this.state.taskMonitor.task,
446
+ (state) => this.props.stateService.includes(state),
359
447
  );
360
- const newServerGroupName = cloneStage?.context?.['deploy.server.groups']?.[command.region];
361
- if (!newServerGroupName) {
448
+ if (!navigation) {
362
449
  return;
363
450
  }
364
-
365
- let transitionTo = '^.^.^.clusters.serverGroup';
366
- if (this.props.stateService.includes('**.clusters.serverGroup')) {
367
- transitionTo = '^.serverGroup';
368
- }
369
- if (this.props.stateService.includes('**.clusters.cluster.serverGroup')) {
370
- transitionTo = '^.^.serverGroup';
371
- }
372
- if (this.props.stateService.includes('**.clusters')) {
373
- transitionTo = '.serverGroup';
374
- }
375
- this.props.stateService.go(transitionTo, {
376
- accountId: command.credentials,
377
- provider: 'gce',
378
- region: command.region,
379
- serverGroup: newServerGroupName,
380
- });
451
+ this.props.stateService.go(navigation.state, navigation.params);
381
452
  };
382
453
 
383
454
  private clearApplicationRefreshSubscription = (): void => {
@@ -772,6 +843,17 @@ function uniqueByName(values: any[]): any[] {
772
843
  });
773
844
  }
774
845
 
846
+ export function reconcileGceServerGroupCommand(
847
+ persistedCommand: IGceServerGroupCommand,
848
+ configuredCommand: IGceServerGroupCommand,
849
+ ): IGceServerGroupCommand {
850
+ const persistedSelections = snapshotPersistedSelections(persistedCommand);
851
+ const refreshedCommand = mergeRefreshedCommand(persistedCommand, configuredCommand);
852
+ initializeCommand(refreshedCommand, persistedSelections);
853
+ restoreUnavailableSelections(refreshedCommand, persistedSelections);
854
+ return refreshedCommand;
855
+ }
856
+
775
857
  function mergeRefreshedCommand(
776
858
  current: IGceServerGroupCommand,
777
859
  configured: IGceServerGroupCommand,
@@ -796,7 +878,7 @@ function mergeRefreshedCommand(
796
878
  };
797
879
  }
798
880
 
799
- function initializePipelineCreateCommand(
881
+ export function initializePipelineCreateCommand(
800
882
  baseCommand: IGceServerGroupCommand,
801
883
  placeholder: IGceServerGroupCommand,
802
884
  ): IGceServerGroupCommand {
@@ -170,6 +170,7 @@ export interface IGceServerGroupCommandValidationErrors {
170
170
  distributionPolicy?: { zones?: string };
171
171
  freeFormDetails?: string;
172
172
  image?: string;
173
+ loadBalancers?: string;
173
174
  region?: string;
174
175
  stack?: string;
175
176
  zone?: string;
@@ -1,3 +1,4 @@
1
+ import type { Mock } from 'vitest';
1
2
  import type { FormikProps } from 'formik';
2
3
  import React from 'react';
3
4
  import { UIRouterReact } from '@uirouter/react';
@@ -106,17 +107,19 @@ describe('GceServerGroupWizard foundation', () => {
106
107
  it('uses runtime-owned readers through the registered default configuration path', async () => {
107
108
  registerGoogleProvider();
108
109
  const runtime = createDeckRuntime(new UIRouterReact());
109
- const getAllSecurityGroups = spyOn(runtime.services.securityGroupReader, 'getAllSecurityGroups').and.resolveTo(
110
- {},
111
- );
112
- const listLoadBalancers = spyOn(runtime.services.loadBalancerReader, 'listLoadBalancers').and.resolveTo([]);
113
- spyOn(AccountService, 'getCredentialsKeyedByAccount').and.resolveTo({});
114
- spyOn(AccountService, 'getAllAccountDetailsForProvider').and.resolveTo([]);
115
- spyOn(AccountService, 'listAccounts').and.resolveTo([]);
116
- spyOn(NetworkReader, 'listNetworksByProvider').and.resolveTo([]);
117
- spyOn(SubnetReader, 'listSubnetsByProvider').and.resolveTo([]);
118
- spyOn(GceImageReader, 'findImages').and.resolveTo([]);
119
- spyOn(GceHealthCheckReader.prototype, 'listHealthChecks').and.resolveTo([]);
110
+ const getAllSecurityGroups = vi
111
+ .spyOn(runtime.services.securityGroupReader, 'getAllSecurityGroups')
112
+ .mockResolvedValue({});
113
+ const listLoadBalancers = vi
114
+ .spyOn(runtime.services.loadBalancerReader, 'listLoadBalancers')
115
+ .mockResolvedValue([]);
116
+ vi.spyOn(AccountService, 'getCredentialsKeyedByAccount').mockResolvedValue({});
117
+ vi.spyOn(AccountService, 'getAllAccountDetailsForProvider').mockResolvedValue([]);
118
+ vi.spyOn(AccountService, 'listAccounts').mockResolvedValue([]);
119
+ vi.spyOn(NetworkReader, 'listNetworksByProvider').mockResolvedValue([]);
120
+ vi.spyOn(SubnetReader, 'listSubnetsByProvider').mockResolvedValue([]);
121
+ vi.spyOn(GceImageReader, 'findImages').mockResolvedValue([]);
122
+ vi.spyOn(GceHealthCheckReader.prototype, 'listHealthChecks').mockResolvedValue([]);
120
123
 
121
124
  try {
122
125
  const delegate = runtime.services.providerServiceDelegate;
@@ -131,7 +134,7 @@ describe('GceServerGroupWizard foundation', () => {
131
134
  );
132
135
 
133
136
  expect(getAllSecurityGroups).toHaveBeenCalledTimes(1);
134
- expect(listLoadBalancers).toHaveBeenCalledOnceWith('gce');
137
+ expect(listLoadBalancers).toHaveBeenCalledExactlyOnceWith('gce');
135
138
  } finally {
136
139
  runtime.dispose();
137
140
  }
@@ -139,10 +142,10 @@ describe('GceServerGroupWizard foundation', () => {
139
142
 
140
143
  it('delegates the four surviving command-builder signatures', async () => {
141
144
  const builder = {
142
- buildNewServerGroupCommand: jasmine.createSpy().and.resolveTo(command()),
143
- buildNewServerGroupCommandForPipeline: jasmine.createSpy().and.resolveTo(command()),
144
- buildServerGroupCommandFromExisting: jasmine.createSpy().and.resolveTo(command()),
145
- buildServerGroupCommandFromPipeline: jasmine.createSpy().and.resolveTo(command()),
145
+ buildNewServerGroupCommand: vi.fn().mockResolvedValue(command()),
146
+ buildNewServerGroupCommandForPipeline: vi.fn().mockResolvedValue(command()),
147
+ buildServerGroupCommandFromExisting: vi.fn().mockResolvedValue(command()),
148
+ buildServerGroupCommandFromPipeline: vi.fn().mockResolvedValue(command()),
146
149
  };
147
150
  const adapter = new GceServerGroupWizardAdapter(builder as any, configurationService());
148
151
  const app = { name: 'app' } as any;
@@ -165,7 +168,7 @@ describe('GceServerGroupWizard foundation', () => {
165
168
  it('configures a cloned command and leaves Formik values unchanged', async () => {
166
169
  const original = command({ backingData: undefined });
167
170
  const service = configurationService();
168
- service.configureCommand.and.callFake(async (_app: any, working: IGceServerGroupCommand) => {
171
+ service.configureCommand.mockImplementation(async (_app: any, working: IGceServerGroupCommand) => {
169
172
  working.backingData = { accounts: ['account'] };
170
173
  });
171
174
  const adapter = new GceServerGroupWizardAdapter(commandBuilder(), service);
@@ -178,13 +181,13 @@ describe('GceServerGroupWizard foundation', () => {
178
181
  });
179
182
 
180
183
  it('applies configuration updates immutably and processes dirty results', async () => {
181
- const processCommandUpdateResult = jasmine.createSpy('processCommandUpdateResult');
184
+ const processCommandUpdateResult = vi.fn();
182
185
  const original = command({
183
186
  processCommandUpdateResult,
184
187
  viewState: { mode: 'create', dirty: { existing: true } },
185
188
  });
186
189
  const service = configurationService();
187
- service.configureImages.and.callFake((working: IGceServerGroupCommand) => {
190
+ service.configureImages.mockImplementation((working: IGceServerGroupCommand) => {
188
191
  working.image = 'new-image';
189
192
  return { dirty: { image: true } };
190
193
  });
@@ -200,7 +203,7 @@ describe('GceServerGroupWizard foundation', () => {
200
203
  });
201
204
 
202
205
  it('invokes command handlers against a clone', async () => {
203
- const credentialsChanged = jasmine.createSpy().and.callFake((working: IGceServerGroupCommand) => {
206
+ const credentialsChanged = vi.fn().mockImplementation((working: IGceServerGroupCommand) => {
204
207
  working.region = 'new-region';
205
208
  return { dirty: { region: true } };
206
209
  });
@@ -211,21 +214,21 @@ describe('GceServerGroupWizard foundation', () => {
211
214
 
212
215
  expect(update.command.region).toBe('new-region');
213
216
  expect(original.region).toBe('region');
214
- expect(credentialsChanged.calls.mostRecent().args[0]).not.toBe(original);
217
+ expect(credentialsChanged.mock.lastCall[0]).not.toBe(original);
215
218
  });
216
219
 
217
220
  it('forwards refresh options while refreshing a cloned command', async () => {
218
221
  const original = command();
219
222
  const service = configurationService();
220
- service.refreshHealthChecks.and.callFake(async (working: IGceServerGroupCommand) => {
223
+ service.refreshHealthChecks.mockImplementation(async (working: IGceServerGroupCommand) => {
221
224
  working.backingData = { healthChecks: ['health-check'] };
222
225
  });
223
226
  const adapter = new GceServerGroupWizardAdapter(commandBuilder(), service);
224
227
 
225
228
  const update = await adapter.applyConfigurationRefresh(original, 'refreshHealthChecks', true);
226
229
 
227
- expect(service.refreshHealthChecks.calls.mostRecent().args[0]).not.toBe(original);
228
- expect(service.refreshHealthChecks.calls.mostRecent().args[1]).toBe(true);
230
+ expect(service.refreshHealthChecks.mock.lastCall[0]).not.toBe(original);
231
+ expect(service.refreshHealthChecks.mock.lastCall[1]).toBe(true);
229
232
  expect(update.command.backingData).toEqual({ healthChecks: ['health-check'] });
230
233
  expect(original.backingData).toBeUndefined();
231
234
  });
@@ -245,9 +248,9 @@ describe('GceServerGroupWizard foundation', () => {
245
248
  return nextCommand;
246
249
  });
247
250
 
248
- expect(setValues.calls.count()).toBe(2);
251
+ expect(setValues.mock.calls.length).toBe(2);
249
252
  expect(requestCommands[1]).toEqual(
250
- jasmine.objectContaining({
253
+ expect.objectContaining({
251
254
  backingData: { regions: ['first-region'] },
252
255
  region: 'first-region',
253
256
  zone: 'second-zone',
@@ -285,7 +288,7 @@ describe('GceServerGroupWizard foundation', () => {
285
288
  await olderRequest;
286
289
 
287
290
  expect(formik.values).toEqual(
288
- jasmine.objectContaining({
291
+ expect.objectContaining({
289
292
  backingData: { regions: ['newer'] },
290
293
  credentials: 'new-account',
291
294
  freeFormDetails: 'newer-user-edit',
@@ -323,7 +326,7 @@ describe('GceServerGroupWizard foundation', () => {
323
326
  await newerRequest;
324
327
 
325
328
  expect(formik.values).toEqual(
326
- jasmine.objectContaining({
329
+ expect.objectContaining({
327
330
  backingData: { regions: ['option-b'] },
328
331
  credentials: 'new-account',
329
332
  freeFormDetails: 'newer-user-edit',
@@ -344,9 +347,9 @@ describe('GceServerGroupWizard foundation', () => {
344
347
  first.resolve(command({ region: 'first' }));
345
348
  await firstRequest;
346
349
 
347
- expect(setValues.calls.count()).toBe(1);
348
- expect(setValues.calls.mostRecent().args[0].region).toBe('second');
349
- expect(onLoadingChanged.calls.allArgs()).toEqual([[true], [true], [false]]);
350
+ expect(setValues.mock.calls.length).toBe(1);
351
+ expect(setValues.mock.lastCall[0].region).toBe('second');
352
+ expect(onLoadingChanged.mock.calls).toEqual([[true], [true], [false]]);
350
353
  });
351
354
 
352
355
  it('does not publish a command after unmount', async () => {
@@ -382,8 +385,8 @@ class TestWizardPage extends GceServerGroupWizardPage {
382
385
  }
383
386
 
384
387
  function testPage(formik = asyncFormik(command()), commandState?: IGceServerGroupWizardCommandState) {
385
- const setValues = formik.setValues as jasmine.Spy;
386
- const onLoadingChanged = jasmine.createSpy('onLoadingChanged');
388
+ const setValues = formik.setValues as Mock;
389
+ const onLoadingChanged = vi.fn();
387
390
  const props = {
388
391
  app: { name: 'app' },
389
392
  commandState,
@@ -395,13 +398,13 @@ function testPage(formik = asyncFormik(command()), commandState?: IGceServerGrou
395
398
 
396
399
  function asyncFormik(values: IGceServerGroupCommand): FormikProps<IGceServerGroupCommand> {
397
400
  const formik = ({ values } as unknown) as FormikProps<IGceServerGroupCommand>;
398
- formik.setValues = jasmine.createSpy('setValues').and.resolveTo(undefined);
401
+ formik.setValues = vi.fn().mockResolvedValue(undefined);
399
402
  return formik;
400
403
  }
401
404
 
402
405
  function publishingFormik(values: IGceServerGroupCommand): FormikProps<IGceServerGroupCommand> {
403
406
  const formik = asyncFormik(values);
404
- (formik.setValues as jasmine.Spy).and.callFake((nextValues: IGceServerGroupCommand) => {
407
+ (formik.setValues as Mock).mockImplementation((nextValues: IGceServerGroupCommand) => {
405
408
  formik.values = nextValues;
406
409
  });
407
410
  return formik;
@@ -426,28 +429,28 @@ function command(overrides: Partial<IGceServerGroupCommand> = {}): IGceServerGro
426
429
 
427
430
  function commandBuilder() {
428
431
  return {
429
- buildNewServerGroupCommand: jasmine.createSpy().and.resolveTo(command()),
430
- buildNewServerGroupCommandForPipeline: jasmine.createSpy().and.resolveTo(command()),
431
- buildServerGroupCommandFromExisting: jasmine.createSpy().and.resolveTo(command()),
432
- buildServerGroupCommandFromPipeline: jasmine.createSpy().and.resolveTo(command()),
432
+ buildNewServerGroupCommand: vi.fn().mockResolvedValue(command()),
433
+ buildNewServerGroupCommandForPipeline: vi.fn().mockResolvedValue(command()),
434
+ buildServerGroupCommandFromExisting: vi.fn().mockResolvedValue(command()),
435
+ buildServerGroupCommandFromPipeline: vi.fn().mockResolvedValue(command()),
433
436
  };
434
437
  }
435
438
 
436
439
  function configurationService(): Record<
437
440
  GceConfigurationRefreshMethod | GceConfigurationUpdateMethod | 'configureCommand',
438
- jasmine.Spy
441
+ Mock
439
442
  > {
440
443
  return {
441
- configureCommand: jasmine.createSpy().and.resolveTo(undefined),
442
- configureImages: jasmine.createSpy().and.returnValue({ dirty: {} }),
443
- configureInstanceTypes: jasmine.createSpy().and.returnValue({ dirty: {} }),
444
- configureLoadBalancerOptions: jasmine.createSpy().and.returnValue({ dirty: {} }),
445
- configureSubnets: jasmine.createSpy().and.returnValue({ dirty: {} }),
446
- configureZones: jasmine.createSpy().and.returnValue({ dirty: {} }),
447
- refreshHealthChecks: jasmine.createSpy().and.resolveTo(undefined),
448
- refreshInstanceTypes: jasmine.createSpy().and.resolveTo(undefined),
449
- refreshLoadBalancers: jasmine.createSpy().and.resolveTo(undefined),
450
- refreshSecurityGroups: jasmine.createSpy().and.resolveTo(undefined),
444
+ configureCommand: vi.fn().mockResolvedValue(undefined),
445
+ configureImages: vi.fn().mockReturnValue({ dirty: {} }),
446
+ configureInstanceTypes: vi.fn().mockReturnValue({ dirty: {} }),
447
+ configureLoadBalancerOptions: vi.fn().mockReturnValue({ dirty: {} }),
448
+ configureSubnets: vi.fn().mockReturnValue({ dirty: {} }),
449
+ configureZones: vi.fn().mockReturnValue({ dirty: {} }),
450
+ refreshHealthChecks: vi.fn().mockResolvedValue(undefined),
451
+ refreshInstanceTypes: vi.fn().mockResolvedValue(undefined),
452
+ refreshLoadBalancers: vi.fn().mockResolvedValue(undefined),
453
+ refreshSecurityGroups: vi.fn().mockResolvedValue(undefined),
451
454
  };
452
455
  }
453
456