@spinnaker/google 2026.3.2 → 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.
- package/dist/autoscalingPolicy/GceAutoScalingFieldLayout.d.ts +2 -2
- package/dist/domain/loadBalancer.d.ts +6 -3
- package/dist/image/ImageSelect.d.ts +1 -1
- package/dist/index.js +159 -1
- package/dist/index.js.map +1 -1
- package/dist/loadBalancer/configure/common/gceLoadBalancerModels.d.ts +13 -2
- package/dist/loadBalancer/configure/http/GceHttpLoadBalancerEditor.d.ts +3 -0
- package/dist/loadBalancer/configure/http/GceHttpLoadBalancerModal.d.ts +1 -1
- package/dist/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerEditor.d.ts +30 -0
- package/dist/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerModal.d.ts +53 -0
- package/dist/loadBalancer/httpLoadBalancerUtils.service.d.ts +3 -1
- package/dist/serverGroup/configure/wizard/GceCloneServerGroupModal.d.ts +3 -0
- package/dist/serverGroup/configure/wizard/GceServerGroupWizard.types.d.ts +1 -0
- package/dist/serverGroup/configure/wizard/customInstance/CustomInstanceConfigurer.d.ts +1 -1
- package/dist/serverGroup/configure/wizard/customInstance/GceCustomInstanceBuilder.d.ts +2 -2
- package/dist/serverGroup/configure/wizard/pages/AdvancedSettings.d.ts +3 -0
- package/dist/serverGroup/configure/wizard/pages/GceServerGroupLoadBalancers.d.ts +1 -0
- package/dist/serverGroup/configure/wizard/pages/Policies.d.ts +21 -3
- package/dist/serverGroup/configure/wizard/pages/ServerGroupBasicSettings.d.ts +1 -0
- package/dist/serverGroup/configure/wizard/pages/ServerGroupCapacity.d.ts +1 -0
- package/dist/serverGroup/configure/wizard/pages/ServerGroupImageSettings.d.ts +1 -0
- package/dist/serverGroup/configure/wizard/pages/ServerGroupInstanceType.d.ts +1 -0
- package/dist/serverGroup/configure/wizard/zones/GceInstanceFlexibilityConfigurer.d.ts +2 -2
- package/dist/serverGroup/details/GceInstanceFlexibilityPolicyDetails.d.ts +2 -2
- package/dist/serverGroup/details/stateful/MarkDiskStatefulButton.d.ts +2 -2
- package/dist/serverGroup/details/stateful/UpdateBootImageButton.d.ts +2 -2
- package/package.json +5 -5
- package/src/autoHealingPolicy/GceAutoHealingPolicyEditor.spec.tsx +68 -52
- package/src/autoscalingPolicy/GceAutoscalingPolicyEditor.spec.tsx +43 -50
- package/src/autoscalingPolicy/GceAutoscalingPolicyEditor.tsx +1 -1
- package/src/autoscalingPolicy/GceAutoscalingPolicyWriter.spec.ts +1 -1
- package/src/domain/loadBalancer.ts +6 -3
- package/src/gce.module.spec.ts +132 -153
- package/src/instance/details/GceInstanceDetails.spec.tsx +86 -83
- package/src/loadBalancer/configure/choice/GceLoadBalancerChoiceModal.spec.tsx +162 -121
- package/src/loadBalancer/configure/choice/GceLoadBalancerChoiceModal.tsx +13 -1
- package/src/loadBalancer/configure/common/GceProxyLoadBalancerEditor.spec.tsx +47 -30
- package/src/loadBalancer/configure/common/GceProxyLoadBalancerEditor.tsx +2 -1
- package/src/loadBalancer/configure/common/GceProxyLoadBalancerModal.spec.tsx +85 -57
- package/src/loadBalancer/configure/common/gceLoadBalancerData.spec.ts +17 -16
- package/src/loadBalancer/configure/common/gceLoadBalancerModels.spec.ts +230 -5
- package/src/loadBalancer/configure/common/gceLoadBalancerModels.ts +186 -22
- package/src/loadBalancer/configure/common/gceLoadBalancerSubmission.spec.ts +298 -2
- package/src/loadBalancer/configure/common/gceLoadBalancerSubmission.ts +35 -5
- package/src/loadBalancer/configure/common/useGceLoadBalancerData.spec.tsx +37 -46
- package/src/loadBalancer/configure/http/GceHttpLoadBalancerEditor.spec.tsx +399 -58
- package/src/loadBalancer/configure/http/GceHttpLoadBalancerEditor.tsx +148 -41
- package/src/loadBalancer/configure/http/GceHttpLoadBalancerListenerEditor.spec.tsx +169 -36
- package/src/loadBalancer/configure/http/GceHttpLoadBalancerListenerEditor.tsx +80 -12
- package/src/loadBalancer/configure/http/GceHttpLoadBalancerModal.spec.tsx +224 -124
- package/src/loadBalancer/configure/http/GceHttpLoadBalancerModal.tsx +8 -2
- package/src/loadBalancer/configure/http/GceHttpLoadBalancerResourceEditors.spec.tsx +57 -22
- package/src/loadBalancer/configure/http/GceHttpLoadBalancerResourceEditors.tsx +21 -0
- package/src/loadBalancer/configure/http/GceHttpLoadBalancerRoutingEditor.spec.tsx +24 -20
- package/src/loadBalancer/configure/network/GceNetworkLoadBalancerEditor.spec.tsx +41 -32
- package/src/loadBalancer/configure/network/GceNetworkLoadBalancerModal.spec.tsx +35 -30
- package/src/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerEditor.spec.tsx +443 -0
- package/src/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerEditor.tsx +401 -0
- package/src/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerModal.spec.tsx +330 -0
- package/src/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerModal.tsx +348 -0
- package/src/loadBalancer/details/gceLoadBalancerDetails.spec.tsx +439 -35
- package/src/loadBalancer/details/gceLoadBalancerDetails.tsx +104 -21
- package/src/loadBalancer/httpLoadBalancerUtils.service.spec.ts +126 -0
- package/src/loadBalancer/httpLoadBalancerUtils.service.ts +27 -11
- package/src/loadBalancer/loadBalancer.setTransformer.spec.ts +165 -0
- package/src/loadBalancer/loadBalancer.setTransformer.ts +15 -2
- package/src/pipeline/stages/googleStageConfig.spec.js +22 -16
- package/src/securityGroup/configure/GceSecurityGroupModal.spec.tsx +177 -193
- package/src/securityGroup/configure/GceSecurityGroupModal.tsx +14 -2
- package/src/securityGroup/details/GceSecurityGroupDetails.spec.tsx +69 -78
- package/src/serverGroup/configure/serverGroupCommandBuilder.service.spec.js +35 -15
- package/src/serverGroup/configure/serverGroupConfiguration.service.js +7 -1
- package/src/serverGroup/configure/serverGroupConfiguration.service.spec.js +74 -4
- package/src/serverGroup/configure/wizard/GceCloneServerGroupModal.spec.tsx +593 -855
- package/src/serverGroup/configure/wizard/GceCloneServerGroupModal.tsx +120 -38
- package/src/serverGroup/configure/wizard/GceServerGroupWizard.types.ts +1 -0
- package/src/serverGroup/configure/wizard/GceServerGroupWizardPage.spec.tsx +53 -50
- package/src/serverGroup/configure/wizard/customInstance/GceCustomInstanceBuilder.spec.tsx +65 -49
- package/src/serverGroup/configure/wizard/hiddenMetadataKeys.value.js +1 -0
- package/src/serverGroup/configure/wizard/pages/AdvancedSettings.spec.tsx +101 -108
- package/src/serverGroup/configure/wizard/pages/AdvancedSettings.tsx +32 -28
- package/src/serverGroup/configure/wizard/pages/GceServerGroupFirewalls.spec.tsx +60 -59
- package/src/serverGroup/configure/wizard/pages/GceServerGroupLoadBalancers.spec.tsx +240 -114
- package/src/serverGroup/configure/wizard/pages/GceServerGroupLoadBalancers.tsx +152 -63
- package/src/serverGroup/configure/wizard/pages/Policies.spec.tsx +93 -48
- package/src/serverGroup/configure/wizard/pages/Policies.tsx +80 -55
- package/src/serverGroup/configure/wizard/pages/ServerGroupBasicSettings.spec.tsx +123 -94
- package/src/serverGroup/configure/wizard/pages/ServerGroupBasicSettings.tsx +24 -18
- package/src/serverGroup/configure/wizard/pages/ServerGroupCapacity.spec.tsx +152 -133
- package/src/serverGroup/configure/wizard/pages/ServerGroupCapacity.tsx +40 -37
- package/src/serverGroup/configure/wizard/pages/ServerGroupImageSettings.spec.tsx +66 -60
- package/src/serverGroup/configure/wizard/pages/ServerGroupImageSettings.tsx +8 -2
- package/src/serverGroup/configure/wizard/pages/ServerGroupInstanceType.spec.tsx +131 -101
- package/src/serverGroup/configure/wizard/pages/ServerGroupInstanceType.tsx +29 -28
- package/src/serverGroup/configure/wizard/zones/GceInstanceFlexibilityConfigurer.spec.tsx +104 -81
- package/src/serverGroup/details/GceInstanceFlexibilityPolicyDetails.spec.tsx +10 -11
- package/src/serverGroup/details/autoHealingPolicy/GceAutoHealingPolicyDetails.spec.tsx +51 -59
- package/src/serverGroup/details/autoHealingPolicy/modal/GceUpsertAutoHealingPolicyModal.spec.tsx +3 -3
- package/src/serverGroup/details/autoscalingPolicy/GceAutoscalingPolicyDetails.spec.tsx +49 -58
- package/src/serverGroup/details/autoscalingPolicy/modal/GceUpsertAutoscalingPolicyModal.spec.tsx +3 -3
- package/src/serverGroup/details/gceServerGroupDetails.spec.tsx +162 -141
- package/src/serverGroup/details/resize/GceResizeServerGroupModal.spec.tsx +102 -86
- package/src/serverGroup/details/resize/GceResizeServerGroupModal.tsx +4 -0
- package/src/serverGroup/details/rollback/GceRollbackServerGroupModal.spec.tsx +40 -31
- package/src/serverGroup/serverGroup.transformer.js +1 -0
- package/src/serverGroup/serverGroup.transformer.spec.js +48 -0
- package/src/validation/ApplicationNameValidator.spec.ts +1 -1
- /package/dist/{gce.logo2ae85d8704b1d6b8.png → gce.logoChI3w_A5.png} +0 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Mock } from 'vitest';
|
|
2
2
|
import type { FormikProps } from 'formik';
|
|
3
3
|
import React from 'react';
|
|
4
|
+
import { fireEvent, render } from '@testing-library/react';
|
|
5
|
+
import type { RenderResult } from '@testing-library/react';
|
|
4
6
|
|
|
5
7
|
import { DeckRuntimeContext } from '@spinnaker/core';
|
|
6
8
|
|
|
@@ -9,21 +11,16 @@ import type {
|
|
|
9
11
|
IGceServerGroupWizardAdapter,
|
|
10
12
|
IGceServerGroupWizardPageProps,
|
|
11
13
|
} from '../GceServerGroupWizard.types';
|
|
12
|
-
import { ServerGroupInstanceType } from './ServerGroupInstanceType';
|
|
14
|
+
import { ServerGroupInstanceType, validateGceServerGroupInstanceType } from './ServerGroupInstanceType';
|
|
13
15
|
|
|
14
16
|
describe('ServerGroupInstanceType', () => {
|
|
15
17
|
let runtimeServices: any;
|
|
16
|
-
const
|
|
17
|
-
<DeckRuntimeContext.Provider value={{ services: runtimeServices } as any}>{children}</DeckRuntimeContext.Provider>
|
|
18
|
-
);
|
|
19
|
-
const shallow = (component: React.ReactElement) => enzymeMount(component, { wrappingComponent: RuntimeWrapper });
|
|
18
|
+
const renderPage = (component: React.ReactElement) => renderWithRuntime(component, runtimeServices);
|
|
20
19
|
|
|
21
20
|
beforeEach(() => {
|
|
22
21
|
runtimeServices = {
|
|
23
22
|
instanceTypeService: {
|
|
24
|
-
getInstanceTypeDetails:
|
|
25
|
-
.createSpy('getInstanceTypeDetails')
|
|
26
|
-
.and.returnValue(new Promise(() => undefined)),
|
|
23
|
+
getInstanceTypeDetails: vi.fn().mockReturnValue(new Promise(() => undefined)),
|
|
27
24
|
},
|
|
28
25
|
};
|
|
29
26
|
});
|
|
@@ -35,9 +32,9 @@ describe('ServerGroupInstanceType', () => {
|
|
|
35
32
|
acceleratorConfigs: [{ acceleratorType: 'retired-gpu', acceleratorCount: 3 }],
|
|
36
33
|
});
|
|
37
34
|
|
|
38
|
-
const wrapper =
|
|
35
|
+
const wrapper = renderPage(<ServerGroupInstanceType {...props(values)} />);
|
|
39
36
|
|
|
40
|
-
expect(wrapper.
|
|
37
|
+
expect(wrapper.getByText(/Machine type/, { selector: 'label' })).toHaveAttribute('for', 'gce-machine-type');
|
|
41
38
|
expect(optionText(wrapper, '#gce-machine-type')).toContain('retired-machine (Unavailable)');
|
|
42
39
|
expect(optionText(wrapper, '#gce-min-cpu-platform')).toContain('Retired CPU (Unavailable)');
|
|
43
40
|
expect(optionText(wrapper, '#gce-accelerator-type-0')).toContain('retired-gpu (Unavailable)');
|
|
@@ -46,19 +43,19 @@ describe('ServerGroupInstanceType', () => {
|
|
|
46
43
|
|
|
47
44
|
it('selects standard machine types without mutating current Formik values', () => {
|
|
48
45
|
runtimeServices.instanceTypeService = {
|
|
49
|
-
getInstanceTypeDetails:
|
|
46
|
+
getInstanceTypeDetails: vi.fn().mockReturnValue(new Promise(() => undefined)),
|
|
50
47
|
};
|
|
51
48
|
const values = command({ instanceType: 'n1-standard-1' });
|
|
52
49
|
const testProps = props(values);
|
|
53
|
-
const wrapper =
|
|
50
|
+
const wrapper = renderPage(<ServerGroupInstanceType {...testProps} />);
|
|
54
51
|
|
|
55
|
-
|
|
52
|
+
fireEvent.change(control(wrapper, '#gce-machine-type'), { target: { value: 'n2-standard-2' } });
|
|
56
53
|
|
|
57
54
|
expect(values.instanceType).toBe('n1-standard-1');
|
|
58
55
|
expect(testProps.formik.setValues).toHaveBeenCalledWith(
|
|
59
|
-
|
|
56
|
+
expect.objectContaining({
|
|
60
57
|
instanceType: 'n2-standard-2',
|
|
61
|
-
viewState:
|
|
58
|
+
viewState: expect.objectContaining({ instanceProfile: 'custom' }),
|
|
62
59
|
}),
|
|
63
60
|
);
|
|
64
61
|
});
|
|
@@ -70,7 +67,7 @@ describe('ServerGroupInstanceType', () => {
|
|
|
70
67
|
].forEach(({ description, instanceType }) => {
|
|
71
68
|
it(`publishes pipeline ${description} machine types without concrete type reconciliation`, () => {
|
|
72
69
|
const instanceTypeService = {
|
|
73
|
-
getInstanceTypeDetails:
|
|
70
|
+
getInstanceTypeDetails: vi.fn(),
|
|
74
71
|
};
|
|
75
72
|
runtimeServices.instanceTypeService = instanceTypeService;
|
|
76
73
|
const values = command({
|
|
@@ -78,11 +75,11 @@ describe('ServerGroupInstanceType', () => {
|
|
|
78
75
|
});
|
|
79
76
|
const adapter = adapterForHandlers();
|
|
80
77
|
const testProps = props(values, adapter);
|
|
81
|
-
const wrapper =
|
|
78
|
+
const wrapper = renderPage(<ServerGroupInstanceType {...testProps} />);
|
|
82
79
|
|
|
83
|
-
|
|
80
|
+
fireEvent.change(control(wrapper, '#gce-machine-type'), { target: { value: instanceType } });
|
|
84
81
|
|
|
85
|
-
expect(testProps.formik.setValues).toHaveBeenCalledWith(
|
|
82
|
+
expect(testProps.formik.setValues).toHaveBeenCalledWith(expect.objectContaining({ instanceType }));
|
|
86
83
|
expect(instanceTypeService.getInstanceTypeDetails).not.toHaveBeenCalled();
|
|
87
84
|
expect(adapter.applyCommandHandler).not.toHaveBeenCalled();
|
|
88
85
|
});
|
|
@@ -91,7 +88,7 @@ describe('ServerGroupInstanceType', () => {
|
|
|
91
88
|
it('loads standard machine type details and reconciles storage and accelerator capabilities', async () => {
|
|
92
89
|
const detailsRequest = deferred<any>();
|
|
93
90
|
const instanceTypeService = {
|
|
94
|
-
getInstanceTypeDetails:
|
|
91
|
+
getInstanceTypeDetails: vi.fn().mockReturnValue(detailsRequest.promise),
|
|
95
92
|
};
|
|
96
93
|
runtimeServices.instanceTypeService = instanceTypeService;
|
|
97
94
|
const values = command({
|
|
@@ -108,7 +105,7 @@ describe('ServerGroupInstanceType', () => {
|
|
|
108
105
|
},
|
|
109
106
|
});
|
|
110
107
|
const adapter = adapterForHandlers();
|
|
111
|
-
(adapter.applyCommandHandler as
|
|
108
|
+
(adapter.applyCommandHandler as Mock).mockImplementation(async (next, handler) => ({
|
|
112
109
|
command: {
|
|
113
110
|
...next,
|
|
114
111
|
acceleratorConfigs: [],
|
|
@@ -121,9 +118,9 @@ describe('ServerGroupInstanceType', () => {
|
|
|
121
118
|
result: { dirty: {} },
|
|
122
119
|
}));
|
|
123
120
|
const testProps = props(values, adapter);
|
|
124
|
-
const wrapper =
|
|
121
|
+
const wrapper = renderPage(<ServerGroupInstanceType {...testProps} />);
|
|
125
122
|
|
|
126
|
-
|
|
123
|
+
fireEvent.change(control(wrapper, '#gce-machine-type'), { target: { value: 'n2-standard-2' } });
|
|
127
124
|
|
|
128
125
|
expect(instanceTypeService.getInstanceTypeDetails).toHaveBeenCalledWith('gce', 'n2-standard-2');
|
|
129
126
|
detailsRequest.resolve({
|
|
@@ -136,16 +133,16 @@ describe('ServerGroupInstanceType', () => {
|
|
|
136
133
|
await settle();
|
|
137
134
|
|
|
138
135
|
expect(adapter.applyCommandHandler).toHaveBeenCalledWith(
|
|
139
|
-
|
|
136
|
+
expect.objectContaining({
|
|
140
137
|
instanceType: 'n2-standard-2',
|
|
141
138
|
disks: [{ type: 'pd-ssd', sizeGb: 20 }],
|
|
142
|
-
viewState:
|
|
143
|
-
instanceTypeDetails:
|
|
139
|
+
viewState: expect.objectContaining({
|
|
140
|
+
instanceTypeDetails: expect.objectContaining({ name: 'n2-standard-2' }),
|
|
144
141
|
}),
|
|
145
142
|
}),
|
|
146
143
|
'zoneChanged',
|
|
147
144
|
);
|
|
148
|
-
const published = (testProps.formik.setValues as
|
|
145
|
+
const published = (testProps.formik.setValues as Mock).mock.lastCall[0];
|
|
149
146
|
expect(published.disks).toEqual([{ type: 'pd-ssd', sizeGb: 20 }]);
|
|
150
147
|
expect(published.viewState.overriddenStorageDescription).toBeUndefined();
|
|
151
148
|
expect(published.viewState.instanceTypeDetails.storage.localSSDSupported).toBe(false);
|
|
@@ -157,9 +154,9 @@ describe('ServerGroupInstanceType', () => {
|
|
|
157
154
|
const oldRequest = deferred<any>();
|
|
158
155
|
const newRequest = deferred<any>();
|
|
159
156
|
const instanceTypeService = {
|
|
160
|
-
getInstanceTypeDetails:
|
|
161
|
-
.
|
|
162
|
-
.
|
|
157
|
+
getInstanceTypeDetails: vi
|
|
158
|
+
.fn()
|
|
159
|
+
.mockImplementation((_provider, instanceType) =>
|
|
163
160
|
instanceType === 'n2-standard-2' ? oldRequest.promise : newRequest.promise,
|
|
164
161
|
),
|
|
165
162
|
};
|
|
@@ -167,18 +164,18 @@ describe('ServerGroupInstanceType', () => {
|
|
|
167
164
|
const values = command();
|
|
168
165
|
const adapter = adapterForHandlers();
|
|
169
166
|
const testProps = props(values, adapter);
|
|
170
|
-
const wrapper =
|
|
167
|
+
const wrapper = renderPage(<ServerGroupInstanceType {...testProps} />);
|
|
171
168
|
|
|
172
|
-
|
|
173
|
-
setFormikValues(wrapper, testProps, (testProps.formik.setValues as
|
|
174
|
-
|
|
169
|
+
fireEvent.change(control(wrapper, '#gce-machine-type'), { target: { value: 'n2-standard-2' } });
|
|
170
|
+
setFormikValues(wrapper, testProps, (testProps.formik.setValues as Mock).mock.lastCall[0], runtimeServices);
|
|
171
|
+
fireEvent.change(control(wrapper, '#gce-machine-type'), { target: { value: 'n1-standard-1' } });
|
|
175
172
|
|
|
176
173
|
newRequest.resolve(instanceTypeDetails('n1-standard-1', 30));
|
|
177
174
|
await settle();
|
|
178
175
|
oldRequest.resolve(instanceTypeDetails('n2-standard-2', 40));
|
|
179
176
|
await settle();
|
|
180
177
|
|
|
181
|
-
const published = (testProps.formik.setValues as
|
|
178
|
+
const published = (testProps.formik.setValues as Mock).mock.lastCall[0];
|
|
182
179
|
expect(published.instanceType).toBe('n1-standard-1');
|
|
183
180
|
expect(published.viewState.instanceTypeDetails.name).toBe('n1-standard-1');
|
|
184
181
|
expect(published.disks).toEqual([{ type: 'pd-ssd', sizeGb: 30 }]);
|
|
@@ -188,48 +185,51 @@ describe('ServerGroupInstanceType', () => {
|
|
|
188
185
|
it('ignores standard machine type details after the user switches to a custom type', async () => {
|
|
189
186
|
const detailsRequest = deferred<any>();
|
|
190
187
|
runtimeServices.instanceTypeService = {
|
|
191
|
-
getInstanceTypeDetails:
|
|
188
|
+
getInstanceTypeDetails: vi.fn().mockReturnValue(detailsRequest.promise),
|
|
192
189
|
};
|
|
193
190
|
const values = command();
|
|
194
191
|
const adapter = adapterForHandlers();
|
|
195
192
|
const testProps = props(values, adapter);
|
|
196
|
-
const wrapper =
|
|
193
|
+
const wrapper = renderPage(<ServerGroupInstanceType {...testProps} />);
|
|
197
194
|
|
|
198
|
-
|
|
199
|
-
setFormikValues(wrapper, testProps, (testProps.formik.setValues as
|
|
200
|
-
|
|
195
|
+
fireEvent.change(control(wrapper, '#gce-machine-type'), { target: { value: 'n2-standard-2' } });
|
|
196
|
+
setFormikValues(wrapper, testProps, (testProps.formik.setValues as Mock).mock.lastCall[0], runtimeServices);
|
|
197
|
+
fireEvent.click(control(wrapper, '#gce-machine-type-custom'));
|
|
201
198
|
await settle();
|
|
202
199
|
detailsRequest.resolve(instanceTypeDetails('n2-standard-2', 40));
|
|
203
200
|
await settle();
|
|
204
201
|
|
|
205
|
-
const published = (testProps.formik.setValues as
|
|
202
|
+
const published = (testProps.formik.setValues as Mock).mock.lastCall[0];
|
|
206
203
|
expect(published.instanceType).toContain('custom-');
|
|
207
204
|
expect(published.viewState.instanceTypeDetails).toBeUndefined();
|
|
208
205
|
expect(published.disks).toEqual([{ type: 'pd-ssd', sizeGb: 20 }]);
|
|
209
|
-
expect((adapter.applyCommandHandler as
|
|
210
|
-
'customInstanceChanged',
|
|
211
|
-
]);
|
|
206
|
+
expect((adapter.applyCommandHandler as Mock).mock.calls.map((args) => args[1])).toEqual(['customInstanceChanged']);
|
|
212
207
|
});
|
|
213
208
|
|
|
214
209
|
it('preserves a disk edit made while machine type details are loading', async () => {
|
|
215
210
|
const detailsRequest = deferred<any>();
|
|
216
211
|
runtimeServices.instanceTypeService = {
|
|
217
|
-
getInstanceTypeDetails:
|
|
212
|
+
getInstanceTypeDetails: vi.fn().mockReturnValue(detailsRequest.promise),
|
|
218
213
|
};
|
|
219
214
|
const values = command();
|
|
220
215
|
const testProps = props(values);
|
|
221
|
-
const wrapper =
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const selectedValues = (testProps.formik.setValues as
|
|
225
|
-
setFormikValues(
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
216
|
+
const wrapper = renderPage(<ServerGroupInstanceType {...testProps} />);
|
|
217
|
+
|
|
218
|
+
fireEvent.change(control(wrapper, '#gce-machine-type'), { target: { value: 'n2-standard-2' } });
|
|
219
|
+
const selectedValues = (testProps.formik.setValues as Mock).mock.lastCall[0];
|
|
220
|
+
setFormikValues(
|
|
221
|
+
wrapper,
|
|
222
|
+
testProps,
|
|
223
|
+
{
|
|
224
|
+
...selectedValues,
|
|
225
|
+
disks: [{ type: 'pd-standard', sizeGb: 500 }],
|
|
226
|
+
},
|
|
227
|
+
runtimeServices,
|
|
228
|
+
);
|
|
229
229
|
detailsRequest.resolve(instanceTypeDetails('n2-standard-2', 40));
|
|
230
230
|
await settle();
|
|
231
231
|
|
|
232
|
-
const published = (testProps.formik.setValues as
|
|
232
|
+
const published = (testProps.formik.setValues as Mock).mock.lastCall[0];
|
|
233
233
|
expect(published.instanceType).toBe('n2-standard-2');
|
|
234
234
|
expect(published.viewState.instanceTypeDetails.name).toBe('n2-standard-2');
|
|
235
235
|
expect(published.disks).toEqual([{ type: 'pd-standard', sizeGb: 500 }]);
|
|
@@ -247,22 +247,22 @@ describe('ServerGroupInstanceType', () => {
|
|
|
247
247
|
});
|
|
248
248
|
const adapter = adapterForHandlers();
|
|
249
249
|
const testProps = props(values, adapter);
|
|
250
|
-
const wrapper =
|
|
250
|
+
const wrapper = renderPage(<ServerGroupInstanceType {...testProps} />);
|
|
251
251
|
|
|
252
|
-
|
|
252
|
+
fireEvent.change(control(wrapper, '#gce-custom-cpu'), { target: { value: '8' } });
|
|
253
253
|
await settle();
|
|
254
254
|
|
|
255
255
|
expect(adapter.applyCommandHandler).toHaveBeenCalledWith(
|
|
256
|
-
|
|
256
|
+
expect.objectContaining({
|
|
257
257
|
instanceType: 'n2-custom-8-16384',
|
|
258
|
-
viewState:
|
|
259
|
-
customInstance:
|
|
258
|
+
viewState: expect.objectContaining({
|
|
259
|
+
customInstance: expect.objectContaining({ vCpuCount: 8, memory: 16 }),
|
|
260
260
|
}),
|
|
261
261
|
}),
|
|
262
262
|
'customInstanceChanged',
|
|
263
263
|
);
|
|
264
264
|
expect(testProps.formik.setValues).toHaveBeenCalledWith(
|
|
265
|
-
|
|
265
|
+
expect.objectContaining({ instanceType: 'n2-custom-8-16384', handledBy: 'customInstanceChanged' }),
|
|
266
266
|
);
|
|
267
267
|
expect(values.instanceType).toBe('n2-custom-4-16384');
|
|
268
268
|
});
|
|
@@ -270,12 +270,12 @@ describe('ServerGroupInstanceType', () => {
|
|
|
270
270
|
it('keeps spot scheduling fields compatible when preemptibility changes', () => {
|
|
271
271
|
const values = command({ preemptible: false, automaticRestart: true, onHostMaintenance: 'MIGRATE' });
|
|
272
272
|
const testProps = props(values);
|
|
273
|
-
const wrapper =
|
|
273
|
+
const wrapper = renderPage(<ServerGroupInstanceType {...testProps} />);
|
|
274
274
|
|
|
275
|
-
|
|
275
|
+
fireEvent.click(control(wrapper, '#gce-preemptible'));
|
|
276
276
|
|
|
277
277
|
expect(testProps.formik.setValues).toHaveBeenCalledWith(
|
|
278
|
-
|
|
278
|
+
expect.objectContaining({ preemptible: true, automaticRestart: false, onHostMaintenance: 'TERMINATE' }),
|
|
279
279
|
);
|
|
280
280
|
expect(values.preemptible).toBe(false);
|
|
281
281
|
});
|
|
@@ -288,12 +288,12 @@ describe('ServerGroupInstanceType', () => {
|
|
|
288
288
|
],
|
|
289
289
|
});
|
|
290
290
|
const testProps = props(values);
|
|
291
|
-
const wrapper =
|
|
291
|
+
const wrapper = renderPage(<ServerGroupInstanceType {...testProps} />);
|
|
292
292
|
|
|
293
|
-
|
|
293
|
+
fireEvent.change(control(wrapper, '#gce-accelerator-type-0'), { target: { value: 'nvidia-tesla-v100' } });
|
|
294
294
|
|
|
295
295
|
expect(testProps.formik.setValues).toHaveBeenCalledWith(
|
|
296
|
-
|
|
296
|
+
expect.objectContaining({
|
|
297
297
|
acceleratorConfigs: [
|
|
298
298
|
{ acceleratorType: 'nvidia-tesla-v100', acceleratorCount: 2 },
|
|
299
299
|
{ acceleratorType: 'retired-gpu', acceleratorCount: 3 },
|
|
@@ -312,14 +312,14 @@ describe('ServerGroupInstanceType', () => {
|
|
|
312
312
|
],
|
|
313
313
|
});
|
|
314
314
|
const testProps = props(values);
|
|
315
|
-
const wrapper =
|
|
315
|
+
const wrapper = renderPage(<ServerGroupInstanceType {...testProps} />);
|
|
316
316
|
|
|
317
|
-
expect(wrapper
|
|
318
|
-
expect(wrapper
|
|
319
|
-
|
|
317
|
+
expect(control(wrapper, '#gce-boot-disk-size')).toHaveAttribute('min', '10');
|
|
318
|
+
expect(control(wrapper, '#gce-boot-disk-size')).toHaveAttribute('max', '65536');
|
|
319
|
+
fireEvent.change(control(wrapper, '#gce-boot-disk-type'), { target: { value: 'pd-standard' } });
|
|
320
320
|
|
|
321
321
|
expect(testProps.formik.setValues).toHaveBeenCalledWith(
|
|
322
|
-
|
|
322
|
+
expect.objectContaining({
|
|
323
323
|
disks: [
|
|
324
324
|
{ type: 'pd-standard', sizeGb: 20 },
|
|
325
325
|
{ type: 'local-ssd', sizeGb: 375 },
|
|
@@ -331,14 +331,13 @@ describe('ServerGroupInstanceType', () => {
|
|
|
331
331
|
});
|
|
332
332
|
|
|
333
333
|
it('accepts pipeline expressions but validates finite concrete disk and accelerator values', () => {
|
|
334
|
-
const page = shallow(<ServerGroupInstanceType {...props(command())} />).instance() as ServerGroupInstanceType;
|
|
335
334
|
const invalid = command({
|
|
336
335
|
instanceType: '',
|
|
337
336
|
disks: [{ type: 'pd-ssd', sizeGb: Infinity }],
|
|
338
337
|
acceleratorConfigs: [{ acceleratorType: 'nvidia-tesla-t4', acceleratorCount: 3 }],
|
|
339
338
|
});
|
|
340
339
|
|
|
341
|
-
expect(
|
|
340
|
+
expect(validateGceServerGroupInstanceType(invalid)).toEqual({
|
|
342
341
|
instanceType: 'Machine type required.',
|
|
343
342
|
disks: 'Boot disk size must be between 10 and 65536 GB.',
|
|
344
343
|
acceleratorConfigs: 'Accelerator count is unavailable for the selected type.',
|
|
@@ -349,10 +348,10 @@ describe('ServerGroupInstanceType', () => {
|
|
|
349
348
|
disks: [{ type: 'pd-ssd', sizeGb: '${ parameters.diskSize }' }],
|
|
350
349
|
acceleratorConfigs: [{ acceleratorType: 'retired-gpu', acceleratorCount: '${ parameters.gpuCount }' }],
|
|
351
350
|
});
|
|
352
|
-
expect(
|
|
353
|
-
const expressionWrapper =
|
|
354
|
-
expect(expressionWrapper
|
|
355
|
-
expect(expressionWrapper
|
|
351
|
+
expect(validateGceServerGroupInstanceType(expressionValues)).toEqual({});
|
|
352
|
+
const expressionWrapper = renderPage(<ServerGroupInstanceType {...props(expressionValues)} />);
|
|
353
|
+
expect(control(expressionWrapper, '#gce-boot-disk-size')).toHaveAttribute('type', 'text');
|
|
354
|
+
expect(control(expressionWrapper, '#gce-accelerator-count-0')).toHaveAttribute('type', 'text');
|
|
356
355
|
});
|
|
357
356
|
|
|
358
357
|
it('renders and associates instance type, boot disk, and accelerator validation errors', () => {
|
|
@@ -367,7 +366,7 @@ describe('ServerGroupInstanceType', () => {
|
|
|
367
366
|
acceleratorConfigs: 'Accelerator count is unavailable for the selected type.',
|
|
368
367
|
};
|
|
369
368
|
const testProps = props(values, adapterForHandlers(), errors);
|
|
370
|
-
const wrapper =
|
|
369
|
+
const wrapper = renderPage(<ServerGroupInstanceType {...testProps} />);
|
|
371
370
|
|
|
372
371
|
[
|
|
373
372
|
['#gce-machine-type', 'gce-machine-type-error'],
|
|
@@ -389,7 +388,11 @@ describe('ServerGroupInstanceType', () => {
|
|
|
389
388
|
acceleratorTypes: [],
|
|
390
389
|
},
|
|
391
390
|
});
|
|
392
|
-
|
|
391
|
+
rerenderInstanceType(
|
|
392
|
+
wrapper,
|
|
393
|
+
{ ...testProps, formik: { ...testProps.formik, errors, values: customValues } },
|
|
394
|
+
runtimeServices,
|
|
395
|
+
);
|
|
393
396
|
|
|
394
397
|
expectAssociatedError(wrapper, '#gce-custom-cpu', 'gce-machine-type-error');
|
|
395
398
|
expectAssociatedError(wrapper, '#gce-custom-memory', 'gce-machine-type-error');
|
|
@@ -397,24 +400,52 @@ describe('ServerGroupInstanceType', () => {
|
|
|
397
400
|
});
|
|
398
401
|
});
|
|
399
402
|
|
|
400
|
-
function expectAssociatedError(wrapper:
|
|
401
|
-
const
|
|
402
|
-
expect(
|
|
403
|
-
expect(
|
|
403
|
+
function expectAssociatedError(wrapper: RenderResult, selector: string, errorId: string): void {
|
|
404
|
+
const element = control(wrapper, selector);
|
|
405
|
+
expect(element, selector).toHaveAttribute('aria-invalid', 'true');
|
|
406
|
+
expect(element, selector).toHaveAttribute('aria-describedby', errorId);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function expectValidationAlert(wrapper: RenderResult, id: string, message: string): void {
|
|
410
|
+
const alert = control(wrapper, `#${id}`);
|
|
411
|
+
expect(alert, id).toHaveAttribute('role', 'alert');
|
|
412
|
+
expect(alert, id).toHaveTextContent(message);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function optionText(wrapper: RenderResult, selector: string): string[] {
|
|
416
|
+
return Array.from(control(wrapper, selector).querySelectorAll('option')).map((option) => option.textContent || '');
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function optionValues(wrapper: RenderResult, selector: string): string[] {
|
|
420
|
+
return Array.from(control(wrapper, selector).querySelectorAll('option')).map((option) => option.value);
|
|
404
421
|
}
|
|
405
422
|
|
|
406
|
-
function
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
|
|
423
|
+
function control(wrapper: RenderResult, selector: string): HTMLElement {
|
|
424
|
+
const element = wrapper.container.querySelector<HTMLElement>(selector);
|
|
425
|
+
if (!element) {
|
|
426
|
+
throw new Error(`Missing control: ${selector}`);
|
|
427
|
+
}
|
|
428
|
+
return element;
|
|
410
429
|
}
|
|
411
430
|
|
|
412
|
-
function
|
|
413
|
-
|
|
431
|
+
function renderWithRuntime(component: React.ReactElement, runtimeServices: any): RenderResult {
|
|
432
|
+
const container = document.body.appendChild(document.createElement('div'));
|
|
433
|
+
return render(
|
|
434
|
+
<DeckRuntimeContext.Provider value={{ services: runtimeServices } as any}>{component}</DeckRuntimeContext.Provider>,
|
|
435
|
+
{ baseElement: container, container },
|
|
436
|
+
);
|
|
414
437
|
}
|
|
415
438
|
|
|
416
|
-
function
|
|
417
|
-
|
|
439
|
+
function rerenderInstanceType(
|
|
440
|
+
rendered: RenderResult,
|
|
441
|
+
pageProps: IGceServerGroupWizardPageProps,
|
|
442
|
+
runtimeServices: any,
|
|
443
|
+
): void {
|
|
444
|
+
rendered.rerender(
|
|
445
|
+
<DeckRuntimeContext.Provider value={{ services: runtimeServices } as any}>
|
|
446
|
+
<ServerGroupInstanceType {...pageProps} />
|
|
447
|
+
</DeckRuntimeContext.Provider>,
|
|
448
|
+
);
|
|
418
449
|
}
|
|
419
450
|
|
|
420
451
|
function props(
|
|
@@ -425,24 +456,22 @@ function props(
|
|
|
425
456
|
return {
|
|
426
457
|
app: { name: 'app' } as any,
|
|
427
458
|
adapter,
|
|
428
|
-
formik: ({ errors, values, setValues:
|
|
429
|
-
IGceServerGroupCommand
|
|
430
|
-
>,
|
|
459
|
+
formik: ({ errors, values, setValues: vi.fn() } as unknown) as FormikProps<IGceServerGroupCommand>,
|
|
431
460
|
};
|
|
432
461
|
}
|
|
433
462
|
|
|
434
463
|
function adapterForHandlers(): IGceServerGroupWizardAdapter {
|
|
435
464
|
return {
|
|
436
|
-
configureCommand:
|
|
437
|
-
applyCommandHandler:
|
|
465
|
+
configureCommand: vi.fn().mockImplementation(async (_app, next) => next),
|
|
466
|
+
applyCommandHandler: vi.fn().mockImplementation(async (next, handler) => ({
|
|
438
467
|
command: { ...next, handledBy: handler },
|
|
439
468
|
result: { dirty: {} },
|
|
440
469
|
})),
|
|
441
|
-
applyConfigurationUpdate:
|
|
470
|
+
applyConfigurationUpdate: vi.fn().mockImplementation(async (next) => ({
|
|
442
471
|
command: next,
|
|
443
472
|
result: { dirty: {} },
|
|
444
473
|
})),
|
|
445
|
-
applyConfigurationRefresh:
|
|
474
|
+
applyConfigurationRefresh: vi.fn().mockImplementation(async (next) => ({
|
|
446
475
|
command: next,
|
|
447
476
|
result: { dirty: {} },
|
|
448
477
|
})),
|
|
@@ -527,10 +556,11 @@ function instanceTypeDetails(name: string, diskSize: number): any {
|
|
|
527
556
|
}
|
|
528
557
|
|
|
529
558
|
function setFormikValues(
|
|
530
|
-
wrapper:
|
|
559
|
+
wrapper: RenderResult,
|
|
531
560
|
testProps: IGceServerGroupWizardPageProps,
|
|
532
561
|
values: IGceServerGroupCommand,
|
|
562
|
+
runtimeServices: any,
|
|
533
563
|
): void {
|
|
534
564
|
const formik = ({ ...testProps.formik, values } as unknown) as FormikProps<IGceServerGroupCommand>;
|
|
535
|
-
wrapper
|
|
565
|
+
rerenderInstanceType(wrapper, { ...testProps, formik }, runtimeServices);
|
|
536
566
|
}
|
|
@@ -38,6 +38,34 @@ interface IGceInstanceTypeValidationErrors {
|
|
|
38
38
|
instanceType?: string;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
export function validateGceServerGroupInstanceType(values: IGceServerGroupCommand): IGceInstanceTypeValidationErrors {
|
|
42
|
+
const errors: IGceInstanceTypeValidationErrors = {};
|
|
43
|
+
if (!values.instanceType) {
|
|
44
|
+
errors.instanceType = 'Machine type required.';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const bootDisk = getBootDisk(values);
|
|
48
|
+
if (!bootDisk?.type || !isValidBoundedValue(bootDisk.sizeGb, values, MIN_BOOT_DISK_SIZE_GB, MAX_BOOT_DISK_SIZE_GB)) {
|
|
49
|
+
errors.disks = `Boot disk size must be between ${MIN_BOOT_DISK_SIZE_GB} and ${MAX_BOOT_DISK_SIZE_GB} GB.`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const accelerators = getAcceleratorConfigs(values);
|
|
53
|
+
const availableAccelerators = getAvailableAccelerators(values);
|
|
54
|
+
if (
|
|
55
|
+
accelerators.some((config) => {
|
|
56
|
+
if (!config.acceleratorType || !isValidPositiveInteger(config.acceleratorCount, values)) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
const available = availableAccelerators.find((accelerator) => accelerator.name === config.acceleratorType);
|
|
60
|
+
return available ? !available.availableCardCounts.includes(Number(config.acceleratorCount)) : false;
|
|
61
|
+
})
|
|
62
|
+
) {
|
|
63
|
+
errors.acceleratorConfigs = 'Accelerator count is unavailable for the selected type.';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return errors;
|
|
67
|
+
}
|
|
68
|
+
|
|
41
69
|
export class ServerGroupInstanceType extends GceServerGroupWizardPage {
|
|
42
70
|
public static contextType = DeckRuntimeContext;
|
|
43
71
|
public declare context: React.ContextType<typeof DeckRuntimeContext>;
|
|
@@ -45,34 +73,7 @@ export class ServerGroupInstanceType extends GceServerGroupWizardPage {
|
|
|
45
73
|
private instanceTypeRequestGeneration = 0;
|
|
46
74
|
|
|
47
75
|
public validate(values: IGceServerGroupCommand): IGceInstanceTypeValidationErrors {
|
|
48
|
-
|
|
49
|
-
if (!values.instanceType) {
|
|
50
|
-
errors.instanceType = 'Machine type required.';
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const bootDisk = getBootDisk(values);
|
|
54
|
-
if (
|
|
55
|
-
!bootDisk?.type ||
|
|
56
|
-
!isValidBoundedValue(bootDisk.sizeGb, values, MIN_BOOT_DISK_SIZE_GB, MAX_BOOT_DISK_SIZE_GB)
|
|
57
|
-
) {
|
|
58
|
-
errors.disks = `Boot disk size must be between ${MIN_BOOT_DISK_SIZE_GB} and ${MAX_BOOT_DISK_SIZE_GB} GB.`;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const accelerators = getAcceleratorConfigs(values);
|
|
62
|
-
const availableAccelerators = getAvailableAccelerators(values);
|
|
63
|
-
if (
|
|
64
|
-
accelerators.some((config) => {
|
|
65
|
-
if (!config.acceleratorType || !isValidPositiveInteger(config.acceleratorCount, values)) {
|
|
66
|
-
return true;
|
|
67
|
-
}
|
|
68
|
-
const available = availableAccelerators.find((accelerator) => accelerator.name === config.acceleratorType);
|
|
69
|
-
return available ? !available.availableCardCounts.includes(Number(config.acceleratorCount)) : false;
|
|
70
|
-
})
|
|
71
|
-
) {
|
|
72
|
-
errors.acceleratorConfigs = 'Accelerator count is unavailable for the selected type.';
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return errors;
|
|
76
|
+
return validateGceServerGroupInstanceType(values);
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
private publish = (nextValues: IGceServerGroupCommand): void => {
|