@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,28 +1,34 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fireEvent, render, screen } from '@testing-library/react';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { AccountService } from '@spinnaker/core';
|
|
5
5
|
|
|
6
6
|
import { GceFindImageStageConfig } from './googleStageConfig';
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
});
|
|
13
|
-
const field = wrapper.find(StageConfigField).filterWhere((node) => node.prop('label') === 'Server Group Filters');
|
|
8
|
+
vi.mock('@spinnaker/core', async (importOriginal) => {
|
|
9
|
+
const actual = await importOriginal();
|
|
10
|
+
return { ...actual, AccountRegionClusterSelector: () => null };
|
|
11
|
+
});
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
describe('GCE find image onlyEnabled control', () => {
|
|
14
|
+
beforeEach(() => vi.spyOn(AccountService, 'listAccounts').mockResolvedValue([]));
|
|
15
|
+
|
|
16
|
+
function renderStage(stage, updateStage = vi.fn()) {
|
|
17
|
+
const rendered = render(React.createElement(GceFindImageStageConfig, { application: {}, stage, updateStage }));
|
|
18
|
+
return {
|
|
19
|
+
...rendered,
|
|
20
|
+
checkbox: screen.getByRole('checkbox', { name: 'Only consider enabled Server Groups' }),
|
|
21
|
+
updateStage,
|
|
22
|
+
};
|
|
16
23
|
}
|
|
17
24
|
|
|
18
25
|
it('defaults to considering only enabled server groups', () => {
|
|
19
26
|
const stage = {};
|
|
20
|
-
const { checkbox
|
|
27
|
+
const { checkbox } = renderStage(stage);
|
|
21
28
|
|
|
22
29
|
expect(stage.onlyEnabled).toBe(true);
|
|
23
|
-
expect(
|
|
24
|
-
expect(
|
|
25
|
-
expect(checkbox.prop('checked')).toBe(true);
|
|
30
|
+
expect(screen.getByText('Server Group Filters')).toBeInTheDocument();
|
|
31
|
+
expect(checkbox).toBeChecked();
|
|
26
32
|
});
|
|
27
33
|
|
|
28
34
|
it('preserves an explicit false value', () => {
|
|
@@ -30,15 +36,15 @@ describe('GCE find image onlyEnabled control', () => {
|
|
|
30
36
|
const { checkbox } = renderStage(stage);
|
|
31
37
|
|
|
32
38
|
expect(stage.onlyEnabled).toBe(false);
|
|
33
|
-
expect(checkbox
|
|
39
|
+
expect(checkbox).not.toBeChecked();
|
|
34
40
|
});
|
|
35
41
|
|
|
36
42
|
it('updates onlyEnabled with the checkbox value', () => {
|
|
37
43
|
const stage = { onlyEnabled: true };
|
|
38
44
|
const { checkbox, updateStage } = renderStage(stage);
|
|
39
45
|
|
|
40
|
-
|
|
46
|
+
fireEvent.click(checkbox);
|
|
41
47
|
|
|
42
|
-
expect(updateStage).toHaveBeenCalledWith(
|
|
48
|
+
expect(updateStage).toHaveBeenCalledWith(expect.objectContaining({ onlyEnabled: false }));
|
|
43
49
|
});
|
|
44
50
|
});
|
|
@@ -1,25 +1,36 @@
|
|
|
1
|
+
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
2
|
+
import { UIRouterContext, UIRouterReact } from '@uirouter/react';
|
|
1
3
|
import React from 'react';
|
|
2
|
-
import { mount as enzymeMount } from 'enzyme';
|
|
3
4
|
|
|
4
|
-
import { DeckRuntimeContext, SecurityGroupWriter,
|
|
5
|
+
import { DeckRuntimeContext, SecurityGroupWriter, TaskReader } from '@spinnaker/core';
|
|
5
6
|
|
|
6
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
GceSecurityGroupModalComponent as GceSecurityGroupModal,
|
|
9
|
+
initializeGceSecurityGroupForModal,
|
|
10
|
+
} from './GceSecurityGroupModal';
|
|
7
11
|
|
|
8
12
|
describe('GceSecurityGroupModal', () => {
|
|
9
13
|
let runtimeServices: any;
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
const application = {
|
|
15
|
+
getDataSource: vi.fn(),
|
|
16
|
+
name: 'my-app',
|
|
17
|
+
securityGroups: { refresh: vi.fn() },
|
|
18
|
+
};
|
|
14
19
|
|
|
15
20
|
beforeEach(() => {
|
|
16
21
|
runtimeServices = { securityGroupReader: { getAllSecurityGroups: () => Promise.resolve({}) } };
|
|
17
22
|
});
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
function renderModal(component: React.ReactElement) {
|
|
25
|
+
const router = new UIRouterReact();
|
|
26
|
+
return render(
|
|
27
|
+
<UIRouterContext.Provider value={router}>
|
|
28
|
+
<DeckRuntimeContext.Provider value={{ services: runtimeServices } as any}>
|
|
29
|
+
{component}
|
|
30
|
+
</DeckRuntimeContext.Provider>
|
|
31
|
+
</UIRouterContext.Provider>,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
23
34
|
|
|
24
35
|
function validSecurityGroup(overrides: any = {}): any {
|
|
25
36
|
return {
|
|
@@ -51,43 +62,43 @@ describe('GceSecurityGroupModal', () => {
|
|
|
51
62
|
};
|
|
52
63
|
}
|
|
53
64
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
65
|
+
function fillValidSecurityGroup(name = 'new-firewall'): void {
|
|
66
|
+
fireEvent.change(screen.getByRole('textbox', { name: 'Name' }), { target: { value: name } });
|
|
67
|
+
fireEvent.change(screen.getByRole('textbox', { name: 'Source CIDRs' }), {
|
|
68
|
+
target: { value: '10.0.0.0/8' },
|
|
69
|
+
});
|
|
70
|
+
fireEvent.change(screen.getByRole('spinbutton', { name: 'Start port 1' }), { target: { value: '443' } });
|
|
71
|
+
fireEvent.change(screen.getByRole('spinbutton', { name: 'End port 1' }), { target: { value: '443' } });
|
|
57
72
|
}
|
|
58
73
|
|
|
59
74
|
it('keeps create submit disabled until the global firewall inventory has loaded', async () => {
|
|
60
75
|
let finishLoading: ((securityGroups: any) => void) | undefined;
|
|
61
|
-
const getAllSecurityGroups =
|
|
76
|
+
const getAllSecurityGroups = vi.fn().mockReturnValue(
|
|
62
77
|
new Promise((resolve) => {
|
|
63
78
|
finishLoading = resolve;
|
|
64
79
|
}),
|
|
65
80
|
);
|
|
66
81
|
runtimeServices.securityGroupReader = { getAllSecurityGroups };
|
|
67
|
-
|
|
82
|
+
renderModal(
|
|
68
83
|
<GceSecurityGroupModal
|
|
69
84
|
application={{ ...application, securityGroups: { data: [] } } as any}
|
|
70
85
|
credentials="my-account"
|
|
71
86
|
/>,
|
|
72
87
|
);
|
|
73
|
-
|
|
88
|
+
fillValidSecurityGroup();
|
|
74
89
|
|
|
75
|
-
expect(
|
|
90
|
+
expect(screen.getByRole('button', { name: 'Create' })).toBeDisabled();
|
|
76
91
|
|
|
77
|
-
finishLoading?.(globalSecurityGroups());
|
|
78
|
-
await flush();
|
|
79
|
-
wrapper.update();
|
|
92
|
+
await act(async () => finishLoading?.(globalSecurityGroups()));
|
|
80
93
|
|
|
81
|
-
|
|
94
|
+
await waitFor(() => expect(screen.getByRole('button', { name: 'Create' })).toBeEnabled());
|
|
82
95
|
});
|
|
83
96
|
|
|
84
97
|
(['create', 'clone'] as const).forEach((mode) => {
|
|
85
98
|
it(`rejects a ${mode} name used in the same account on another network outside the application`, async () => {
|
|
86
|
-
const getAllSecurityGroups =
|
|
87
|
-
.createSpy('getAllSecurityGroups')
|
|
88
|
-
.and.returnValue(Promise.resolve(globalSecurityGroups()));
|
|
99
|
+
const getAllSecurityGroups = vi.fn().mockResolvedValue(globalSecurityGroups());
|
|
89
100
|
runtimeServices.securityGroupReader = { getAllSecurityGroups };
|
|
90
|
-
|
|
101
|
+
renderModal(
|
|
91
102
|
<GceSecurityGroupModal
|
|
92
103
|
application={{ ...application, securityGroups: { data: [] } } as any}
|
|
93
104
|
credentials="my-account"
|
|
@@ -95,87 +106,81 @@ describe('GceSecurityGroupModal', () => {
|
|
|
95
106
|
securityGroup={mode === 'clone' ? validSecurityGroup({ name: 'source-firewall' }) : undefined}
|
|
96
107
|
/>,
|
|
97
108
|
);
|
|
98
|
-
|
|
109
|
+
fillValidSecurityGroup('existing-firewall');
|
|
99
110
|
|
|
100
|
-
await
|
|
101
|
-
wrapper.update();
|
|
111
|
+
await waitFor(() => expect(getAllSecurityGroups).toHaveBeenCalled());
|
|
102
112
|
|
|
103
|
-
expect(
|
|
104
|
-
expect(wrapper.find(SubmitButton).prop('isDisabled')).toBe(true);
|
|
113
|
+
expect(screen.getByRole('button', { name: mode === 'clone' ? 'Clone' : 'Create' })).toBeDisabled();
|
|
105
114
|
});
|
|
106
115
|
});
|
|
107
116
|
|
|
108
117
|
it('permits a name used only in another account and permits editing the current firewall identity', async () => {
|
|
109
|
-
const getAllSecurityGroups =
|
|
110
|
-
.createSpy('getAllSecurityGroups')
|
|
111
|
-
.and.returnValue(Promise.resolve(globalSecurityGroups()));
|
|
118
|
+
const getAllSecurityGroups = vi.fn().mockResolvedValue(globalSecurityGroups());
|
|
112
119
|
runtimeServices.securityGroupReader = { getAllSecurityGroups };
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
120
|
+
const create = renderModal(<GceSecurityGroupModal application={application as any} credentials="my-account" />);
|
|
121
|
+
fillValidSecurityGroup('cross-account-firewall');
|
|
122
|
+
await waitFor(() => expect(screen.getByRole('button', { name: 'Create' })).toBeEnabled());
|
|
123
|
+
create.unmount();
|
|
124
|
+
|
|
125
|
+
renderModal(
|
|
116
126
|
<GceSecurityGroupModal application={application as any} mode="edit" securityGroup={validSecurityGroup()} />,
|
|
117
127
|
);
|
|
118
|
-
|
|
119
|
-
await flush();
|
|
120
|
-
createWrapper.update();
|
|
121
|
-
editWrapper.update();
|
|
122
|
-
|
|
123
|
-
expect(createWrapper.find(SubmitButton).prop('isDisabled')).toBe(false);
|
|
124
|
-
expect(editWrapper.find(SubmitButton).prop('isDisabled')).toBe(false);
|
|
128
|
+
expect(screen.getByRole('button', { name: 'Update' })).toBeEnabled();
|
|
125
129
|
});
|
|
126
130
|
|
|
127
131
|
it('keeps create submit disabled when the global firewall inventory cannot be loaded', async () => {
|
|
128
|
-
const getAllSecurityGroups =
|
|
129
|
-
.createSpy('getAllSecurityGroups')
|
|
130
|
-
.and.returnValue(Promise.reject(new Error('inventory unavailable')));
|
|
132
|
+
const getAllSecurityGroups = vi.fn().mockRejectedValue(new Error('inventory unavailable'));
|
|
131
133
|
runtimeServices.securityGroupReader = { getAllSecurityGroups };
|
|
132
|
-
|
|
133
|
-
|
|
134
|
+
renderModal(<GceSecurityGroupModal application={application as any} credentials="my-account" />);
|
|
135
|
+
fillValidSecurityGroup();
|
|
134
136
|
|
|
135
|
-
await
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
expect(wrapper.find(SubmitButton).prop('isDisabled')).toBe(true);
|
|
139
|
-
expect(wrapper.find('.security-group-validation-error').text()).toContain('Unable to validate firewall name');
|
|
137
|
+
expect(await screen.findByText('Unable to validate firewall name.')).toBeInTheDocument();
|
|
138
|
+
expect(screen.getByRole('button', { name: 'Create' })).toBeDisabled();
|
|
140
139
|
});
|
|
141
140
|
|
|
142
141
|
(['create', 'clone'] as const).forEach((mode) => {
|
|
143
|
-
it(`waits for refreshed security groups before closing and navigating after ${mode}`, () => {
|
|
142
|
+
it(`waits for refreshed security groups before closing and navigating after ${mode}`, async () => {
|
|
144
143
|
let finishRefresh: (() => void) | undefined;
|
|
145
|
-
const refresh =
|
|
146
|
-
const onNextRefresh =
|
|
144
|
+
const refresh = vi.fn();
|
|
145
|
+
const onNextRefresh = vi.fn().mockImplementation((callback: () => void) => {
|
|
147
146
|
finishRefresh = callback;
|
|
148
147
|
return () => undefined;
|
|
149
148
|
});
|
|
150
|
-
const closeModal =
|
|
151
|
-
const
|
|
152
|
-
go:
|
|
153
|
-
includes:
|
|
149
|
+
const closeModal = vi.fn();
|
|
150
|
+
const stateService = {
|
|
151
|
+
go: vi.fn(),
|
|
152
|
+
includes: vi.fn().mockReturnValue(mode === 'clone'),
|
|
154
153
|
};
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
(
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
154
|
+
const task = { id: 'task' } as any;
|
|
155
|
+
vi.spyOn(SecurityGroupWriter, 'upsertSecurityGroup').mockResolvedValue(task);
|
|
156
|
+
vi.spyOn(TaskReader, 'waitUntilTaskCompletes').mockResolvedValue(task);
|
|
157
|
+
renderModal(
|
|
158
|
+
<GceSecurityGroupModal
|
|
159
|
+
application={{ getDataSource: vi.fn(), name: 'my-app', securityGroups: { onNextRefresh, refresh } }}
|
|
160
|
+
closeModal={closeModal}
|
|
161
|
+
credentials="my-account"
|
|
162
|
+
mode={mode}
|
|
163
|
+
securityGroup={mode === 'clone' ? validSecurityGroup({ name: 'source-firewall' }) : undefined}
|
|
164
|
+
stateService={stateService as any}
|
|
165
|
+
/>,
|
|
166
|
+
);
|
|
167
|
+
fillValidSecurityGroup(' new-firewall ');
|
|
168
|
+
fireEvent.change(screen.getByRole('textbox', { name: 'Network' }), { target: { value: ' default ' } });
|
|
169
|
+
|
|
170
|
+
await waitFor(() =>
|
|
171
|
+
expect(screen.getByRole('button', { name: mode === 'clone' ? 'Clone' : 'Create' })).toBeEnabled(),
|
|
172
|
+
);
|
|
173
|
+
fireEvent.click(screen.getByRole('button', { name: mode === 'clone' ? 'Clone' : 'Create' }));
|
|
174
|
+
await waitFor(() => expect(refresh).toHaveBeenCalled());
|
|
175
|
+
|
|
176
|
+
expect(onNextRefresh.mock.invocationCallOrder[0]).toBeLessThan(refresh.mock.invocationCallOrder[0]);
|
|
172
177
|
expect(closeModal).not.toHaveBeenCalled();
|
|
173
|
-
expect(
|
|
178
|
+
expect(stateService.go).not.toHaveBeenCalled();
|
|
174
179
|
|
|
175
180
|
finishRefresh?.();
|
|
176
181
|
|
|
177
182
|
expect(closeModal).toHaveBeenCalled();
|
|
178
|
-
expect(
|
|
183
|
+
expect(stateService.go).toHaveBeenCalledWith(mode === 'clone' ? '^.firewallDetails' : '.firewallDetails', {
|
|
179
184
|
accountId: 'my-account',
|
|
180
185
|
name: 'new-firewall',
|
|
181
186
|
provider: 'gce',
|
|
@@ -185,65 +190,65 @@ describe('GceSecurityGroupModal', () => {
|
|
|
185
190
|
});
|
|
186
191
|
});
|
|
187
192
|
|
|
188
|
-
it('owns its refresh subscription
|
|
189
|
-
const
|
|
190
|
-
const secondUnsubscribe = jasmine.createSpy('secondUnsubscribe');
|
|
193
|
+
it('owns its refresh subscription through unmount', async () => {
|
|
194
|
+
const unsubscribe = vi.fn();
|
|
191
195
|
const callbacks: Array<() => void> = [];
|
|
192
|
-
const onNextRefresh =
|
|
196
|
+
const onNextRefresh = vi.fn().mockImplementation((callback: () => void) => {
|
|
193
197
|
callbacks.push(callback);
|
|
194
|
-
return
|
|
198
|
+
return unsubscribe;
|
|
195
199
|
});
|
|
196
|
-
const refresh =
|
|
197
|
-
const closeModal =
|
|
198
|
-
const stateService = { go:
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
modal.onTaskComplete();
|
|
200
|
+
const refresh = vi.fn();
|
|
201
|
+
const closeModal = vi.fn();
|
|
202
|
+
const stateService = { go: vi.fn(), includes: vi.fn() };
|
|
203
|
+
const task = { id: 'task' } as any;
|
|
204
|
+
vi.spyOn(SecurityGroupWriter, 'upsertSecurityGroup').mockResolvedValue(task);
|
|
205
|
+
vi.spyOn(TaskReader, 'waitUntilTaskCompletes').mockResolvedValue(task);
|
|
206
|
+
const rendered = renderModal(
|
|
207
|
+
<GceSecurityGroupModal
|
|
208
|
+
application={{ getDataSource: vi.fn(), name: 'my-app', securityGroups: { onNextRefresh, refresh } }}
|
|
209
|
+
closeModal={closeModal}
|
|
210
|
+
credentials="my-account"
|
|
211
|
+
stateService={stateService as any}
|
|
212
|
+
/>,
|
|
213
|
+
);
|
|
214
|
+
fillValidSecurityGroup();
|
|
212
215
|
|
|
213
|
-
expect(
|
|
216
|
+
await waitFor(() => expect(screen.getByRole('button', { name: 'Create' })).toBeEnabled());
|
|
217
|
+
fireEvent.click(screen.getByRole('button', { name: 'Create' }));
|
|
218
|
+
await waitFor(() => expect(refresh).toHaveBeenCalled());
|
|
219
|
+
expect(onNextRefresh.mock.invocationCallOrder[0]).toBeLessThan(refresh.mock.invocationCallOrder[0]);
|
|
214
220
|
|
|
215
|
-
|
|
216
|
-
callbacks[
|
|
221
|
+
rendered.unmount();
|
|
222
|
+
callbacks[0]();
|
|
217
223
|
|
|
218
|
-
expect(
|
|
219
|
-
expect(modal.applicationRefreshUnsubscribe).toBeUndefined();
|
|
224
|
+
expect(unsubscribe).toHaveBeenCalledTimes(1);
|
|
220
225
|
expect(closeModal).not.toHaveBeenCalled();
|
|
221
226
|
expect(stateService.go).not.toHaveBeenCalled();
|
|
222
227
|
});
|
|
223
228
|
|
|
224
|
-
it('submits multiple protocol and port-range rows using the GCE firewall operation contract', () => {
|
|
225
|
-
|
|
226
|
-
const
|
|
227
|
-
(
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
(
|
|
240
|
-
|
|
241
|
-
expect(upsert.
|
|
229
|
+
it('submits multiple protocol and port-range rows using the GCE firewall operation contract', async () => {
|
|
230
|
+
runtimeServices.securityGroupReader = { getAllSecurityGroups: vi.fn().mockResolvedValue({}) };
|
|
231
|
+
const upsert = vi.spyOn(SecurityGroupWriter, 'upsertSecurityGroup').mockReturnValue(new Promise(() => undefined));
|
|
232
|
+
renderModal(<GceSecurityGroupModal application={application as any} credentials="my-account" />);
|
|
233
|
+
fillValidSecurityGroup('my-app-firewall');
|
|
234
|
+
fireEvent.click(screen.getByRole('button', { name: /Add New Protocol and Port Range/ }));
|
|
235
|
+
fireEvent.change(screen.getByRole('combobox', { name: 'Protocol 2' }), { target: { value: 'udp' } });
|
|
236
|
+
fireEvent.change(screen.getByRole('spinbutton', { name: 'Start port 2' }), { target: { value: '7001' } });
|
|
237
|
+
fireEvent.change(screen.getByRole('spinbutton', { name: 'End port 2' }), { target: { value: '7002' } });
|
|
238
|
+
fireEvent.click(screen.getByRole('button', { name: /Add New Protocol and Port Range/ }));
|
|
239
|
+
fireEvent.change(screen.getByRole('combobox', { name: 'Protocol 3' }), { target: { value: 'icmp' } });
|
|
240
|
+
fireEvent.change(screen.getByRole('spinbutton', { name: 'Start port 3' }), { target: { value: '' } });
|
|
241
|
+
fireEvent.change(screen.getByRole('spinbutton', { name: 'End port 3' }), { target: { value: '' } });
|
|
242
|
+
|
|
243
|
+
await waitFor(() => expect(screen.getByRole('button', { name: 'Create' })).toBeEnabled());
|
|
244
|
+
fireEvent.click(screen.getByRole('button', { name: 'Create' }));
|
|
245
|
+
|
|
246
|
+
expect(upsert.mock.lastCall[0].allowed).toEqual([
|
|
242
247
|
{ ipProtocol: 'tcp', portRanges: ['443-443'] },
|
|
243
248
|
{ ipProtocol: 'udp', portRanges: ['7001-7002'] },
|
|
244
249
|
{ ipProtocol: 'icmp' },
|
|
245
250
|
]);
|
|
246
|
-
expect(upsert.
|
|
251
|
+
expect(upsert.mock.lastCall[2]).toBe('Create');
|
|
247
252
|
});
|
|
248
253
|
|
|
249
254
|
it('loads and normalizes all inbound rules for edit while retaining firewall identity', () => {
|
|
@@ -269,15 +274,8 @@ describe('GceSecurityGroupModal', () => {
|
|
|
269
274
|
sourceTags: '[backend, jobs]',
|
|
270
275
|
targetTags: '[web, api]',
|
|
271
276
|
};
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
application: application as any,
|
|
275
|
-
mode: 'edit',
|
|
276
|
-
securityGroup: source,
|
|
277
|
-
} as any);
|
|
278
|
-
|
|
279
|
-
expect((modal.state as any).securityGroup).toEqual(
|
|
280
|
-
jasmine.objectContaining({
|
|
277
|
+
expect(initializeGceSecurityGroupForModal({ mode: 'edit', securityGroup: source }, application.name)).toEqual(
|
|
278
|
+
expect.objectContaining({
|
|
281
279
|
accountId: 'my-account',
|
|
282
280
|
credentials: 'my-account',
|
|
283
281
|
id: 'my-firewall',
|
|
@@ -292,90 +290,76 @@ describe('GceSecurityGroupModal', () => {
|
|
|
292
290
|
targetTags: ['web', 'api'],
|
|
293
291
|
}),
|
|
294
292
|
);
|
|
293
|
+
const upsert = vi.spyOn(SecurityGroupWriter, 'upsertSecurityGroup').mockReturnValue(new Promise(() => undefined));
|
|
294
|
+
renderModal(<GceSecurityGroupModal application={application as any} mode="edit" securityGroup={source} />);
|
|
295
295
|
|
|
296
|
-
(
|
|
296
|
+
fireEvent.click(screen.getByRole('button', { name: 'Update' }));
|
|
297
297
|
|
|
298
|
-
expect(upsert.
|
|
299
|
-
|
|
300
|
-
accountId: 'my-account',
|
|
301
|
-
id: 'my-firewall',
|
|
302
|
-
name: 'my-firewall',
|
|
303
|
-
}),
|
|
298
|
+
expect(upsert.mock.lastCall[0]).toEqual(
|
|
299
|
+
expect.objectContaining({ accountId: 'my-account', id: 'my-firewall', name: 'my-firewall' }),
|
|
304
300
|
);
|
|
305
|
-
expect(upsert.
|
|
301
|
+
expect(upsert.mock.lastCall[0].allowed).toEqual([
|
|
306
302
|
{ ipProtocol: 'tcp', portRanges: ['80-80'] },
|
|
307
303
|
{ ipProtocol: 'tcp', portRanges: ['443-444'] },
|
|
308
304
|
{ ipProtocol: 'icmp' },
|
|
309
305
|
]);
|
|
310
|
-
expect(upsert.
|
|
306
|
+
expect(upsert.mock.lastCall[2]).toBe('Update');
|
|
311
307
|
});
|
|
312
308
|
|
|
313
309
|
it('preserves fetched firewall CIDRs from ipRangeRules without duplicating sourceRanges', () => {
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
range: { ip: '192.168.0.0', cidr: '/24' },
|
|
331
|
-
},
|
|
332
|
-
],
|
|
333
|
-
name: 'fetched-firewall',
|
|
334
|
-
network: 'default',
|
|
335
|
-
sourceRanges: ['10.0.0.0/8'],
|
|
336
|
-
},
|
|
337
|
-
} as any);
|
|
338
|
-
|
|
339
|
-
expect((modal.state as any).securityGroup.sourceRanges).toEqual(['10.0.0.0/8', '192.168.0.0/24']);
|
|
310
|
+
const source = {
|
|
311
|
+
accountName: 'my-account',
|
|
312
|
+
id: 'fetched-firewall',
|
|
313
|
+
ipRangeRules: [
|
|
314
|
+
{ portRanges: [{ startPort: 443, endPort: 443 }], protocol: 'tcp', range: { ip: '10.0.0.0', cidr: '/8' } },
|
|
315
|
+
{ portRanges: [{ startPort: 53, endPort: 53 }], protocol: 'udp', range: { ip: '192.168.0.0', cidr: '/24' } },
|
|
316
|
+
],
|
|
317
|
+
name: 'fetched-firewall',
|
|
318
|
+
network: 'default',
|
|
319
|
+
sourceRanges: ['10.0.0.0/8'],
|
|
320
|
+
};
|
|
321
|
+
expect(
|
|
322
|
+
initializeGceSecurityGroupForModal({ mode: 'edit', securityGroup: source }, application.name).sourceRanges,
|
|
323
|
+
).toEqual(['10.0.0.0/8', '192.168.0.0/24']);
|
|
324
|
+
const upsert = vi.spyOn(SecurityGroupWriter, 'upsertSecurityGroup').mockReturnValue(new Promise(() => undefined));
|
|
325
|
+
renderModal(<GceSecurityGroupModal application={application as any} mode="edit" securityGroup={source} />);
|
|
340
326
|
|
|
341
|
-
(
|
|
327
|
+
fireEvent.click(screen.getByRole('button', { name: 'Update' }));
|
|
342
328
|
|
|
343
|
-
expect(upsert.
|
|
329
|
+
expect(upsert.mock.lastCall[0].sourceRanges).toEqual(['10.0.0.0/8', '192.168.0.0/24']);
|
|
344
330
|
});
|
|
345
331
|
|
|
346
|
-
it('clears cloned firewall identity and name but preserves its editable rules', () => {
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
sourceRanges: ['10.0.0.0/8'],
|
|
358
|
-
},
|
|
359
|
-
} as any);
|
|
360
|
-
|
|
361
|
-
expect((modal.state as any).securityGroup).toEqual(
|
|
362
|
-
jasmine.objectContaining({
|
|
332
|
+
it('clears cloned firewall identity and name but preserves its editable rules', async () => {
|
|
333
|
+
const source = {
|
|
334
|
+
accountId: 'my-account',
|
|
335
|
+
id: 'source-firewall',
|
|
336
|
+
ipIngressRules: [{ protocol: 'sctp', portRanges: [{ startPort: 5000, endPort: 5001 }] }],
|
|
337
|
+
name: 'source-firewall',
|
|
338
|
+
network: 'default',
|
|
339
|
+
sourceRanges: ['10.0.0.0/8'],
|
|
340
|
+
};
|
|
341
|
+
expect(initializeGceSecurityGroupForModal({ mode: 'clone', securityGroup: source }, application.name)).toEqual(
|
|
342
|
+
expect.objectContaining({
|
|
363
343
|
id: undefined,
|
|
364
344
|
ipIngress: [{ type: 'sctp', startPort: 5000, endPort: 5001 }],
|
|
365
345
|
name: '',
|
|
366
346
|
}),
|
|
367
347
|
);
|
|
348
|
+
runtimeServices.securityGroupReader = { getAllSecurityGroups: vi.fn().mockResolvedValue({}) };
|
|
349
|
+
const upsert = vi.spyOn(SecurityGroupWriter, 'upsertSecurityGroup').mockReturnValue(new Promise(() => undefined));
|
|
350
|
+
renderModal(<GceSecurityGroupModal application={application as any} mode="clone" securityGroup={source} />);
|
|
351
|
+
fireEvent.change(screen.getByRole('textbox', { name: 'Name' }), { target: { value: 'cloned-firewall' } });
|
|
368
352
|
|
|
369
|
-
(
|
|
370
|
-
(
|
|
353
|
+
await waitFor(() => expect(screen.getByRole('button', { name: 'Clone' })).toBeEnabled());
|
|
354
|
+
fireEvent.click(screen.getByRole('button', { name: 'Clone' }));
|
|
371
355
|
|
|
372
|
-
expect(upsert.
|
|
373
|
-
|
|
356
|
+
expect(upsert.mock.lastCall[0]).toEqual(
|
|
357
|
+
expect.objectContaining({
|
|
374
358
|
allowed: [{ ipProtocol: 'sctp', portRanges: ['5000-5001'] }],
|
|
375
359
|
id: undefined,
|
|
376
360
|
name: 'cloned-firewall',
|
|
377
361
|
}),
|
|
378
362
|
);
|
|
379
|
-
expect(upsert.
|
|
363
|
+
expect(upsert.mock.lastCall[2]).toBe('Clone');
|
|
380
364
|
});
|
|
381
365
|
});
|
|
@@ -377,12 +377,16 @@ export class GceSecurityGroupModalComponent extends React.Component<
|
|
|
377
377
|
onChange: (value: string) => void,
|
|
378
378
|
readOnly = false,
|
|
379
379
|
): JSX.Element {
|
|
380
|
+
const id = `gce-firewall-${label.toLowerCase().replace(/\s+/g, '-')}`;
|
|
380
381
|
return (
|
|
381
382
|
<div className="form-group">
|
|
382
|
-
<label className="col-md-3 control-label"
|
|
383
|
+
<label className="col-md-3 control-label" htmlFor={id}>
|
|
384
|
+
{label}
|
|
385
|
+
</label>
|
|
383
386
|
<div className="col-md-7">
|
|
384
387
|
<input
|
|
385
388
|
className="form-control input-sm"
|
|
389
|
+
id={id}
|
|
386
390
|
onChange={(event) => onChange(event.target.value)}
|
|
387
391
|
readOnly={readOnly}
|
|
388
392
|
value={value || ''}
|
|
@@ -410,6 +414,7 @@ export class GceSecurityGroupModalComponent extends React.Component<
|
|
|
410
414
|
<tr key={index}>
|
|
411
415
|
<td>
|
|
412
416
|
<select
|
|
417
|
+
aria-label={`Protocol ${index + 1}`}
|
|
413
418
|
className="form-control input-sm"
|
|
414
419
|
onChange={(event) => this.updateRule(index, { type: event.target.value })}
|
|
415
420
|
value={rule.type}
|
|
@@ -423,6 +428,7 @@ export class GceSecurityGroupModalComponent extends React.Component<
|
|
|
423
428
|
</td>
|
|
424
429
|
<td>
|
|
425
430
|
<input
|
|
431
|
+
aria-label={`Start port ${index + 1}`}
|
|
426
432
|
className="form-control input-sm"
|
|
427
433
|
min="0"
|
|
428
434
|
onChange={(event) =>
|
|
@@ -436,6 +442,7 @@ export class GceSecurityGroupModalComponent extends React.Component<
|
|
|
436
442
|
</td>
|
|
437
443
|
<td>
|
|
438
444
|
<input
|
|
445
|
+
aria-label={`End port ${index + 1}`}
|
|
439
446
|
className="form-control input-sm"
|
|
440
447
|
min="0"
|
|
441
448
|
onChange={(event) =>
|
|
@@ -448,7 +455,12 @@ export class GceSecurityGroupModalComponent extends React.Component<
|
|
|
448
455
|
/>
|
|
449
456
|
</td>
|
|
450
457
|
<td>
|
|
451
|
-
<button
|
|
458
|
+
<button
|
|
459
|
+
aria-label={`Remove rule ${index + 1}`}
|
|
460
|
+
className="btn btn-link"
|
|
461
|
+
onClick={() => this.removeRule(index)}
|
|
462
|
+
type="button"
|
|
463
|
+
>
|
|
452
464
|
<span className="glyphicon glyphicon-trash" />
|
|
453
465
|
</button>
|
|
454
466
|
</td>
|