@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.
- 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,17 +1,14 @@
|
|
|
1
|
+
import { fireEvent, render, within } from '@testing-library/react';
|
|
1
2
|
import React from 'react';
|
|
2
|
-
import { shallow } from 'enzyme';
|
|
3
3
|
|
|
4
|
-
import type { IGceLoadBalancerData } from '../common';
|
|
5
|
-
import { normalizeGceLoadBalancerCommand } from '../common';
|
|
6
|
-
|
|
7
|
-
import * as editorModule from './GceHttpLoadBalancerEditor';
|
|
8
4
|
import {
|
|
9
5
|
buildGceHttpLoadBalancerOptions,
|
|
10
6
|
constrainGceHttpLoadBalancerCommand,
|
|
11
|
-
FormRow,
|
|
12
7
|
GceHttpLoadBalancerEditor,
|
|
8
|
+
validateGceHttpLoadBalancerCommand,
|
|
13
9
|
} from './GceHttpLoadBalancerEditor';
|
|
14
|
-
import {
|
|
10
|
+
import type { IGceLoadBalancerData } from '../common';
|
|
11
|
+
import { normalizeGceLoadBalancerCommand } from '../common';
|
|
15
12
|
|
|
16
13
|
describe('GceHttpLoadBalancerEditor', () => {
|
|
17
14
|
const emptyData: IGceLoadBalancerData = {
|
|
@@ -40,7 +37,7 @@ describe('GceHttpLoadBalancerEditor', () => {
|
|
|
40
37
|
);
|
|
41
38
|
|
|
42
39
|
expect(constrainGceHttpLoadBalancerCommand(http)).toEqual(
|
|
43
|
-
|
|
40
|
+
expect.objectContaining({
|
|
44
41
|
loadBalancerType: 'HTTP',
|
|
45
42
|
network: undefined,
|
|
46
43
|
region: 'global',
|
|
@@ -112,19 +109,17 @@ describe('GceHttpLoadBalancerEditor', () => {
|
|
|
112
109
|
});
|
|
113
110
|
|
|
114
111
|
it('renders only protocols supported by the selected composite type', () => {
|
|
115
|
-
const onChange =
|
|
112
|
+
const onChange = vi.fn();
|
|
116
113
|
const httpCommand = normalizeGceLoadBalancerCommand(
|
|
117
114
|
{ account: 'account-a', listeners: [{ name: 'frontend', port: 80 }], loadBalancerType: 'HTTP', name: 'web' },
|
|
118
115
|
'create',
|
|
119
116
|
);
|
|
120
|
-
const http =
|
|
117
|
+
const http = render(<GceHttpLoadBalancerEditor command={httpCommand} data={emptyData} onChange={onChange} />);
|
|
121
118
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
httpListener.find('[data-testid="listener-protocol"] option').map((option) => option.prop('value')),
|
|
125
|
-
).toEqual(['HTTP', 'HTTPS']);
|
|
119
|
+
expect(optionValues(http.getByTestId('listener-protocol'))).toEqual(['HTTP', 'HTTPS']);
|
|
120
|
+
http.unmount();
|
|
126
121
|
|
|
127
|
-
const internal =
|
|
122
|
+
const internal = render(
|
|
128
123
|
<GceHttpLoadBalancerEditor
|
|
129
124
|
command={{ ...httpCommand, loadBalancerType: 'INTERNAL_MANAGED', region: 'europe-west1' }}
|
|
130
125
|
data={emptyData}
|
|
@@ -132,10 +127,7 @@ describe('GceHttpLoadBalancerEditor', () => {
|
|
|
132
127
|
/>,
|
|
133
128
|
);
|
|
134
129
|
|
|
135
|
-
|
|
136
|
-
expect(
|
|
137
|
-
internalListener.find('[data-testid="listener-protocol"] option').map((option) => option.prop('value')),
|
|
138
|
-
).toEqual(['HTTP', 'HTTPS']);
|
|
130
|
+
expect(optionValues(internal.getByTestId('listener-protocol'))).toEqual(['HTTP', 'HTTPS']);
|
|
139
131
|
});
|
|
140
132
|
|
|
141
133
|
it('renders location controls as form rows instead of nesting them inside bold labels', () => {
|
|
@@ -143,10 +135,8 @@ describe('GceHttpLoadBalancerEditor', () => {
|
|
|
143
135
|
{ account: 'account-a', listeners: [{ name: 'frontend', port: 80 }], loadBalancerType: 'HTTP', name: 'web' },
|
|
144
136
|
'create',
|
|
145
137
|
);
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
);
|
|
149
|
-
expect(wrapper.find(FormRow).length).toBeGreaterThan(2);
|
|
138
|
+
const { container } = render(<GceHttpLoadBalancerEditor command={command} data={emptyData} onChange={vi.fn()} />);
|
|
139
|
+
expect(container.querySelectorAll('.form-group').length).toBeGreaterThan(2);
|
|
150
140
|
});
|
|
151
141
|
|
|
152
142
|
it('validates account, name, location, listeners, backends, health checks, and routing', () => {
|
|
@@ -175,10 +165,8 @@ describe('GceHttpLoadBalancerEditor', () => {
|
|
|
175
165
|
{ healthCheckType: 'HTTP', name: '', port: 70000, requestPath: '' },
|
|
176
166
|
{ healthCheckType: 'UDP', name: 'unsupported-check', port: 80 },
|
|
177
167
|
];
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
expect(validate(invalid)).toEqual(
|
|
181
|
-
jasmine.arrayContaining([
|
|
168
|
+
expect(validateGceHttpLoadBalancerCommand(invalid)).toEqual(
|
|
169
|
+
expect.arrayContaining([
|
|
182
170
|
'Name is required.',
|
|
183
171
|
'Account is required.',
|
|
184
172
|
'Region is required for INTERNAL_MANAGED load balancers.',
|
|
@@ -204,7 +192,7 @@ describe('GceHttpLoadBalancerEditor', () => {
|
|
|
204
192
|
});
|
|
205
193
|
|
|
206
194
|
it('initializes path matchers from the current composite default without coupling later changes', () => {
|
|
207
|
-
const onChange =
|
|
195
|
+
const onChange = vi.fn();
|
|
208
196
|
const command = normalizeGceLoadBalancerCommand(
|
|
209
197
|
{
|
|
210
198
|
account: 'account-a',
|
|
@@ -217,15 +205,12 @@ describe('GceHttpLoadBalancerEditor', () => {
|
|
|
217
205
|
'create',
|
|
218
206
|
);
|
|
219
207
|
const data = { ...emptyData, backendServices: command.backendServices };
|
|
220
|
-
const
|
|
208
|
+
const initial = render(<GceHttpLoadBalancerEditor command={command} data={data} onChange={onChange} />);
|
|
221
209
|
|
|
222
|
-
|
|
223
|
-
.find('button')
|
|
224
|
-
.filterWhere((button) => button.text() === 'Add host rule')
|
|
225
|
-
.simulate('click');
|
|
210
|
+
fireEvent.click(initial.getByRole('button', { name: 'Add host rule' }));
|
|
226
211
|
|
|
227
212
|
expect(onChange).toHaveBeenCalledWith(
|
|
228
|
-
|
|
213
|
+
expect.objectContaining({
|
|
229
214
|
hostRules: [
|
|
230
215
|
{
|
|
231
216
|
hostPatterns: [],
|
|
@@ -244,14 +229,13 @@ describe('GceHttpLoadBalancerEditor', () => {
|
|
|
244
229
|
},
|
|
245
230
|
],
|
|
246
231
|
};
|
|
247
|
-
|
|
232
|
+
initial.unmount();
|
|
233
|
+
const stable = render(<GceHttpLoadBalancerEditor command={withMatcher} data={data} onChange={onChange} />);
|
|
248
234
|
|
|
249
|
-
|
|
250
|
-
.find('[data-testid="default-backend-service"]')
|
|
251
|
-
.simulate('change', { target: { value: 'backend-b' } });
|
|
235
|
+
fireEvent.change(stable.getByTestId('default-backend-service'), { target: { value: 'backend-b' } });
|
|
252
236
|
|
|
253
237
|
expect(onChange).toHaveBeenCalledWith(
|
|
254
|
-
|
|
238
|
+
expect.objectContaining({
|
|
255
239
|
defaultService: { name: 'backend-b' },
|
|
256
240
|
hostRules: [
|
|
257
241
|
{
|
|
@@ -292,8 +276,8 @@ describe('GceHttpLoadBalancerEditor', () => {
|
|
|
292
276
|
} as any);
|
|
293
277
|
|
|
294
278
|
expect(options.addresses.map(({ name }) => name)).toEqual(['current-address', 'removed-address']);
|
|
295
|
-
expect(options.certificates).
|
|
296
|
-
|
|
279
|
+
expect(options.certificates).toContainEqual(
|
|
280
|
+
expect.objectContaining({ name: 'removed-cert', selfLink: 'https://compute/sslCertificates/removed-cert' }),
|
|
297
281
|
);
|
|
298
282
|
expect(options.healthChecks.map(({ name }) => name)).toEqual(['current-check', 'removed-check']);
|
|
299
283
|
expect(options.backendServices.map(({ name }) => name)).toEqual(['current-backend', 'removed-backend']);
|
|
@@ -399,7 +383,7 @@ describe('GceHttpLoadBalancerEditor', () => {
|
|
|
399
383
|
});
|
|
400
384
|
|
|
401
385
|
it('locks infrastructure identity controls only while editing', () => {
|
|
402
|
-
const onChange =
|
|
386
|
+
const onChange = vi.fn();
|
|
403
387
|
const internal = normalizeGceLoadBalancerCommand(
|
|
404
388
|
{
|
|
405
389
|
account: 'account-a',
|
|
@@ -411,22 +395,94 @@ describe('GceHttpLoadBalancerEditor', () => {
|
|
|
411
395
|
},
|
|
412
396
|
'edit',
|
|
413
397
|
);
|
|
414
|
-
const
|
|
398
|
+
const rendered = render(<GceHttpLoadBalancerEditor command={internal} data={emptyData} onChange={onChange} />);
|
|
415
399
|
|
|
416
400
|
['load-balancer-name', 'load-balancer-type', 'credentials', 'region'].forEach((testId) =>
|
|
417
|
-
expect(
|
|
401
|
+
expect(rendered.getByTestId(testId)).toBeDisabled(),
|
|
418
402
|
);
|
|
419
403
|
|
|
420
|
-
|
|
404
|
+
rendered.rerender(
|
|
421
405
|
<GceHttpLoadBalancerEditor command={{ ...internal, mode: 'create' }} data={emptyData} onChange={onChange} />,
|
|
422
406
|
);
|
|
423
407
|
['load-balancer-name', 'load-balancer-type', 'credentials', 'region'].forEach((testId) =>
|
|
424
|
-
expect(
|
|
408
|
+
expect(rendered.getByTestId(testId)).not.toBeDisabled(),
|
|
409
|
+
);
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
it('locks the EXTERNAL_MANAGED network while editing because existing listeners keep their network', () => {
|
|
413
|
+
const onChange = vi.fn();
|
|
414
|
+
const command = (loadBalancerType: 'EXTERNAL_MANAGED' | 'INTERNAL_MANAGED', mode: 'create' | 'edit') =>
|
|
415
|
+
normalizeGceLoadBalancerCommand(
|
|
416
|
+
{
|
|
417
|
+
account: 'account-a',
|
|
418
|
+
loadBalancerType,
|
|
419
|
+
name: 'web',
|
|
420
|
+
network: 'network-a',
|
|
421
|
+
region: 'europe-west1',
|
|
422
|
+
subnet: 'subnet-a',
|
|
423
|
+
},
|
|
424
|
+
mode,
|
|
425
|
+
);
|
|
426
|
+
const networkSelect = (loadBalancerType: 'EXTERNAL_MANAGED' | 'INTERNAL_MANAGED', mode: 'create' | 'edit') => {
|
|
427
|
+
const { getByTestId, unmount } = render(
|
|
428
|
+
<GceHttpLoadBalancerEditor command={command(loadBalancerType, mode)} data={emptyData} onChange={onChange} />,
|
|
429
|
+
);
|
|
430
|
+
const network = getByTestId('network');
|
|
431
|
+
return { network, unmount };
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
const externalEdit = networkSelect('EXTERNAL_MANAGED', 'edit');
|
|
435
|
+
expect(externalEdit.network).toBeDisabled();
|
|
436
|
+
externalEdit.unmount();
|
|
437
|
+
const externalCreate = networkSelect('EXTERNAL_MANAGED', 'create');
|
|
438
|
+
expect(externalCreate.network).toBeEnabled();
|
|
439
|
+
externalCreate.unmount();
|
|
440
|
+
const internalEdit = networkSelect('INTERNAL_MANAGED', 'edit');
|
|
441
|
+
expect(internalEdit.network).toBeEnabled();
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
it('drops EXTERNAL_MANAGED listener addresses when the account or region changes', () => {
|
|
445
|
+
const scopedCommand = (loadBalancerType: 'EXTERNAL_MANAGED' | 'INTERNAL_MANAGED') =>
|
|
446
|
+
normalizeGceLoadBalancerCommand(
|
|
447
|
+
{
|
|
448
|
+
account: 'account-a',
|
|
449
|
+
listeners: [{ ipAddress: '203.0.113.10', name: 'frontend', port: 80, protocol: 'HTTP', subnet: 'subnet-a' }],
|
|
450
|
+
loadBalancerType,
|
|
451
|
+
name: 'web',
|
|
452
|
+
network: 'network-a',
|
|
453
|
+
region: 'europe-west1',
|
|
454
|
+
subnet: 'subnet-a',
|
|
455
|
+
},
|
|
456
|
+
'create',
|
|
457
|
+
);
|
|
458
|
+
const onChange = vi.fn();
|
|
459
|
+
const data = {
|
|
460
|
+
...emptyData,
|
|
461
|
+
accounts: [{ name: 'account-a' }, { name: 'account-b' }],
|
|
462
|
+
regions: [{ name: 'europe-west1' }, { name: 'us-central1' }],
|
|
463
|
+
};
|
|
464
|
+
const external = render(
|
|
465
|
+
<GceHttpLoadBalancerEditor command={scopedCommand('EXTERNAL_MANAGED')} data={data} onChange={onChange} />,
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
fireEvent.change(external.getByTestId('credentials'), { target: { value: 'account-b' } });
|
|
469
|
+
expect(onChange.mock.lastCall[0].credentials).toBe('account-b');
|
|
470
|
+
expect(onChange.mock.lastCall[0].listeners[0].address).toBeUndefined();
|
|
471
|
+
fireEvent.change(external.getByTestId('region'), { target: { value: 'us-central1' } });
|
|
472
|
+
expect(onChange.mock.lastCall[0].region).toBe('us-central1');
|
|
473
|
+
expect(onChange.mock.lastCall[0].listeners[0].address).toBeUndefined();
|
|
474
|
+
external.unmount();
|
|
475
|
+
|
|
476
|
+
const internal = render(
|
|
477
|
+
<GceHttpLoadBalancerEditor command={scopedCommand('INTERNAL_MANAGED')} data={data} onChange={onChange} />,
|
|
425
478
|
);
|
|
479
|
+
fireEvent.change(internal.getByTestId('region'), { target: { value: 'us-central1' } });
|
|
480
|
+
expect(onChange.mock.lastCall[0].region).toBe('us-central1');
|
|
481
|
+
expect(onChange.mock.lastCall[0].listeners[0].address).toEqual({ name: '203.0.113.10' });
|
|
426
482
|
});
|
|
427
483
|
|
|
428
484
|
it('restores account, region, network, subnet, and composite type controls', () => {
|
|
429
|
-
const onChange =
|
|
485
|
+
const onChange = vi.fn();
|
|
430
486
|
const command = normalizeGceLoadBalancerCommand(
|
|
431
487
|
{
|
|
432
488
|
account: 'account-a',
|
|
@@ -439,7 +495,7 @@ describe('GceHttpLoadBalancerEditor', () => {
|
|
|
439
495
|
},
|
|
440
496
|
'create',
|
|
441
497
|
);
|
|
442
|
-
const
|
|
498
|
+
const rendered = render(
|
|
443
499
|
<GceHttpLoadBalancerEditor
|
|
444
500
|
command={command}
|
|
445
501
|
data={{
|
|
@@ -453,20 +509,305 @@ describe('GceHttpLoadBalancerEditor', () => {
|
|
|
453
509
|
/>,
|
|
454
510
|
);
|
|
455
511
|
|
|
456
|
-
expect(
|
|
457
|
-
expect(
|
|
458
|
-
expect(
|
|
459
|
-
expect(
|
|
460
|
-
expect(
|
|
461
|
-
expect(
|
|
512
|
+
expect(rendered.getByTestId('load-balancer-type')).toHaveValue('INTERNAL_MANAGED');
|
|
513
|
+
expect(rendered.getByTestId('load-balancer-name')).toHaveValue('web');
|
|
514
|
+
expect(rendered.getByTestId('credentials')).toHaveValue('account-a');
|
|
515
|
+
expect(rendered.getByTestId('region')).toHaveValue('europe-west1');
|
|
516
|
+
expect(rendered.getByTestId('network')).toHaveValue('network-a');
|
|
517
|
+
expect(rendered.getByTestId('subnet')).toHaveValue('subnet-a');
|
|
462
518
|
|
|
463
|
-
|
|
464
|
-
expect(onChange).toHaveBeenCalledWith(
|
|
519
|
+
fireEvent.change(rendered.getByTestId('load-balancer-name'), { target: { value: 'web-updated' } });
|
|
520
|
+
expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ name: 'web-updated' }));
|
|
465
521
|
|
|
466
|
-
|
|
522
|
+
fireEvent.change(rendered.getByTestId('load-balancer-type'), { target: { value: 'HTTP' } });
|
|
467
523
|
|
|
468
524
|
expect(onChange).toHaveBeenCalledWith(
|
|
469
|
-
|
|
525
|
+
expect.objectContaining({ loadBalancerType: 'HTTP', network: undefined, region: 'global', subnet: undefined }),
|
|
470
526
|
);
|
|
471
527
|
});
|
|
528
|
+
|
|
529
|
+
it('constrains EXTERNAL_MANAGED to regional scope with network and listener tier', () => {
|
|
530
|
+
const command = normalizeGceLoadBalancerCommand(
|
|
531
|
+
{
|
|
532
|
+
account: 'account-a',
|
|
533
|
+
listeners: [
|
|
534
|
+
{
|
|
535
|
+
certificate: 'regional-cert',
|
|
536
|
+
ipAddress: '203.0.113.10',
|
|
537
|
+
name: 'app-https',
|
|
538
|
+
networkTier: 'STANDARD',
|
|
539
|
+
port: 443,
|
|
540
|
+
protocol: 'HTTPS',
|
|
541
|
+
},
|
|
542
|
+
],
|
|
543
|
+
loadBalancerType: 'EXTERNAL_MANAGED',
|
|
544
|
+
name: 'web',
|
|
545
|
+
network: 'network-a',
|
|
546
|
+
region: 'europe-west1',
|
|
547
|
+
},
|
|
548
|
+
'create',
|
|
549
|
+
);
|
|
550
|
+
|
|
551
|
+
expect(constrainGceHttpLoadBalancerCommand(command)).toEqual(
|
|
552
|
+
expect.objectContaining({
|
|
553
|
+
loadBalancerType: 'EXTERNAL_MANAGED',
|
|
554
|
+
network: { name: 'network-a' },
|
|
555
|
+
region: 'europe-west1',
|
|
556
|
+
subnet: undefined,
|
|
557
|
+
}),
|
|
558
|
+
);
|
|
559
|
+
expect(constrainGceHttpLoadBalancerCommand(command).listeners).toEqual([
|
|
560
|
+
{
|
|
561
|
+
certificate: { name: 'regional-cert' },
|
|
562
|
+
address: { name: '203.0.113.10' },
|
|
563
|
+
name: 'app-https',
|
|
564
|
+
networkTier: 'STANDARD',
|
|
565
|
+
portRange: '443',
|
|
566
|
+
protocol: 'HTTPS',
|
|
567
|
+
},
|
|
568
|
+
]);
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
it('retains networkTier when constraining an EXTERNAL_MANAGED plaintext HTTP listener', () => {
|
|
572
|
+
const command = normalizeGceLoadBalancerCommand(
|
|
573
|
+
{
|
|
574
|
+
account: 'account-a',
|
|
575
|
+
listeners: [
|
|
576
|
+
{
|
|
577
|
+
ipAddress: '203.0.113.10',
|
|
578
|
+
name: 'app-http',
|
|
579
|
+
networkTier: 'STANDARD',
|
|
580
|
+
port: 80,
|
|
581
|
+
protocol: 'HTTP',
|
|
582
|
+
},
|
|
583
|
+
],
|
|
584
|
+
loadBalancerType: 'EXTERNAL_MANAGED',
|
|
585
|
+
name: 'web',
|
|
586
|
+
network: 'network-a',
|
|
587
|
+
region: 'europe-west1',
|
|
588
|
+
},
|
|
589
|
+
'create',
|
|
590
|
+
);
|
|
591
|
+
|
|
592
|
+
expect(constrainGceHttpLoadBalancerCommand(command).listeners).toEqual([
|
|
593
|
+
{
|
|
594
|
+
address: { name: '203.0.113.10' },
|
|
595
|
+
name: 'app-http',
|
|
596
|
+
networkTier: 'STANDARD',
|
|
597
|
+
portRange: '80',
|
|
598
|
+
protocol: 'HTTP',
|
|
599
|
+
},
|
|
600
|
+
]);
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
it('scopes EXTERNAL_MANAGED resources by account and region and filters proxy-only networks', () => {
|
|
604
|
+
const data = {
|
|
605
|
+
...emptyData,
|
|
606
|
+
addresses: [
|
|
607
|
+
{ account: 'account-a', address: '203.0.113.10', addressType: 'EXTERNAL', region: 'europe-west1' },
|
|
608
|
+
{ account: 'account-a', address: '10.0.0.1', addressType: 'INTERNAL', region: 'europe-west1' },
|
|
609
|
+
{ account: 'account-a', address: '198.51.100.1', addressType: 'EXTERNAL', region: 'us-central1' },
|
|
610
|
+
{ account: 'account-b', address: '203.0.113.11', addressType: 'EXTERNAL', region: 'europe-west1' },
|
|
611
|
+
],
|
|
612
|
+
backendServices: [
|
|
613
|
+
{ account: 'account-a', name: 'regional-backend', region: 'europe-west1' },
|
|
614
|
+
{ account: 'account-a', name: 'global-backend', region: 'global' },
|
|
615
|
+
],
|
|
616
|
+
certificates: [
|
|
617
|
+
{ account: 'account-a', name: 'regional-cert', region: 'europe-west1' },
|
|
618
|
+
{ account: 'account-b', name: 'wrong-account-cert', region: 'europe-west1' },
|
|
619
|
+
],
|
|
620
|
+
healthChecks: [
|
|
621
|
+
{ account: 'account-a', name: 'regional-check', region: 'europe-west1' },
|
|
622
|
+
{ account: 'account-a', name: 'global-check', region: 'global' },
|
|
623
|
+
],
|
|
624
|
+
networks: [
|
|
625
|
+
{ account: 'account-a', id: 'network-a', name: 'network-a', region: 'global' },
|
|
626
|
+
{ account: 'account-a', id: 'network-b', name: 'network-b', region: 'global' },
|
|
627
|
+
],
|
|
628
|
+
subnets: [
|
|
629
|
+
{
|
|
630
|
+
account: 'account-a',
|
|
631
|
+
name: 'proxy-subnet',
|
|
632
|
+
network: 'network-a',
|
|
633
|
+
purpose: 'REGIONAL_MANAGED_PROXY',
|
|
634
|
+
region: 'europe-west1',
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
account: 'account-a',
|
|
638
|
+
name: 'internal-subnet',
|
|
639
|
+
network: 'network-b',
|
|
640
|
+
purpose: 'INTERNAL_HTTPS_LOAD_BALANCER',
|
|
641
|
+
region: 'europe-west1',
|
|
642
|
+
},
|
|
643
|
+
],
|
|
644
|
+
} as any;
|
|
645
|
+
const command = normalizeGceLoadBalancerCommand(
|
|
646
|
+
{
|
|
647
|
+
account: 'account-a',
|
|
648
|
+
backendServices: [{ healthCheck: 'regional-check', name: 'regional-backend' }],
|
|
649
|
+
certificate: 'regional-cert',
|
|
650
|
+
ipAddress: '203.0.113.10',
|
|
651
|
+
loadBalancerType: 'EXTERNAL_MANAGED',
|
|
652
|
+
name: 'external',
|
|
653
|
+
network: 'network-a',
|
|
654
|
+
region: 'europe-west1',
|
|
655
|
+
},
|
|
656
|
+
'create',
|
|
657
|
+
);
|
|
658
|
+
|
|
659
|
+
const options = buildGceHttpLoadBalancerOptions(command, data);
|
|
660
|
+
expect(options.addresses.filter(({ address }) => address).map(({ address }) => address)).toEqual(['203.0.113.10']);
|
|
661
|
+
// The listener select value is the persisted IP, so it must stay selectable next to the named address.
|
|
662
|
+
expect(options.addresses.map(({ name }) => name)).toContain('203.0.113.10');
|
|
663
|
+
expect(options.certificates.map(({ name }) => name)).toEqual(['regional-cert']);
|
|
664
|
+
expect(options.healthChecks.map(({ name }) => name)).toEqual(['regional-check']);
|
|
665
|
+
expect(options.backendServices.map(({ name }) => name)).toEqual(['regional-backend']);
|
|
666
|
+
expect(options.networks.map(({ name }) => name)).toEqual(['network-a']);
|
|
667
|
+
expect(options.subnets).toEqual([]);
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
it('rejects Shared VPCs, unsupported backend protocols, and destructive same-name listener edits', () => {
|
|
671
|
+
const command = normalizeGceLoadBalancerCommand(
|
|
672
|
+
{
|
|
673
|
+
account: 'account-a',
|
|
674
|
+
backendServices: [{ healthCheck: 'check-a', name: 'backend-a', portName: 'http', protocol: 'HTTP2' }],
|
|
675
|
+
defaultService: 'backend-a',
|
|
676
|
+
healthChecks: [{ healthCheckType: 'HTTP', name: 'check-a', port: 80, requestPath: '/' }],
|
|
677
|
+
listeners: [{ name: 'frontend', networkTier: 'PREMIUM', port: 80, protocol: 'HTTP' }],
|
|
678
|
+
loadBalancerType: 'EXTERNAL_MANAGED',
|
|
679
|
+
name: 'web',
|
|
680
|
+
network: 'host-project/shared-network',
|
|
681
|
+
region: 'europe-west1',
|
|
682
|
+
},
|
|
683
|
+
'edit',
|
|
684
|
+
);
|
|
685
|
+
command.listeners[0].portRange = '8080';
|
|
686
|
+
|
|
687
|
+
expect(validateGceHttpLoadBalancerCommand(command)).toEqual(
|
|
688
|
+
expect.arrayContaining([
|
|
689
|
+
'Shared VPC networks are not supported for EXTERNAL_MANAGED load balancers.',
|
|
690
|
+
'Backend service protocol must be HTTP or HTTPS.',
|
|
691
|
+
'Rename the listener to change its port, address, network tier, or HTTP/HTTPS protocol.',
|
|
692
|
+
]),
|
|
693
|
+
);
|
|
694
|
+
|
|
695
|
+
command.network = { name: 'local-network' };
|
|
696
|
+
command.backendServices[0].protocol = 'HTTPS';
|
|
697
|
+
command.listeners[0] = { ...command.listeners[0], name: 'replacement' };
|
|
698
|
+
expect(validateGceHttpLoadBalancerCommand(command)).not.toEqual(
|
|
699
|
+
expect.arrayContaining([
|
|
700
|
+
'Shared VPC networks are not supported for EXTERNAL_MANAGED load balancers.',
|
|
701
|
+
'Backend service protocol must be HTTP or HTTPS.',
|
|
702
|
+
'Rename the listener to change its port, address, network tier, or HTTP/HTTPS protocol.',
|
|
703
|
+
]),
|
|
704
|
+
);
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
it('reports a missing network for a new EXTERNAL_MANAGED load balancer instead of throwing', () => {
|
|
708
|
+
const command = normalizeGceLoadBalancerCommand(
|
|
709
|
+
{ account: 'account-a', loadBalancerType: 'EXTERNAL_MANAGED', region: 'europe-west1' },
|
|
710
|
+
'create',
|
|
711
|
+
);
|
|
712
|
+
command.network = undefined;
|
|
713
|
+
|
|
714
|
+
const errors = validateGceHttpLoadBalancerCommand(command);
|
|
715
|
+
|
|
716
|
+
expect(errors).toContain('Network is required for EXTERNAL_MANAGED load balancers.');
|
|
717
|
+
expect(errors).not.toContain('Shared VPC networks are not supported for EXTERNAL_MANAGED load balancers.');
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
it('offers only account-local networks for EXTERNAL_MANAGED', () => {
|
|
721
|
+
const command = normalizeGceLoadBalancerCommand(
|
|
722
|
+
{
|
|
723
|
+
account: 'account-a',
|
|
724
|
+
loadBalancerType: 'EXTERNAL_MANAGED',
|
|
725
|
+
name: 'web',
|
|
726
|
+
region: 'europe-west1',
|
|
727
|
+
},
|
|
728
|
+
'create',
|
|
729
|
+
);
|
|
730
|
+
const options = buildGceHttpLoadBalancerOptions(command, {
|
|
731
|
+
...emptyData,
|
|
732
|
+
networks: [
|
|
733
|
+
{ account: 'account-a', id: 'local-network', name: 'local-network' },
|
|
734
|
+
{ account: 'account-a', id: 'host-project/shared-network', name: 'shared-network' },
|
|
735
|
+
],
|
|
736
|
+
subnets: [
|
|
737
|
+
{
|
|
738
|
+
account: 'account-a',
|
|
739
|
+
name: 'local-proxy',
|
|
740
|
+
network: 'local-network',
|
|
741
|
+
purpose: 'REGIONAL_MANAGED_PROXY',
|
|
742
|
+
region: 'europe-west1',
|
|
743
|
+
},
|
|
744
|
+
{
|
|
745
|
+
account: 'account-a',
|
|
746
|
+
name: 'shared-proxy',
|
|
747
|
+
network: 'host-project/shared-network',
|
|
748
|
+
purpose: 'REGIONAL_MANAGED_PROXY',
|
|
749
|
+
region: 'europe-west1',
|
|
750
|
+
},
|
|
751
|
+
],
|
|
752
|
+
} as any);
|
|
753
|
+
|
|
754
|
+
expect(options.networks.map(({ name }) => name)).toEqual(['local-network']);
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
it('rejects EXTERNAL_MANAGED listeners that use certificate maps', () => {
|
|
758
|
+
const command = normalizeGceLoadBalancerCommand(
|
|
759
|
+
{
|
|
760
|
+
account: 'account-a',
|
|
761
|
+
listeners: [{ certificateMap: 'shared-map', name: 'frontend', port: 443, protocol: 'HTTPS' }],
|
|
762
|
+
loadBalancerType: 'EXTERNAL_MANAGED',
|
|
763
|
+
name: 'web',
|
|
764
|
+
network: 'network-a',
|
|
765
|
+
region: 'europe-west1',
|
|
766
|
+
},
|
|
767
|
+
'create',
|
|
768
|
+
);
|
|
769
|
+
expect(validateGceHttpLoadBalancerCommand(command)).toContain(
|
|
770
|
+
'Certificate maps are not supported for EXTERNAL_MANAGED load balancers.',
|
|
771
|
+
);
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
(['HTTP', 'INTERNAL_MANAGED'] as const).forEach((loadBalancerType) => {
|
|
775
|
+
it(`strips stale networkTier when constraining ${loadBalancerType} from EXTERNAL_MANAGED`, () => {
|
|
776
|
+
const external = normalizeGceLoadBalancerCommand(
|
|
777
|
+
{
|
|
778
|
+
account: 'account-a',
|
|
779
|
+
listeners: [
|
|
780
|
+
{
|
|
781
|
+
ipAddress: '203.0.113.10',
|
|
782
|
+
name: 'frontend',
|
|
783
|
+
networkTier: 'STANDARD',
|
|
784
|
+
port: 443,
|
|
785
|
+
protocol: 'HTTPS',
|
|
786
|
+
},
|
|
787
|
+
],
|
|
788
|
+
loadBalancerType: 'EXTERNAL_MANAGED',
|
|
789
|
+
name: 'web',
|
|
790
|
+
network: 'network-a',
|
|
791
|
+
region: 'europe-west1',
|
|
792
|
+
},
|
|
793
|
+
'create',
|
|
794
|
+
);
|
|
795
|
+
|
|
796
|
+
const constrained = constrainGceHttpLoadBalancerCommand({
|
|
797
|
+
...external,
|
|
798
|
+
loadBalancerType,
|
|
799
|
+
...(loadBalancerType === 'INTERNAL_MANAGED'
|
|
800
|
+
? { network: { name: 'network-a' }, region: 'europe-west1', subnet: { name: 'subnet-a' } }
|
|
801
|
+
: { network: undefined, region: 'global', subnet: undefined }),
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
expect(constrained.listeners.every(({ networkTier }) => networkTier === undefined)).toBe(true);
|
|
805
|
+
});
|
|
806
|
+
});
|
|
472
807
|
});
|
|
808
|
+
|
|
809
|
+
function optionValues(select: HTMLElement): string[] {
|
|
810
|
+
return within(select)
|
|
811
|
+
.getAllByRole('option')
|
|
812
|
+
.map((option) => (option as HTMLOptionElement).value);
|
|
813
|
+
}
|