@spinnaker/google 2026.3.2 → 2026.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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,9 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
3
|
+
import { UIRouterContext, UIRouterReact } from '@uirouter/react';
|
|
3
4
|
|
|
4
|
-
import { LoadBalancerWriter,
|
|
5
|
+
import { LoadBalancerWriter, TaskReader } from '@spinnaker/core';
|
|
5
6
|
|
|
6
|
-
import { GceProxyLoadBalancerEditor } from './GceProxyLoadBalancerEditor';
|
|
7
7
|
import {
|
|
8
8
|
GceProxyLoadBalancerModal,
|
|
9
9
|
normalizeGceProxyLoadBalancerCommand,
|
|
@@ -13,7 +13,8 @@ import {
|
|
|
13
13
|
|
|
14
14
|
describe('GceProxyLoadBalancerModal', () => {
|
|
15
15
|
const application = {
|
|
16
|
-
|
|
16
|
+
getDataSource: vi.fn(),
|
|
17
|
+
loadBalancers: { onNextRefresh: vi.fn(), refresh: vi.fn() },
|
|
17
18
|
name: 'app',
|
|
18
19
|
} as any;
|
|
19
20
|
|
|
@@ -50,14 +51,14 @@ describe('GceProxyLoadBalancerModal', () => {
|
|
|
50
51
|
|
|
51
52
|
expect(command.mode).toBe('edit');
|
|
52
53
|
expect(command.listeners[0]).toEqual(
|
|
53
|
-
|
|
54
|
+
expect.objectContaining({
|
|
54
55
|
address: { name: 'removed-address', selfLink: 'projects/test/global/addresses/removed-address' },
|
|
55
56
|
certificate: { name: 'removed-cert', selfLink: 'projects/test/global/sslCertificates/removed-cert' },
|
|
56
57
|
protocol: 'SSL',
|
|
57
58
|
}),
|
|
58
59
|
);
|
|
59
60
|
expect(command.backendServices[0]).toEqual(
|
|
60
|
-
|
|
61
|
+
expect.objectContaining({
|
|
61
62
|
affinityCookieTtlSec: 120,
|
|
62
63
|
connectionDrainingTimeoutSec: 30,
|
|
63
64
|
healthCheck: {
|
|
@@ -71,7 +72,7 @@ describe('GceProxyLoadBalancerModal', () => {
|
|
|
71
72
|
}),
|
|
72
73
|
);
|
|
73
74
|
expect(command.healthChecks[0]).toEqual(
|
|
74
|
-
|
|
75
|
+
expect.objectContaining({ healthCheckType: 'TCP', name: 'removed-check', port: 443, timeoutSec: 5 }),
|
|
75
76
|
);
|
|
76
77
|
});
|
|
77
78
|
|
|
@@ -114,10 +115,10 @@ describe('GceProxyLoadBalancerModal', () => {
|
|
|
114
115
|
|
|
115
116
|
expect(command.listeners[0].protocol).toBe('SSL');
|
|
116
117
|
expect(payload).toEqual(
|
|
117
|
-
|
|
118
|
-
backendService:
|
|
118
|
+
expect.objectContaining({
|
|
119
|
+
backendService: expect.objectContaining({
|
|
119
120
|
connectionDrainingTimeoutSec: 30,
|
|
120
|
-
healthCheck:
|
|
121
|
+
healthCheck: expect.objectContaining({
|
|
121
122
|
checkIntervalSec: 15,
|
|
122
123
|
healthCheckType: 'HTTP',
|
|
123
124
|
healthyThreshold: 2,
|
|
@@ -175,7 +176,7 @@ describe('GceProxyLoadBalancerModal', () => {
|
|
|
175
176
|
const payload = serializeGceProxyLoadBalancerCommand(command);
|
|
176
177
|
|
|
177
178
|
expect(payload).toEqual(
|
|
178
|
-
|
|
179
|
+
expect.objectContaining({
|
|
179
180
|
ipProtocol: 'UDP',
|
|
180
181
|
network: 'network-a',
|
|
181
182
|
ports: ['80', '443'],
|
|
@@ -187,22 +188,22 @@ describe('GceProxyLoadBalancerModal', () => {
|
|
|
187
188
|
});
|
|
188
189
|
|
|
189
190
|
it('returns the operation payload through the modal in pipeline mode without starting a task', () => {
|
|
190
|
-
const closeModal =
|
|
191
|
-
const taskMonitor = { submit:
|
|
191
|
+
const closeModal = vi.fn();
|
|
192
|
+
const taskMonitor = { submit: vi.fn() } as any;
|
|
192
193
|
const command = validCommand('pipeline');
|
|
193
194
|
|
|
194
195
|
submitGceProxyLoadBalancerCommand(command, { application, closeModal, taskMonitor });
|
|
195
196
|
|
|
196
|
-
expect(closeModal).
|
|
197
|
+
expect(closeModal).toHaveBeenCalledExactlyOnceWith(serializeGceProxyLoadBalancerCommand(command));
|
|
197
198
|
expect(taskMonitor.submit).not.toHaveBeenCalled();
|
|
198
199
|
});
|
|
199
200
|
|
|
200
201
|
(['create', 'edit'] as const).forEach((mode) => {
|
|
201
202
|
it(`submits the Clouddriver payload through LoadBalancerWriter in infrastructure ${mode} mode`, () => {
|
|
202
|
-
const upsert = spyOn(LoadBalancerWriter, 'upsertLoadBalancer').
|
|
203
|
+
const upsert = vi.spyOn(LoadBalancerWriter, 'upsertLoadBalancer').mockReturnValue(new Promise(() => {}));
|
|
203
204
|
let operation: (() => PromiseLike<any>) | undefined;
|
|
204
205
|
const taskMonitor = {
|
|
205
|
-
submit:
|
|
206
|
+
submit: vi.fn().mockImplementation((submitOperation: () => PromiseLike<any>) => {
|
|
206
207
|
operation = submitOperation;
|
|
207
208
|
}),
|
|
208
209
|
} as any;
|
|
@@ -210,7 +211,7 @@ describe('GceProxyLoadBalancerModal', () => {
|
|
|
210
211
|
|
|
211
212
|
submitGceProxyLoadBalancerCommand(command, {
|
|
212
213
|
application,
|
|
213
|
-
closeModal:
|
|
214
|
+
closeModal: vi.fn(),
|
|
214
215
|
taskMonitor,
|
|
215
216
|
});
|
|
216
217
|
operation?.();
|
|
@@ -227,11 +228,11 @@ describe('GceProxyLoadBalancerModal', () => {
|
|
|
227
228
|
it('exposes pipeline support and locks infrastructure identity fields in edit mode', () => {
|
|
228
229
|
expect(GceProxyLoadBalancerModal.supportsPipelineConfig).toBe(true);
|
|
229
230
|
expect(typeof GceProxyLoadBalancerModal.show).toBe('function');
|
|
230
|
-
const
|
|
231
|
+
const { container } = render(
|
|
231
232
|
<GceProxyLoadBalancerModal
|
|
232
233
|
app={application}
|
|
233
|
-
closeModal={
|
|
234
|
-
dismissModal={
|
|
234
|
+
closeModal={vi.fn()}
|
|
235
|
+
dismissModal={vi.fn()}
|
|
235
236
|
isNew={false}
|
|
236
237
|
loadBalancer={
|
|
237
238
|
{
|
|
@@ -243,56 +244,50 @@ describe('GceProxyLoadBalancerModal', () => {
|
|
|
243
244
|
} as any
|
|
244
245
|
}
|
|
245
246
|
loadBalancerType="TCP"
|
|
247
|
+
readers={resolvedReaders()}
|
|
246
248
|
/>,
|
|
247
249
|
);
|
|
248
250
|
|
|
249
|
-
expect(
|
|
250
|
-
|
|
251
|
+
expect(screen.getByRole('heading', { name: 'Edit app-main' })).toBeInTheDocument();
|
|
252
|
+
['name', 'credentials', 'region'].forEach((field) => expect(fieldControl(container, field)).toBeDisabled());
|
|
251
253
|
});
|
|
252
254
|
|
|
253
|
-
it('owns its refresh subscription
|
|
254
|
-
const
|
|
255
|
-
const secondUnsubscribe = jasmine.createSpy('secondUnsubscribe');
|
|
255
|
+
it('owns its refresh subscription through public submission and unmount', async () => {
|
|
256
|
+
const unsubscribe = vi.fn();
|
|
256
257
|
const callbacks: Array<() => void> = [];
|
|
257
|
-
const onNextRefresh =
|
|
258
|
+
const onNextRefresh = vi.fn().mockImplementation((callback: () => void) => {
|
|
258
259
|
callbacks.push(callback);
|
|
259
|
-
return
|
|
260
|
+
return unsubscribe;
|
|
260
261
|
});
|
|
261
|
-
const refresh =
|
|
262
|
-
const closeModal =
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
loadBalancerType="TCP"
|
|
279
|
-
readers={readers}
|
|
280
|
-
/>,
|
|
262
|
+
const refresh = vi.fn();
|
|
263
|
+
const closeModal = vi.fn();
|
|
264
|
+
const task = { id: 'task' } as any;
|
|
265
|
+
vi.spyOn(LoadBalancerWriter, 'upsertLoadBalancer').mockResolvedValue(task);
|
|
266
|
+
vi.spyOn(TaskReader, 'waitUntilTaskCompletes').mockResolvedValue(task);
|
|
267
|
+
const router = new UIRouterReact();
|
|
268
|
+
const rendered = render(
|
|
269
|
+
<UIRouterContext.Provider value={router}>
|
|
270
|
+
<GceProxyLoadBalancerModal
|
|
271
|
+
application={{ getDataSource: vi.fn(), loadBalancers: { onNextRefresh, refresh }, name: 'app' } as any}
|
|
272
|
+
closeModal={closeModal}
|
|
273
|
+
isNew={false}
|
|
274
|
+
loadBalancer={proxyLoadBalancer()}
|
|
275
|
+
loadBalancerType="TCP"
|
|
276
|
+
readers={resolvedReaders()}
|
|
277
|
+
/>
|
|
278
|
+
</UIRouterContext.Provider>,
|
|
281
279
|
);
|
|
282
|
-
|
|
280
|
+
await waitFor(() => expect(screen.getByRole('button', { name: 'Update' })).toBeEnabled());
|
|
283
281
|
|
|
284
|
-
|
|
282
|
+
fireEvent.click(screen.getByRole('button', { name: 'Update' }));
|
|
283
|
+
await waitFor(() => expect(refresh).toHaveBeenCalled());
|
|
285
284
|
|
|
286
|
-
expect(onNextRefresh.
|
|
285
|
+
expect(onNextRefresh.mock.invocationCallOrder[0]).toBeLessThan(refresh.mock.invocationCallOrder[0]);
|
|
287
286
|
|
|
288
|
-
|
|
287
|
+
rendered.unmount();
|
|
288
|
+
callbacks[0]();
|
|
289
289
|
|
|
290
|
-
expect(
|
|
291
|
-
|
|
292
|
-
wrapper.unmount();
|
|
293
|
-
callbacks[1]();
|
|
294
|
-
|
|
295
|
-
expect(secondUnsubscribe).toHaveBeenCalledTimes(1);
|
|
290
|
+
expect(unsubscribe).toHaveBeenCalledTimes(1);
|
|
296
291
|
expect(closeModal).not.toHaveBeenCalled();
|
|
297
292
|
});
|
|
298
293
|
});
|
|
@@ -311,3 +306,36 @@ function validCommand(mode: 'create' | 'edit' | 'pipeline'): any {
|
|
|
311
306
|
'TCP',
|
|
312
307
|
);
|
|
313
308
|
}
|
|
309
|
+
|
|
310
|
+
function proxyLoadBalancer(): any {
|
|
311
|
+
return {
|
|
312
|
+
account: 'account-a',
|
|
313
|
+
backendService: { name: 'app-main', portName: 'tcp', sessionAffinity: 'NONE' },
|
|
314
|
+
healthChecks: [{ healthCheckType: 'TCP', name: 'check-a', port: 443 }],
|
|
315
|
+
loadBalancerType: 'TCP',
|
|
316
|
+
name: 'app-main',
|
|
317
|
+
portRange: '443',
|
|
318
|
+
region: 'global',
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function fieldControl(container: HTMLElement, name: string): HTMLInputElement | HTMLSelectElement {
|
|
323
|
+
const control = container.querySelector(`[data-field="${name}"] input, [data-field="${name}"] select`);
|
|
324
|
+
if (!(control instanceof HTMLInputElement || control instanceof HTMLSelectElement)) {
|
|
325
|
+
throw new Error(`No control found for ${name}`);
|
|
326
|
+
}
|
|
327
|
+
return control;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function resolvedReaders(): any {
|
|
331
|
+
return {
|
|
332
|
+
accounts: () => Promise.resolve([]),
|
|
333
|
+
addresses: () => Promise.resolve([]),
|
|
334
|
+
backendServices: () => Promise.resolve([]),
|
|
335
|
+
certificates: () => Promise.resolve([]),
|
|
336
|
+
healthChecks: () => Promise.resolve([]),
|
|
337
|
+
networks: () => Promise.resolve([]),
|
|
338
|
+
regions: () => Promise.resolve([]),
|
|
339
|
+
subnets: () => Promise.resolve([]),
|
|
340
|
+
};
|
|
341
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Mocked } from 'vitest';
|
|
1
2
|
import {
|
|
2
3
|
GceLoadBalancerDataController,
|
|
3
4
|
IGceLoadBalancerDataReaders,
|
|
@@ -39,7 +40,7 @@ describe('GCE load balancer data', () => {
|
|
|
39
40
|
const first = deferred<unknown[]>();
|
|
40
41
|
const second = deferred<unknown[]>();
|
|
41
42
|
const readers = testReaders();
|
|
42
|
-
readers.regions.
|
|
43
|
+
readers.regions.mockImplementation((account: string) => (account === 'first' ? first.promise : second.promise));
|
|
43
44
|
const controller = new GceLoadBalancerDataController(readers);
|
|
44
45
|
|
|
45
46
|
const firstLoad = controller.load('first');
|
|
@@ -56,7 +57,7 @@ describe('GCE load balancer data', () => {
|
|
|
56
57
|
it('reports an error from the latest request', async () => {
|
|
57
58
|
const readers = testReaders();
|
|
58
59
|
const failure = new Error('regions failed');
|
|
59
|
-
readers.regions.
|
|
60
|
+
readers.regions.mockReturnValue(Promise.reject(failure));
|
|
60
61
|
const controller = new GceLoadBalancerDataController(readers);
|
|
61
62
|
|
|
62
63
|
await controller.load('test-account');
|
|
@@ -68,13 +69,13 @@ describe('GCE load balancer data', () => {
|
|
|
68
69
|
it('does not publish responses after disposal', async () => {
|
|
69
70
|
const pendingRegions = deferred<unknown[]>();
|
|
70
71
|
const readers = testReaders();
|
|
71
|
-
readers.regions.
|
|
72
|
+
readers.regions.mockReturnValue(pendingRegions.promise);
|
|
72
73
|
const controller = new GceLoadBalancerDataController(readers);
|
|
73
|
-
const listener =
|
|
74
|
+
const listener = vi.fn();
|
|
74
75
|
controller.subscribe(listener);
|
|
75
76
|
|
|
76
77
|
const loading = controller.load('test-account');
|
|
77
|
-
listener.
|
|
78
|
+
listener.mockClear();
|
|
78
79
|
controller.dispose();
|
|
79
80
|
pendingRegions.resolve([{ name: 'late-region' }]);
|
|
80
81
|
await loading;
|
|
@@ -97,17 +98,17 @@ describe('GCE load balancer data', () => {
|
|
|
97
98
|
});
|
|
98
99
|
});
|
|
99
100
|
|
|
100
|
-
function testReaders():
|
|
101
|
-
return
|
|
102
|
-
accounts: Promise.resolve([{ name: 'test-account' }]),
|
|
103
|
-
addresses: Promise.resolve([{ name: 'address' }]),
|
|
104
|
-
backendServices: Promise.resolve([{ name: 'backend' }]),
|
|
105
|
-
certificates: Promise.resolve([{ name: 'certificate' }]),
|
|
106
|
-
healthChecks: Promise.resolve([{ name: 'check' }]),
|
|
107
|
-
networks: Promise.resolve([{ name: 'network' }]),
|
|
108
|
-
regions: Promise.resolve([{ name: 'europe-west1' }]),
|
|
109
|
-
subnets: Promise.resolve([{ name: 'subnet' }]),
|
|
110
|
-
})
|
|
101
|
+
function testReaders(): Mocked<IGceLoadBalancerDataReaders> {
|
|
102
|
+
return ({
|
|
103
|
+
accounts: vi.fn().mockReturnValue(Promise.resolve([{ name: 'test-account' }])),
|
|
104
|
+
addresses: vi.fn().mockReturnValue(Promise.resolve([{ name: 'address' }])),
|
|
105
|
+
backendServices: vi.fn().mockReturnValue(Promise.resolve([{ name: 'backend' }])),
|
|
106
|
+
certificates: vi.fn().mockReturnValue(Promise.resolve([{ name: 'certificate' }])),
|
|
107
|
+
healthChecks: vi.fn().mockReturnValue(Promise.resolve([{ name: 'check' }])),
|
|
108
|
+
networks: vi.fn().mockReturnValue(Promise.resolve([{ name: 'network' }])),
|
|
109
|
+
regions: vi.fn().mockReturnValue(Promise.resolve([{ name: 'europe-west1' }])),
|
|
110
|
+
subnets: vi.fn().mockReturnValue(Promise.resolve([{ name: 'subnet' }])),
|
|
111
|
+
} as unknown) as Mocked<IGceLoadBalancerDataReaders>;
|
|
111
112
|
}
|
|
112
113
|
|
|
113
114
|
function deferred<T>() {
|
|
@@ -19,14 +19,23 @@ describe('GCE load balancer models', () => {
|
|
|
19
19
|
);
|
|
20
20
|
|
|
21
21
|
expect(serializeGceLoadBalancerCommand(command)).toEqual(
|
|
22
|
-
|
|
23
|
-
healthChecks: [
|
|
24
|
-
listeners: [
|
|
22
|
+
expect.objectContaining({
|
|
23
|
+
healthChecks: [expect.objectContaining({ healthCheckType: 'GRPC', grpcServiceName: 'example.Health' })],
|
|
24
|
+
listeners: [expect.objectContaining({ certificateMap: 'shared-map' })],
|
|
25
25
|
}),
|
|
26
26
|
);
|
|
27
27
|
});
|
|
28
28
|
it('defines capabilities for every supported load balancer type', () => {
|
|
29
|
-
expect(GCE_LOAD_BALANCER_TYPES).toEqual([
|
|
29
|
+
expect(GCE_LOAD_BALANCER_TYPES).toEqual([
|
|
30
|
+
'NETWORK',
|
|
31
|
+
'INTERNAL',
|
|
32
|
+
'TCP',
|
|
33
|
+
'SSL',
|
|
34
|
+
'HTTP',
|
|
35
|
+
'INTERNAL_MANAGED',
|
|
36
|
+
'EXTERNAL_MANAGED',
|
|
37
|
+
'REGIONAL_EXTERNAL_NETWORK',
|
|
38
|
+
]);
|
|
30
39
|
expect(Object.keys(GCE_LOAD_BALANCER_CAPABILITIES)).toEqual(GCE_LOAD_BALANCER_TYPES);
|
|
31
40
|
expect(GCE_LOAD_BALANCER_CAPABILITIES.NETWORK).toEqual({
|
|
32
41
|
address: true,
|
|
@@ -359,7 +368,7 @@ describe('GCE load balancer models', () => {
|
|
|
359
368
|
);
|
|
360
369
|
|
|
361
370
|
expect(command.healthChecks).toEqual([
|
|
362
|
-
|
|
371
|
+
expect.objectContaining({
|
|
363
372
|
checkIntervalSec: 15,
|
|
364
373
|
healthCheckType: 'HTTP',
|
|
365
374
|
healthyThreshold: 2,
|
|
@@ -387,6 +396,222 @@ describe('GCE load balancer models', () => {
|
|
|
387
396
|
|
|
388
397
|
expect(command.healthChecks).toEqual([{ healthCheckType: 'HTTP', port: 53, requestPath: '/health' }]);
|
|
389
398
|
});
|
|
399
|
+
|
|
400
|
+
it('normalizes and serializes EXTERNAL_MANAGED with regional scope, network, address, tier, and certificate', () => {
|
|
401
|
+
const regionalCertUrl =
|
|
402
|
+
'//certificatemanager.googleapis.com/projects/test/locations/europe-west1/certificates/regional-cert';
|
|
403
|
+
const command = normalizeGceLoadBalancerCommand(
|
|
404
|
+
{
|
|
405
|
+
account: 'account-a',
|
|
406
|
+
backendServices: [
|
|
407
|
+
{
|
|
408
|
+
healthCheck: 'projects/test/regions/europe-west1/healthChecks/check-a',
|
|
409
|
+
name: 'backend-a',
|
|
410
|
+
},
|
|
411
|
+
],
|
|
412
|
+
defaultService: 'backend-a',
|
|
413
|
+
healthChecks: [{ healthCheckType: 'HTTP', name: 'check-a', port: 80, requestPath: '/health' }],
|
|
414
|
+
listeners: [
|
|
415
|
+
{
|
|
416
|
+
certificate: regionalCertUrl,
|
|
417
|
+
ipAddress: '203.0.113.10',
|
|
418
|
+
name: 'app-https',
|
|
419
|
+
networkTier: 'STANDARD',
|
|
420
|
+
port: 443,
|
|
421
|
+
protocol: 'HTTPS',
|
|
422
|
+
},
|
|
423
|
+
],
|
|
424
|
+
loadBalancerType: 'EXTERNAL_MANAGED',
|
|
425
|
+
name: 'app-main',
|
|
426
|
+
network: 'projects/test/global/networks/network-a',
|
|
427
|
+
region: 'europe-west1',
|
|
428
|
+
urlMapName: 'app-main',
|
|
429
|
+
},
|
|
430
|
+
'edit',
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
expect(command.loadBalancerType).toBe('EXTERNAL_MANAGED');
|
|
434
|
+
expect(command.region).toBe('europe-west1');
|
|
435
|
+
expect(command.network).toEqual({
|
|
436
|
+
name: 'network-a',
|
|
437
|
+
selfLink: 'projects/test/global/networks/network-a',
|
|
438
|
+
});
|
|
439
|
+
expect(command.listeners).toEqual([
|
|
440
|
+
{
|
|
441
|
+
address: { name: '203.0.113.10' },
|
|
442
|
+
certificate: { name: 'regional-cert', selfLink: regionalCertUrl },
|
|
443
|
+
name: 'app-https',
|
|
444
|
+
networkTier: 'STANDARD',
|
|
445
|
+
portRange: '443',
|
|
446
|
+
protocol: 'HTTPS',
|
|
447
|
+
},
|
|
448
|
+
]);
|
|
449
|
+
expect(command.listeners[0].certificateMap).toBeUndefined();
|
|
450
|
+
|
|
451
|
+
const serialized = serializeGceLoadBalancerCommand(command);
|
|
452
|
+
expect(serialized).toEqual(
|
|
453
|
+
expect.objectContaining({
|
|
454
|
+
credentials: 'account-a',
|
|
455
|
+
loadBalancerType: 'EXTERNAL_MANAGED',
|
|
456
|
+
network: 'network-a',
|
|
457
|
+
region: 'europe-west1',
|
|
458
|
+
}),
|
|
459
|
+
);
|
|
460
|
+
expect(serialized.listeners).toEqual([
|
|
461
|
+
{
|
|
462
|
+
certificate: regionalCertUrl,
|
|
463
|
+
ipAddress: '203.0.113.10',
|
|
464
|
+
name: 'app-https',
|
|
465
|
+
networkTier: 'STANDARD',
|
|
466
|
+
portRange: '443',
|
|
467
|
+
protocol: 'HTTPS',
|
|
468
|
+
},
|
|
469
|
+
]);
|
|
470
|
+
expect((serialized.listeners as any[])[0].certificateMap).toBeUndefined();
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
it('preserves EXTERNAL_MANAGED listener identity from a flat pipeline operation', () => {
|
|
474
|
+
const command = normalizeGceLoadBalancerCommand(
|
|
475
|
+
{
|
|
476
|
+
certificate:
|
|
477
|
+
'//certificatemanager.googleapis.com/projects/test/locations/europe-west1/certificates/regional-cert',
|
|
478
|
+
credentials: 'account-a',
|
|
479
|
+
ipAddress: '203.0.113.10',
|
|
480
|
+
ipProtocol: 'TCP',
|
|
481
|
+
loadBalancerName: 'app-https',
|
|
482
|
+
loadBalancerType: 'EXTERNAL_MANAGED',
|
|
483
|
+
name: 'app-https',
|
|
484
|
+
network: 'network-a',
|
|
485
|
+
networkTier: 'STANDARD',
|
|
486
|
+
portRange: '443',
|
|
487
|
+
region: 'europe-west1',
|
|
488
|
+
urlMapName: 'app-main',
|
|
489
|
+
},
|
|
490
|
+
'pipeline',
|
|
491
|
+
);
|
|
492
|
+
|
|
493
|
+
expect(command.name).toBe('app-main');
|
|
494
|
+
expect(command.listeners).toEqual([
|
|
495
|
+
{
|
|
496
|
+
address: { name: '203.0.113.10' },
|
|
497
|
+
certificate: {
|
|
498
|
+
name: 'regional-cert',
|
|
499
|
+
selfLink:
|
|
500
|
+
'//certificatemanager.googleapis.com/projects/test/locations/europe-west1/certificates/regional-cert',
|
|
501
|
+
},
|
|
502
|
+
name: 'app-https',
|
|
503
|
+
networkTier: 'STANDARD',
|
|
504
|
+
portRange: '443',
|
|
505
|
+
protocol: 'HTTPS',
|
|
506
|
+
},
|
|
507
|
+
]);
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
it('preserves REGIONAL_EXTERNAL_NETWORK port text for validation and source immutability', () => {
|
|
511
|
+
const command = normalizeGceLoadBalancerCommand(
|
|
512
|
+
{
|
|
513
|
+
account: 'account-a',
|
|
514
|
+
backendService: {
|
|
515
|
+
healthCheck: { healthCheckType: 'TCP', name: 'tcp-check', port: 80 },
|
|
516
|
+
name: 'app-main',
|
|
517
|
+
sessionAffinity: 'CLIENT_IP',
|
|
518
|
+
},
|
|
519
|
+
ipAddress: '35.1.2.3',
|
|
520
|
+
ipProtocol: 'TCP',
|
|
521
|
+
loadBalancerName: 'app-main',
|
|
522
|
+
loadBalancerType: 'REGIONAL_EXTERNAL_NETWORK',
|
|
523
|
+
networkTier: 'PREMIUM',
|
|
524
|
+
ports: ['80', ' 443 '],
|
|
525
|
+
region: 'europe-west1',
|
|
526
|
+
},
|
|
527
|
+
'edit',
|
|
528
|
+
);
|
|
529
|
+
|
|
530
|
+
expect(command.loadBalancerType).toBe('REGIONAL_EXTERNAL_NETWORK');
|
|
531
|
+
expect(command.listeners).toEqual([
|
|
532
|
+
{
|
|
533
|
+
address: { name: '35.1.2.3' },
|
|
534
|
+
name: 'app-main',
|
|
535
|
+
portRange: '80, 443 ',
|
|
536
|
+
protocol: 'TCP',
|
|
537
|
+
},
|
|
538
|
+
]);
|
|
539
|
+
expect(command.networkTier).toBe('PREMIUM');
|
|
540
|
+
expect(command.backendServices).toEqual([
|
|
541
|
+
{
|
|
542
|
+
healthCheck: { healthCheckType: 'TCP', name: 'tcp-check', port: 80 },
|
|
543
|
+
name: 'app-main',
|
|
544
|
+
sessionAffinity: 'CLIENT_IP',
|
|
545
|
+
},
|
|
546
|
+
]);
|
|
547
|
+
expect(command.original).toEqual({
|
|
548
|
+
backendServices: command.backendServices,
|
|
549
|
+
healthChecks: command.healthChecks,
|
|
550
|
+
listeners: command.listeners,
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
command.listeners[0].protocol = 'UDP';
|
|
554
|
+
command.backendServices[0].sessionAffinity = 'CLIENT_IP_PROTO';
|
|
555
|
+
|
|
556
|
+
expect(command.original?.listeners[0].protocol).toBe('TCP');
|
|
557
|
+
expect(command.original?.backendServices[0].sessionAffinity).toBe('CLIENT_IP');
|
|
558
|
+
|
|
559
|
+
const serialized = serializeGceLoadBalancerCommand(command);
|
|
560
|
+
expect(serialized).toEqual({
|
|
561
|
+
backendService: {
|
|
562
|
+
healthCheck: { healthCheckType: 'TCP', name: 'tcp-check', port: 80 },
|
|
563
|
+
name: 'app-main',
|
|
564
|
+
sessionAffinity: 'CLIENT_IP_PROTO',
|
|
565
|
+
},
|
|
566
|
+
cloudProvider: 'gce',
|
|
567
|
+
credentials: 'account-a',
|
|
568
|
+
ipAddress: '35.1.2.3',
|
|
569
|
+
ipProtocol: 'UDP',
|
|
570
|
+
loadBalancerName: 'app-main',
|
|
571
|
+
loadBalancerType: 'REGIONAL_EXTERNAL_NETWORK',
|
|
572
|
+
name: 'app-main',
|
|
573
|
+
networkTier: 'PREMIUM',
|
|
574
|
+
ports: ['80', ' 443 '],
|
|
575
|
+
provider: 'gce',
|
|
576
|
+
region: 'europe-west1',
|
|
577
|
+
type: 'upsertLoadBalancer',
|
|
578
|
+
});
|
|
579
|
+
expect(serialized.portRange).toBeUndefined();
|
|
580
|
+
expect(serialized.backendServices).toBeUndefined();
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
(['HTTP', 'INTERNAL_MANAGED'] as const).forEach((loadBalancerType) => {
|
|
584
|
+
it(`normalizes and serializes ${loadBalancerType} without listener networkTier`, () => {
|
|
585
|
+
const command = normalizeGceLoadBalancerCommand(
|
|
586
|
+
{
|
|
587
|
+
account: 'account-a',
|
|
588
|
+
listeners: [
|
|
589
|
+
{
|
|
590
|
+
ipAddress: '203.0.113.10',
|
|
591
|
+
name: 'frontend',
|
|
592
|
+
networkTier: 'STANDARD',
|
|
593
|
+
port: 80,
|
|
594
|
+
protocol: 'HTTP',
|
|
595
|
+
},
|
|
596
|
+
],
|
|
597
|
+
loadBalancerType,
|
|
598
|
+
name: 'web',
|
|
599
|
+
network: loadBalancerType === 'INTERNAL_MANAGED' ? 'network-a' : undefined,
|
|
600
|
+
networkTier: 'STANDARD',
|
|
601
|
+
region: loadBalancerType === 'INTERNAL_MANAGED' ? 'europe-west1' : 'global',
|
|
602
|
+
subnet: loadBalancerType === 'INTERNAL_MANAGED' ? 'subnet-a' : undefined,
|
|
603
|
+
},
|
|
604
|
+
'create',
|
|
605
|
+
);
|
|
606
|
+
|
|
607
|
+
expect(command.listeners[0].networkTier).toBeUndefined();
|
|
608
|
+
expect(command.networkTier).toBeUndefined();
|
|
609
|
+
|
|
610
|
+
const serialized = serializeGceLoadBalancerCommand(command);
|
|
611
|
+
expect(serialized.networkTier).toBeUndefined();
|
|
612
|
+
expect((serialized.listeners as any[])[0].networkTier).toBeUndefined();
|
|
613
|
+
});
|
|
614
|
+
});
|
|
390
615
|
});
|
|
391
616
|
|
|
392
617
|
function readerHealthCheck() {
|