@rancher/shell 3.0.12-rc.4 → 3.0.12-rc.6
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/apis/intf/resources-api/resource-base.ts +42 -2
- package/apis/intf/resources-api/resource-instance.ts +101 -0
- package/apis/intf/resources-api/resources-api.ts +177 -23
- package/apis/intf/resources.ts +8 -6
- package/apis/resources/__tests__/resources-api-class.test.ts +309 -28
- package/apis/resources/resources-api-class.ts +232 -43
- package/assets/images/providers/traefik.png +0 -0
- package/assets/styles/global/_button.scss +1 -1
- package/assets/translations/en-us.yaml +49 -14
- package/chart/__tests__/rancher-monitoring-dashboards-index.test.ts +269 -0
- package/chart/rancher-monitoring-dashboards/index.vue +155 -0
- package/cloud-credential/__tests__/generic.test.ts +132 -0
- package/cloud-credential/generic.vue +33 -3
- package/components/ActionDropdownShell.vue +5 -3
- package/components/ButtonGroup.vue +26 -1
- package/components/CruResource.vue +51 -2
- package/components/EtcdInfoBanner.vue +2 -12
- package/components/GrafanaDashboard.vue +2 -8
- package/components/PromptRestore.vue +93 -32
- package/components/Questions/index.vue +1 -0
- package/components/Resource/Detail/Masthead/__tests__/index.test.ts +68 -14
- package/components/Resource/Detail/Masthead/index.vue +8 -3
- package/components/ResourceTable.vue +1 -0
- package/components/SortableTable/index.vue +4 -3
- package/components/Wizard.vue +14 -1
- package/components/__tests__/ButtonGroup.test.ts +56 -0
- package/components/__tests__/PromptRestore.test.ts +169 -19
- package/components/fleet/GitRepoAdvancedTab.vue +1 -0
- package/components/fleet/GitRepoMetadataTab.vue +5 -0
- package/components/fleet/HelmOpAppCoConfigTab.vue +4 -0
- package/components/fleet/HelmOpMetadataTab.vue +5 -0
- package/components/form/FileSelector.vue +39 -1
- package/components/form/PrivateRegistry.constants.ts +7 -0
- package/components/form/PrivateRegistry.vue +253 -18
- package/components/form/Security.vue +1 -1
- package/components/form/SelectOrCreateAuthSecret.vue +140 -17
- package/components/form/__tests__/FileSelector.test.ts +23 -0
- package/components/form/__tests__/PrivateRegistry.test.ts +463 -73
- package/components/form/__tests__/SelectOrCreateAuthSecret.test.ts +122 -0
- package/components/formatter/EtcdSnapshotName.vue +73 -0
- package/components/formatter/Translate.vue +22 -1
- package/components/nav/Group.vue +5 -10
- package/components/nav/Header.vue +8 -1
- package/components/nav/TopLevelMenu.vue +1 -30
- package/components/nav/__tests__/Group.test.ts +61 -0
- package/components/templates/default.vue +7 -0
- package/config/features.js +1 -0
- package/config/labels-annotations.js +3 -0
- package/config/product/manager.js +6 -0
- package/config/secret.ts +10 -0
- package/config/settings.ts +6 -2
- package/config/table-headers.js +5 -2
- package/config/types.js +7 -0
- package/core/__tests__/plugin-products-apply.test.ts +787 -0
- package/core/__tests__/plugin-products-extend.test.ts +189 -0
- package/core/__tests__/plugin-products-helpers.test.ts +81 -78
- package/core/__tests__/plugin-products-new.test.ts +389 -0
- package/core/__tests__/plugin-products-scenarios.test.ts +980 -0
- package/core/__tests__/plugin-products-side-menu.test.ts +1935 -0
- package/core/plugin-products-base.ts +201 -93
- package/core/plugin-products-extending.ts +1 -1
- package/core/plugin-products-external.ts +380 -0
- package/core/plugin-products-helpers.ts +28 -24
- package/core/plugin-products-internal.ts +207 -0
- package/core/plugin-products-top-level.ts +10 -5
- package/core/plugin-products-type-guards.ts +17 -6
- package/core/plugin-products.ts +5 -7
- package/core/plugin-types.ts +1 -389
- package/core/plugin.ts +10 -11
- package/core/types.ts +6 -202
- package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +52 -0
- package/detail/pod.vue +1 -1
- package/detail/provisioning.cattle.io.cluster.vue +79 -5
- package/dialog/RotateEncryptionKeyDialog.vue +33 -9
- package/dialog/__tests__/RotateEncryptionKeyDialog.test.ts +78 -0
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +92 -0
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +101 -0
- package/edit/__tests__/management.cattle.io.setting.test.ts +2 -1
- package/edit/auth/AuthProviderWarningBanners.vue +12 -0
- package/edit/auth/__tests__/AuthProviderWarningBanners.test.ts +10 -1
- package/edit/auth/__tests__/azuread.test.ts +1 -0
- package/edit/auth/__tests__/github.test.ts +1 -0
- package/edit/auth/__tests__/oidc.test.ts +1 -0
- package/edit/auth/__tests__/saml.test.ts +1 -0
- package/edit/auth/ldap/__tests__/index.test.ts +1 -0
- package/edit/compliance.cattle.io.clusterscanprofile.vue +39 -41
- package/edit/fleet.cattle.io.gitrepo.vue +70 -16
- package/edit/fleet.cattle.io.helmop.vue +51 -5
- package/edit/helm.cattle.io.projecthelmchart.vue +1 -0
- package/edit/{management.cattle.io.setting.vue → management.cattle.io.setting/index.vue} +32 -9
- package/edit/management.cattle.io.setting/system-default-registry-pull-secrets.vue +81 -0
- package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +3 -12
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +18 -0
- package/edit/provisioning.cattle.io.cluster/index.vue +1 -1
- package/edit/provisioning.cattle.io.cluster/rke2.vue +6 -2
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +0 -1
- package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +2 -2
- package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +14 -55
- package/list/management.cattle.io.user.vue +24 -5
- package/list/utils/management.cattle.io.cluster.utils.ts +1 -1
- package/machine-config/__tests__/generic.test.ts +163 -0
- package/machine-config/components/EC2Networking.vue +31 -7
- package/machine-config/components/__tests__/EC2Networking.test.ts +38 -15
- package/machine-config/generic.vue +32 -5
- package/mixins/resource-fetch.js +1 -1
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +156 -0
- package/models/__tests__/rke-machine.cattle.io.vmwarevspheremachinetemplate.test.ts +22 -0
- package/models/__tests__/secret.test.ts +68 -1
- package/models/management.cattle.io.cluster.js +21 -3
- package/models/pod.js +25 -12
- package/models/provisioning.cattle.io.cluster.js +59 -9
- package/models/rke-machine.cattle.io.vmwarevspheremachinetemplate.js +9 -0
- package/models/rke.cattle.io.etcdsnapshot.js +17 -9
- package/models/secret.js +19 -0
- package/models/workload.js +12 -7
- package/package.json +1 -1
- package/pages/auth/login.vue +1 -1
- package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +569 -81
- package/pages/c/_cluster/apps/charts/install.vue +143 -28
- package/pages/c/_cluster/auth/config/index.vue +11 -4
- package/pages/c/_cluster/monitoring/index.vue +6 -1
- package/pkg/require-asset.lib.js +25 -0
- package/pkg/vue.config.js +7 -0
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +84 -0
- package/plugins/dashboard-store/getters.js +0 -1
- package/plugins/dashboard-store/resource-class.js +62 -15
- package/plugins/steve/__tests__/steve-class-resource-api.test.ts +147 -0
- package/plugins/steve/steve-class.js +43 -0
- package/plugins/steve/subscribe.js +11 -0
- package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +115 -0
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +83 -3
- package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +30 -0
- package/rancher-components/Form/TextArea/__tests__/TextAreaAutoGrow.test.ts +95 -0
- package/rancher-components/RcButton/index.ts +1 -1
- package/rancher-components/RcDropdown/RcDropdownTrigger.vue +6 -1
- package/store/__tests__/auth.test.ts +18 -0
- package/store/__tests__/features.test.ts +131 -0
- package/store/__tests__/growl.test.ts +374 -0
- package/store/__tests__/modal.test.ts +131 -0
- package/store/__tests__/notifications.test.ts +466 -1
- package/store/__tests__/slideInPanel.test.ts +88 -0
- package/store/__tests__/type-map.utils.test.ts +433 -0
- package/store/__tests__/ui-context.test.ts +255 -0
- package/store/auth.js +11 -1
- package/store/features.js +5 -0
- package/store/plugins.js +6 -0
- package/types/rancher/steve.api.ts +2 -2
- package/types/shell/index.d.ts +120 -0
- package/types/store/dashboard-store.types.ts +2 -2
- package/types/store/type-map.ts +262 -1
- package/utils/__tests__/operation-cr.test.ts +34 -0
- package/utils/async.ts +2 -0
- package/utils/grafana.js +2 -17
- package/utils/monitoring.js +38 -1
- package/utils/operation-cr.js +19 -0
- package/utils/require-asset.ts +7 -0
- package/utils/validators/__tests__/private-registry.test.ts +27 -15
- package/utils/validators/private-registry.ts +15 -4
- package/core/__tests__/plugin-products.test.ts +0 -4694
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import Generic from '@shell/machine-config/generic.vue';
|
|
2
|
+
import { shallowMount } from '@vue/test-utils';
|
|
3
|
+
import { MANAGEMENT } from '@shell/config/types';
|
|
4
|
+
import { NODE_DRIVER_FIELD_HINTS } from '@shell/config/labels-annotations';
|
|
5
|
+
import { Banner } from '@components/Banner';
|
|
6
|
+
import Questions from '@shell/components/Questions';
|
|
7
|
+
|
|
8
|
+
const PROVIDER = 'testdriver';
|
|
9
|
+
|
|
10
|
+
const buildFields = () => ({
|
|
11
|
+
zone: { type: 'string' },
|
|
12
|
+
diskSize: { type: 'string' },
|
|
13
|
+
enginePort: { type: 'string' },
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const mockedStore = (fieldHints: Record<string, { type: string }> | null = null) => {
|
|
17
|
+
const driver = { metadata: { annotations: { ...(fieldHints ? { [NODE_DRIVER_FIELD_HINTS]: JSON.stringify(fieldHints) } : {}) } } };
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
getters: {
|
|
21
|
+
'i18n/t': (key: string) => key,
|
|
22
|
+
t: (key: string) => key,
|
|
23
|
+
'plugins/fieldsForDriver': () => Promise.resolve(buildFields()),
|
|
24
|
+
'plugins/credentialFieldForDriver': () => PROVIDER,
|
|
25
|
+
'plugins/fieldNamesForDriver': () => [],
|
|
26
|
+
'rancher/schemaFor': () => null,
|
|
27
|
+
},
|
|
28
|
+
dispatch: jest.fn((_action: string, args: any) => {
|
|
29
|
+
if (args?.type === MANAGEMENT.NODE_DRIVER && args?.id === PROVIDER) {
|
|
30
|
+
return Promise.resolve(driver);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return Promise.resolve(null);
|
|
34
|
+
}),
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const mountAndFetch = async(storeOverrides: Record<string, any> = {}) => {
|
|
39
|
+
const store = {
|
|
40
|
+
...mockedStore(null),
|
|
41
|
+
...storeOverrides,
|
|
42
|
+
getters: {
|
|
43
|
+
...mockedStore(null).getters,
|
|
44
|
+
...(storeOverrides.getters || {}),
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
if (storeOverrides.dispatch) {
|
|
49
|
+
store.dispatch = storeOverrides.dispatch;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const wrapper = shallowMount(Generic, {
|
|
53
|
+
props: {
|
|
54
|
+
credentialId: 'cattle-global-data:cc-12345',
|
|
55
|
+
provider: PROVIDER,
|
|
56
|
+
value: { metadata: { namespace: 'fleet-default' } },
|
|
57
|
+
mode: 'edit',
|
|
58
|
+
},
|
|
59
|
+
global: {
|
|
60
|
+
mocks: {
|
|
61
|
+
$store: store,
|
|
62
|
+
$fetchState: { pending: false },
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
await (Generic as any).fetch.call(wrapper.vm);
|
|
68
|
+
await wrapper.vm.$nextTick();
|
|
69
|
+
|
|
70
|
+
return wrapper;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
describe('generic machine config', () => {
|
|
74
|
+
describe('io.cattle.nodedriver/ui-field-hints annotation', () => {
|
|
75
|
+
it('should override field types when type hints are provided', async() => {
|
|
76
|
+
const hints = {
|
|
77
|
+
zone: { type: 'multiline' },
|
|
78
|
+
diskSize: { type: 'multiline' },
|
|
79
|
+
};
|
|
80
|
+
const wrapper = await mountAndFetch({
|
|
81
|
+
...mockedStore(hints),
|
|
82
|
+
getters: { ...mockedStore(hints).getters },
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
expect((wrapper.vm as any).fields.zone.type).toBe('multiline');
|
|
86
|
+
expect((wrapper.vm as any).fields.diskSize.type).toBe('multiline');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should not modify fields that are not in the hints', async() => {
|
|
90
|
+
const hints = { zone: { type: 'multiline' } };
|
|
91
|
+
const wrapper = await mountAndFetch({
|
|
92
|
+
...mockedStore(hints),
|
|
93
|
+
getters: { ...mockedStore(hints).getters },
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
expect((wrapper.vm as any).fields.enginePort.type).toBe('string');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should not modify fields when no hints annotation exists', async() => {
|
|
100
|
+
const wrapper = await mountAndFetch();
|
|
101
|
+
|
|
102
|
+
expect((wrapper.vm as any).fields.zone.type).toBe('string');
|
|
103
|
+
expect((wrapper.vm as any).fields.diskSize.type).toBe('string');
|
|
104
|
+
expect((wrapper.vm as any).fields.enginePort.type).toBe('string');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should ignore hint entries for fields not present in the schema', async() => {
|
|
108
|
+
const hints = { nonExistentField: { type: 'multiline' } };
|
|
109
|
+
const wrapper = await mountAndFetch({
|
|
110
|
+
...mockedStore(hints),
|
|
111
|
+
getters: { ...mockedStore(hints).getters },
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
expect((wrapper.vm as any).fields.zone.type).toBe('string');
|
|
115
|
+
expect((wrapper.vm as any).fields.nonExistentField).toBeUndefined();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should ignore hint entries that do not specify a type', async() => {
|
|
119
|
+
const hints = { zone: { otherKey: 'blah' } } as any;
|
|
120
|
+
const wrapper = await mountAndFetch({
|
|
121
|
+
...mockedStore(hints),
|
|
122
|
+
getters: { ...mockedStore(hints).getters },
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
expect((wrapper.vm as any).fields.zone.type).toBe('string');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should handle malformed JSON in the annotation gracefully', async() => {
|
|
129
|
+
const driver = { metadata: { annotations: { [NODE_DRIVER_FIELD_HINTS]: 'testing bad json{' } } };
|
|
130
|
+
const wrapper = await mountAndFetch({ dispatch: jest.fn(() => Promise.resolve(driver)) });
|
|
131
|
+
|
|
132
|
+
expect((wrapper.vm as any).fields.zone.type).toBe('string');
|
|
133
|
+
expect((wrapper.vm as any).errors.length).toBe(1);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe('data fetching failures', () => {
|
|
138
|
+
it('should render an error banner and not render Questions when unable to determine fields for the driver', async() => {
|
|
139
|
+
const wrapper = await mountAndFetch({ getters: { 'plugins/fieldsForDriver': () => Promise.resolve(null) } });
|
|
140
|
+
|
|
141
|
+
expect((wrapper.vm as any).errors.length).toBe(1);
|
|
142
|
+
expect((wrapper.vm as any).errors[0].message).toContain(`Machine Driver config schema not found for rke-machine-config.cattle.io.${ PROVIDER }config`);
|
|
143
|
+
|
|
144
|
+
const banners = wrapper.findAllComponents(Banner);
|
|
145
|
+
|
|
146
|
+
expect(banners.length).toBe(1);
|
|
147
|
+
expect(wrapper.findComponent(Questions).exists()).toBe(false);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should render an error banner but still render Questions when unable to retrieve node driver field hints', async() => {
|
|
151
|
+
const driverError = 'node driver not found';
|
|
152
|
+
const wrapper = await mountAndFetch({ dispatch: jest.fn(() => Promise.reject(new Error(driverError))) });
|
|
153
|
+
|
|
154
|
+
expect((wrapper.vm as any).errors.length).toBe(1);
|
|
155
|
+
expect((wrapper.vm as any).errors[0].message).toContain(driverError);
|
|
156
|
+
|
|
157
|
+
const banners = wrapper.findAllComponents(Banner);
|
|
158
|
+
|
|
159
|
+
expect(banners.length).toBe(1);
|
|
160
|
+
expect(wrapper.findComponent(Questions).exists()).toBe(true);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
});
|
|
@@ -10,6 +10,7 @@ import { Banner } from '@components/Banner';
|
|
|
10
10
|
import { scrollToBottom } from '@shell/utils/scroll';
|
|
11
11
|
import { _CREATE } from '@shell/config/query-params';
|
|
12
12
|
import { getSubnetDisplayName, getVpcDisplayName, isIpv4Network, isIpv6Network } from '@shell/utils/aws';
|
|
13
|
+
import { STACK_PREFS } from '@shell/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue';
|
|
13
14
|
|
|
14
15
|
export default {
|
|
15
16
|
name: 'Ec2MachinePoolNetworking',
|
|
@@ -297,10 +298,27 @@ export default {
|
|
|
297
298
|
return this.mode === _CREATE || (this.isNew && this.somePoolisIpv6OrDual) || this.enableIpv6;
|
|
298
299
|
},
|
|
299
300
|
|
|
300
|
-
|
|
301
|
-
const
|
|
301
|
+
dualStackModeInvalid() {
|
|
302
|
+
const poolNetworkModes = new Set(this.machinePools.map((p) => {
|
|
303
|
+
if (p.isDualStack || p.isIpv6) {
|
|
304
|
+
return STACK_PREFS.DUAL;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return STACK_PREFS.IPV4;
|
|
308
|
+
}));
|
|
302
309
|
|
|
303
|
-
return
|
|
310
|
+
return poolNetworkModes.size > 1;
|
|
311
|
+
},
|
|
312
|
+
ipv6ModeInvalid() {
|
|
313
|
+
const poolNetworkModes = new Set(this.machinePools.map((p) => {
|
|
314
|
+
if (p.isIpv6) {
|
|
315
|
+
return STACK_PREFS.IPV6;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return 'other';
|
|
319
|
+
}));
|
|
320
|
+
|
|
321
|
+
return poolNetworkModes.size > 1;
|
|
304
322
|
},
|
|
305
323
|
|
|
306
324
|
addressCountInvalid() {
|
|
@@ -308,7 +326,7 @@ export default {
|
|
|
308
326
|
},
|
|
309
327
|
|
|
310
328
|
allValid() {
|
|
311
|
-
return !this.
|
|
329
|
+
return !this.dualStackModeInvalid && !this.addressCountInvalid && !this.ipv6ModeInvalid;
|
|
312
330
|
}
|
|
313
331
|
},
|
|
314
332
|
|
|
@@ -356,10 +374,10 @@ export default {
|
|
|
356
374
|
|
|
357
375
|
<template>
|
|
358
376
|
<Banner
|
|
359
|
-
v-if="
|
|
377
|
+
v-if="dualStackModeInvalid"
|
|
360
378
|
color="error"
|
|
361
|
-
:label="t('cluster.machineConfig.amazonEc2.
|
|
362
|
-
data-testid="
|
|
379
|
+
:label="t('cluster.machineConfig.amazonEc2.dualStackValidationWarning')"
|
|
380
|
+
data-testid="amazonEc2__dualStackWarning"
|
|
363
381
|
/>
|
|
364
382
|
<div
|
|
365
383
|
v-if="showIpv6Options"
|
|
@@ -410,6 +428,12 @@ export default {
|
|
|
410
428
|
</LabeledSelect>
|
|
411
429
|
</div>
|
|
412
430
|
</div>
|
|
431
|
+
<Banner
|
|
432
|
+
v-if="enableIpv6 && ipv6ModeInvalid"
|
|
433
|
+
color="error"
|
|
434
|
+
:label="t('cluster.machineConfig.amazonEc2.ipv6ValidationWarning')"
|
|
435
|
+
data-testid="amazonEc2__ipv6Warning"
|
|
436
|
+
/>
|
|
413
437
|
<div
|
|
414
438
|
v-if="enableIpv6"
|
|
415
439
|
class="row mb-10 ipv6-row"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { shallowMount } from '@vue/test-utils';
|
|
2
2
|
import EC2Networking from '@shell/machine-config/components/EC2Networking.vue';
|
|
3
3
|
|
|
4
4
|
import { vpcInfo, subnetInfo } from './utils/vpcSubnetMockData';
|
|
@@ -16,6 +16,7 @@ describe('component: EC2Networking', () => {
|
|
|
16
16
|
},
|
|
17
17
|
global: {
|
|
18
18
|
mocks: {
|
|
19
|
+
t: (key: string) => key,
|
|
19
20
|
$fetchState: { pending: false },
|
|
20
21
|
$store: { getters: defaultGetters },
|
|
21
22
|
},
|
|
@@ -65,16 +66,9 @@ describe('component: EC2Networking', () => {
|
|
|
65
66
|
});
|
|
66
67
|
|
|
67
68
|
it('should render the ipv6 only checkbox when the selected network is dual-stack', async() => {
|
|
68
|
-
const wrapper =
|
|
69
|
-
const ipv6Checkbox = wrapper.findComponent('[data-testid="amazonEc2__enableIpv6"]');
|
|
70
|
-
|
|
71
|
-
ipv6Checkbox.vm.$emit('update:value', true);
|
|
72
|
-
await wrapper.vm.$nextTick();
|
|
73
|
-
|
|
74
|
-
const networkDropdown = wrapper.findComponent('[data-testid="amazonEc2__selectedNetwork"]');
|
|
69
|
+
const wrapper = shallowMount(EC2Networking, defaultCreateSetup);
|
|
75
70
|
|
|
76
|
-
|
|
77
|
-
await wrapper.vm.$nextTick();
|
|
71
|
+
await wrapper.setData({ enableIpv6: true, selectedNetwork: 'vpc-12345' });
|
|
78
72
|
|
|
79
73
|
const ipv6OnlyCheckbox = wrapper.findComponent('[data-testid="amazonEc2__ipv6AddressOnly"]');
|
|
80
74
|
|
|
@@ -82,14 +76,19 @@ describe('component: EC2Networking', () => {
|
|
|
82
76
|
});
|
|
83
77
|
|
|
84
78
|
it.each([
|
|
85
|
-
[[{ isIpv6: true }, { isIpv6: false }], true],
|
|
86
|
-
[[{ isIpv6: false }, { isIpv6: false }], false],
|
|
87
|
-
[[{ isIpv6: true }, { isIpv6: true }], false],
|
|
88
|
-
|
|
79
|
+
[[{ isIpv6: true }, { isIpv6: false }], true, true],
|
|
80
|
+
[[{ isIpv6: false, isDualStack: true }, { isIpv6: false, isDualStack: false }], true, false],
|
|
81
|
+
[[{ isIpv6: true, isDualStack: false }, { isIpv6: false, isDualStack: true }], false, true],
|
|
82
|
+
[[{ isIpv6: false, isDualStack: false }, { isIpv6: false, isDualStack: false }], false, false],
|
|
83
|
+
[[{ isIpv6: true, isDualStack: false }, { isIpv6: true, isDualStack: false }], false, false],
|
|
84
|
+
[[{ isIpv6: false, isDualStack: true }, { isIpv6: false, isDualStack: true }], false, false],
|
|
85
|
+
])('should show the correct warning banner based on mixed machine pool network modes', (pools, shouldShowDualStackWarning, shouldShowIpv6Warning) => {
|
|
89
86
|
const wrapper = shallowMount(EC2Networking, { ...defaultCreateSetup, propsData: { ...defaultCreateSetup.propsData, machinePools: pools } });
|
|
87
|
+
const dualStackWarning = wrapper.findComponent('[data-testid="amazonEc2__dualStackWarning"]');
|
|
90
88
|
const ipv6Warning = wrapper.findComponent('[data-testid="amazonEc2__ipv6Warning"]');
|
|
91
89
|
|
|
92
|
-
expect(
|
|
90
|
+
expect(dualStackWarning.exists()).toBe(shouldShowDualStackWarning);
|
|
91
|
+
expect(ipv6Warning.exists()).toBe(shouldShowIpv6Warning);
|
|
93
92
|
});
|
|
94
93
|
|
|
95
94
|
it('should show ipv6 inputs and automatically check the enable ipv6 checkbox when adding a new pool if some other pool in the cluster is using ipv6 or dual stack', () => {
|
|
@@ -145,4 +144,28 @@ describe('component: EC2Networking', () => {
|
|
|
145
144
|
|
|
146
145
|
expect(wrapper.emitted('validationChanged')?.[0][0]).toBe(true);
|
|
147
146
|
});
|
|
147
|
+
|
|
148
|
+
it('should emit a validationChanged: false event when ipv6-only and dual-stack pools are mixed', async() => {
|
|
149
|
+
const wrapper = shallowMount(EC2Networking, {
|
|
150
|
+
...defaultCreateSetup,
|
|
151
|
+
propsData: {
|
|
152
|
+
...defaultCreateSetup.propsData,
|
|
153
|
+
machinePools: [{ isIpv6: true, isDualStack: false }, { isIpv6: false, isDualStack: true }],
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
expect(wrapper.emitted('validationChanged')?.[0][0]).toBe(false);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should emit a validationChanged: true event when all pools are dual-stack', async() => {
|
|
161
|
+
const wrapper = shallowMount(EC2Networking, {
|
|
162
|
+
...defaultCreateSetup,
|
|
163
|
+
propsData: {
|
|
164
|
+
...defaultCreateSetup.propsData,
|
|
165
|
+
machinePools: [{ isIpv6: false, isDualStack: true }, { isIpv6: false, isDualStack: true }],
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
expect(wrapper.emitted('validationChanged')?.[0][0]).toBe(true);
|
|
170
|
+
});
|
|
148
171
|
});
|
|
@@ -4,6 +4,10 @@ import { Banner } from '@components/Banner';
|
|
|
4
4
|
import CreateEditView from '@shell/mixins/create-edit-view';
|
|
5
5
|
import { exceptionToErrorsArray, stringify } from '@shell/utils/error';
|
|
6
6
|
import Questions from '@shell/components/Questions';
|
|
7
|
+
import { MANAGEMENT } from '@shell/config/types';
|
|
8
|
+
import { NODE_DRIVER_FIELD_HINTS } from '@shell/config/labels-annotations';
|
|
9
|
+
import { isEmpty } from '@shell/utils/object';
|
|
10
|
+
import cloneDeep from 'lodash/cloneDeep';
|
|
7
11
|
|
|
8
12
|
export default {
|
|
9
13
|
emits: ['input'],
|
|
@@ -35,12 +39,36 @@ export default {
|
|
|
35
39
|
this.errors = [];
|
|
36
40
|
|
|
37
41
|
try {
|
|
38
|
-
|
|
42
|
+
const fields = await this.$store.getters['plugins/fieldsForDriver'](this.provider);
|
|
43
|
+
|
|
44
|
+
// copy the result of the getter before modifying it to add hints
|
|
45
|
+
this.fields = cloneDeep(fields);
|
|
39
46
|
const name = `rke-machine-config.cattle.io.${ this.provider }config`;
|
|
40
47
|
|
|
41
48
|
if ( !this.fields ) {
|
|
42
49
|
throw new Error(`Machine Driver config schema not found for ${ name }`);
|
|
43
50
|
}
|
|
51
|
+
} catch (e) {
|
|
52
|
+
this.errors = exceptionToErrorsArray(e);
|
|
53
|
+
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const driver = await this.$store.dispatch('management/find', { type: MANAGEMENT.NODE_DRIVER, id: this.provider } );
|
|
59
|
+
const fieldHints = driver?.metadata?.annotations?.[NODE_DRIVER_FIELD_HINTS];
|
|
60
|
+
let parsedHints;
|
|
61
|
+
|
|
62
|
+
if (fieldHints) {
|
|
63
|
+
parsedHints = JSON.parse(fieldHints);
|
|
64
|
+
}
|
|
65
|
+
if (parsedHints && !isEmpty(parsedHints)) {
|
|
66
|
+
Object.keys(this.fields).forEach((fieldKey) => {
|
|
67
|
+
if (parsedHints[fieldKey] && parsedHints[fieldKey].type) {
|
|
68
|
+
this.fields[fieldKey].type = parsedHints[fieldKey].type;
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
44
72
|
} catch (e) {
|
|
45
73
|
this.errors = exceptionToErrorsArray(e);
|
|
46
74
|
}
|
|
@@ -77,7 +105,7 @@ export default {
|
|
|
77
105
|
v-if="$fetchState.pending"
|
|
78
106
|
:delayed="true"
|
|
79
107
|
/>
|
|
80
|
-
<
|
|
108
|
+
<template v-else>
|
|
81
109
|
<div
|
|
82
110
|
v-for="(err, idx) in errors"
|
|
83
111
|
:key="idx"
|
|
@@ -87,9 +115,8 @@ export default {
|
|
|
87
115
|
:label="stringify(err)"
|
|
88
116
|
/>
|
|
89
117
|
</div>
|
|
90
|
-
</div>
|
|
91
|
-
<div v-else>
|
|
92
118
|
<Questions
|
|
119
|
+
v-if="fields"
|
|
93
120
|
:value="value"
|
|
94
121
|
:mode="mode"
|
|
95
122
|
:tabbed="false"
|
|
@@ -99,5 +126,5 @@ export default {
|
|
|
99
126
|
:disabled="disabled"
|
|
100
127
|
@update:value="$emit('input', $event)"
|
|
101
128
|
/>
|
|
102
|
-
</
|
|
129
|
+
</template>
|
|
103
130
|
</template>
|
package/mixins/resource-fetch.js
CHANGED
|
@@ -35,7 +35,7 @@ export default {
|
|
|
35
35
|
// Normally owner components supply `resource` and `inStore` as part of their data, however these are needed here before parent data runs
|
|
36
36
|
// So set up both here
|
|
37
37
|
const params = { ...this.$route.params };
|
|
38
|
-
const resource =
|
|
38
|
+
const resource = this.schema?.id || params.resource; // Resource can either be on a page showing single list, or a page of a resource showing a list of another resource
|
|
39
39
|
const inStore = this.$store.getters['currentStore'](resource);
|
|
40
40
|
|
|
41
41
|
return {
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import ProvCluster from '@shell/models/provisioning.cattle.io.cluster';
|
|
2
2
|
import MgmtCluster from '@shell/models/management.cattle.io.cluster';
|
|
3
|
+
import { IMPORTED_DAY_2_OPS } from '@shell/config/features';
|
|
4
|
+
import { OPERATION_ANNOTATIONS } from '@shell/config/labels-annotations';
|
|
5
|
+
import { SETTING } from '@shell/config/settings';
|
|
6
|
+
import { MANAGEMENT, OPERATION } from '@shell/config/types';
|
|
7
|
+
import { createOperationCR } from '@shell/utils/operation-cr';
|
|
8
|
+
|
|
9
|
+
jest.mock('@shell/utils/operation-cr', () => ({ createOperationCR: jest.fn() }));
|
|
3
10
|
|
|
4
11
|
jest.mock('@shell/utils/provider', () => ({
|
|
5
12
|
isHostedProvider: jest.fn().mockImplementation((context, provider) => {
|
|
@@ -441,4 +448,153 @@ describe('class ProvCluster', () => {
|
|
|
441
448
|
jest.clearAllMocks();
|
|
442
449
|
});
|
|
443
450
|
});
|
|
451
|
+
describe('day 2 operations', () => {
|
|
452
|
+
const createContext = ({
|
|
453
|
+
byId = jest.fn(),
|
|
454
|
+
all = jest.fn(() => []),
|
|
455
|
+
schemaFor = jest.fn(() => ({})),
|
|
456
|
+
} = {}) => {
|
|
457
|
+
return {
|
|
458
|
+
getters: { schemaFor },
|
|
459
|
+
rootGetters: {
|
|
460
|
+
'management/byId': byId,
|
|
461
|
+
'management/all': all,
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
it('should return true when the day 2 operations feature is enabled', () => {
|
|
467
|
+
const byId = jest.fn().mockImplementation((type, id) => {
|
|
468
|
+
if (type === MANAGEMENT.FEATURE && id === IMPORTED_DAY_2_OPS) {
|
|
469
|
+
return { enabled: true };
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return undefined;
|
|
473
|
+
});
|
|
474
|
+
const cluster = new ProvCluster({}, createContext({ byId }));
|
|
475
|
+
|
|
476
|
+
expect(cluster.isDayTwoOpsFeatureEnabled).toBe(true);
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
it('should return true for imported RKE2 day 2 operations when the annotation is enabled', () => {
|
|
480
|
+
const byId = jest.fn().mockImplementation((type, id) => {
|
|
481
|
+
if (type === MANAGEMENT.FEATURE && id === IMPORTED_DAY_2_OPS) {
|
|
482
|
+
return { enabled: true };
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
return undefined;
|
|
486
|
+
});
|
|
487
|
+
const cluster = new ProvCluster({}, createContext({ byId }));
|
|
488
|
+
|
|
489
|
+
jest.spyOn(cluster, 'mgmt', 'get').mockReturnValue({ metadata: { annotations: { [OPERATION_ANNOTATIONS.ENABLED]: 'true' } } });
|
|
490
|
+
jest.spyOn(cluster, 'isImportedRke2K3s', 'get').mockReturnValue(true);
|
|
491
|
+
|
|
492
|
+
expect(cluster.isImportedWithDayTwoOps).toBe(true);
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it('should return true for imported RKE2 day 2 operations when the global setting is enabled', () => {
|
|
496
|
+
const byId = jest.fn().mockImplementation((type, id) => {
|
|
497
|
+
if (type === MANAGEMENT.FEATURE && id === IMPORTED_DAY_2_OPS) {
|
|
498
|
+
return { enabled: true };
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (type === MANAGEMENT.SETTING && id === SETTING.IMPORTED_CLUSTER_DAY2_OPS_DEFAULT) {
|
|
502
|
+
return { value: 'true' };
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return undefined;
|
|
506
|
+
});
|
|
507
|
+
const cluster = new ProvCluster({}, createContext({ byId }));
|
|
508
|
+
|
|
509
|
+
jest.spyOn(cluster, 'mgmt', 'get').mockReturnValue({ metadata: { annotations: {} } });
|
|
510
|
+
jest.spyOn(cluster, 'isImportedRke2K3s', 'get').mockReturnValue(true);
|
|
511
|
+
|
|
512
|
+
expect(cluster.isImportedWithDayTwoOps).toBe(true);
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
it('should return false for imported RKE2 day 2 operations when the feature is disabled', () => {
|
|
516
|
+
const byId = jest.fn().mockImplementation((type, id) => {
|
|
517
|
+
if (type === MANAGEMENT.SETTING && id === SETTING.IMPORTED_CLUSTER_DAY2_OPS_DEFAULT) {
|
|
518
|
+
return { value: 'true' };
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
return undefined;
|
|
522
|
+
});
|
|
523
|
+
const cluster = new ProvCluster({}, createContext({ byId }));
|
|
524
|
+
|
|
525
|
+
jest.spyOn(cluster, 'mgmt', 'get').mockReturnValue({ metadata: { annotations: { [OPERATION_ANNOTATIONS.ENABLED]: 'true' } } });
|
|
526
|
+
jest.spyOn(cluster, 'isImportedRke2K3s', 'get').mockReturnValue(true);
|
|
527
|
+
|
|
528
|
+
expect(cluster.isImportedWithDayTwoOps).toBeFalsy();
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
it('should return false for imported RKE2 day 2 operations when operation schema is missing', () => {
|
|
532
|
+
const byId = jest.fn().mockImplementation((type, id) => {
|
|
533
|
+
if (type === MANAGEMENT.FEATURE && id === IMPORTED_DAY_2_OPS) {
|
|
534
|
+
return { enabled: true };
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (type === MANAGEMENT.SETTING && id === SETTING.IMPORTED_CLUSTER_DAY2_OPS_DEFAULT) {
|
|
538
|
+
return { value: 'true' };
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return undefined;
|
|
542
|
+
});
|
|
543
|
+
const schemaFor = jest.fn().mockReturnValue(undefined);
|
|
544
|
+
const cluster = new ProvCluster({}, createContext({ byId, schemaFor }));
|
|
545
|
+
|
|
546
|
+
jest.spyOn(cluster, 'mgmt', 'get').mockReturnValue({ metadata: { annotations: { [OPERATION_ANNOTATIONS.ENABLED]: 'true' } } });
|
|
547
|
+
jest.spyOn(cluster, 'isImportedRke2K3s', 'get').mockReturnValue(true);
|
|
548
|
+
|
|
549
|
+
expect(cluster.isImportedWithDayTwoOps).toBeFalsy();
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
it('should filter etcd snapshots by management cluster fields for imported day 2 operations clusters', () => {
|
|
553
|
+
const snapshots = [
|
|
554
|
+
{
|
|
555
|
+
metadata: { namespace: 'c-m-1' },
|
|
556
|
+
spec: { clusterName: 'imported-cluster' }
|
|
557
|
+
},
|
|
558
|
+
{
|
|
559
|
+
metadata: { namespace: 'c-m-1' },
|
|
560
|
+
spec: { clusterName: 'other-cluster' }
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
metadata: { namespace: 'c-m-2' },
|
|
564
|
+
spec: { clusterName: 'imported-cluster' }
|
|
565
|
+
}
|
|
566
|
+
];
|
|
567
|
+
const all = jest.fn().mockReturnValue(snapshots);
|
|
568
|
+
const cluster = new ProvCluster({}, createContext({ all }));
|
|
569
|
+
|
|
570
|
+
jest.spyOn(cluster, 'isImportedWithDayTwoOps', 'get').mockReturnValue(true);
|
|
571
|
+
jest.spyOn(cluster, 'mgmt', 'get').mockReturnValue({ id: 'c-m-1', metadata: { name: 'imported-cluster' } });
|
|
572
|
+
|
|
573
|
+
expect(cluster.etcdSnapshots).toStrictEqual([snapshots[0]]);
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
it('should create an operation CR when taking a snapshot on an imported RKE2 or K3s cluster', () => {
|
|
577
|
+
const cluster = new ProvCluster({}, createContext());
|
|
578
|
+
|
|
579
|
+
(createOperationCR as jest.Mock).mockResolvedValue(undefined);
|
|
580
|
+
|
|
581
|
+
jest.spyOn(cluster, 'isRke1', 'get').mockReturnValue(false);
|
|
582
|
+
jest.spyOn(cluster, 'isImportedWithDayTwoOps', 'get').mockReturnValue(true);
|
|
583
|
+
jest.spyOn(cluster, 'mgmt', 'get').mockReturnValue({ id: 'c-m-1', metadata: { name: 'imported-cluster' } });
|
|
584
|
+
|
|
585
|
+
cluster.takeSnapshot();
|
|
586
|
+
|
|
587
|
+
expect(createOperationCR).toHaveBeenCalledTimes(1);
|
|
588
|
+
expect((createOperationCR as jest.Mock).mock.calls[0][1]).toBe(OPERATION.ETCD_SNAPSHOT);
|
|
589
|
+
expect((createOperationCR as jest.Mock).mock.calls[0][2]).toStrictEqual({
|
|
590
|
+
clusterRef: {
|
|
591
|
+
apiVersion: 'management.cattle.io/v3',
|
|
592
|
+
kind: 'Cluster',
|
|
593
|
+
name: 'c-m-1',
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
expect((createOperationCR as jest.Mock).mock.calls[0][3]).toBe('c-m-1');
|
|
597
|
+
expect((createOperationCR as jest.Mock).mock.calls[0][4]).toBe('imported-cluster');
|
|
598
|
+
});
|
|
599
|
+
});
|
|
444
600
|
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import VmwarevsphereMachineTemplate from '@shell/models/rke-machine.cattle.io.vmwarevspheremachinetemplate';
|
|
2
|
+
|
|
3
|
+
describe('class VmwarevsphereMachineTemplate', () => {
|
|
4
|
+
describe('providerLocation', () => {
|
|
5
|
+
it.each([
|
|
6
|
+
['/DC1/host/ClusterA/Resources', 'ClusterA'],
|
|
7
|
+
['/DC1/host/ClusterA/Resources/PoolX', 'ClusterA'],
|
|
8
|
+
['/DC1/host/ClusterA/Resources/', 'ClusterA'],
|
|
9
|
+
['/DC1/host/esxi01/Resources', 'esxi01'],
|
|
10
|
+
['/DC1/vm/FolderA', null],
|
|
11
|
+
['/DC1/host/foo/ResourcesPool/bar', null],
|
|
12
|
+
['/DC1/host//Resources/PoolX', null],
|
|
13
|
+
['', null],
|
|
14
|
+
[null, null],
|
|
15
|
+
[undefined, null],
|
|
16
|
+
])('returns %p for pool %p', (pool: string | null | undefined, expected: string | null) => {
|
|
17
|
+
const machineTemplate = new VmwarevsphereMachineTemplate({ spec: { template: { spec: { pool } } } });
|
|
18
|
+
|
|
19
|
+
expect(machineTemplate.providerLocation).toStrictEqual(expected);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import Secret from '@shell/models/secret';
|
|
2
|
-
import { SECRET_TYPES as TYPES } from '@shell/config/secret';
|
|
2
|
+
import { SECRET_TYPES as TYPES, GITHUB_APP_SECRET_KEYS } from '@shell/config/secret';
|
|
3
3
|
import { VIRTUAL_TYPES } from '@shell/config/types';
|
|
4
4
|
import { UI_PROJECT_SECRET } from '@shell/config/labels-annotations';
|
|
5
|
+
import { base64Encode } from '@shell/utils/crypto';
|
|
5
6
|
|
|
6
7
|
describe('class Secret', () => {
|
|
7
8
|
describe('detailLocation', () => {
|
|
@@ -132,4 +133,70 @@ ${ part }`;
|
|
|
132
133
|
expect(result).toBe(supported);
|
|
133
134
|
});
|
|
134
135
|
});
|
|
136
|
+
|
|
137
|
+
describe('isGithubApp', () => {
|
|
138
|
+
const githubAppData = {
|
|
139
|
+
github_app_id: 'MTI=',
|
|
140
|
+
github_app_installation_id: 'MzQ=',
|
|
141
|
+
github_app_private_key: 'a2V5',
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
it.each([
|
|
145
|
+
[
|
|
146
|
+
false,
|
|
147
|
+
'type is not Opaque',
|
|
148
|
+
TYPES.SSH,
|
|
149
|
+
githubAppData,
|
|
150
|
+
],
|
|
151
|
+
[
|
|
152
|
+
false,
|
|
153
|
+
'data is null',
|
|
154
|
+
TYPES.OPAQUE,
|
|
155
|
+
null,
|
|
156
|
+
],
|
|
157
|
+
[
|
|
158
|
+
false,
|
|
159
|
+
'Opaque but missing a GitHub App key',
|
|
160
|
+
TYPES.OPAQUE,
|
|
161
|
+
{ github_app_id: 'MTI=', github_app_installation_id: 'MzQ=' },
|
|
162
|
+
],
|
|
163
|
+
[
|
|
164
|
+
true,
|
|
165
|
+
'Opaque with all GitHub App keys',
|
|
166
|
+
TYPES.OPAQUE,
|
|
167
|
+
githubAppData,
|
|
168
|
+
],
|
|
169
|
+
])('is %p if %p', (
|
|
170
|
+
expected,
|
|
171
|
+
descr,
|
|
172
|
+
_type,
|
|
173
|
+
data
|
|
174
|
+
) => {
|
|
175
|
+
const secret = new Secret({ _type, data });
|
|
176
|
+
|
|
177
|
+
expect(secret.isGithubApp).toBe(expected);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe('GitHub App display', () => {
|
|
182
|
+
const githubAppData = {
|
|
183
|
+
[GITHUB_APP_SECRET_KEYS.APP_ID]: base64Encode('12345'),
|
|
184
|
+
[GITHUB_APP_SECRET_KEYS.INSTALLATION_ID]: base64Encode('67890'),
|
|
185
|
+
[GITHUB_APP_SECRET_KEYS.PRIVATE_KEY]: base64Encode('a-private-key'),
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const ctx = { rootGetters: { 'i18n/withFallback': (_key: string, _args: any, fallback: string) => fallback } };
|
|
189
|
+
|
|
190
|
+
it('dataPreview should show the decoded App ID and Installation ID', () => {
|
|
191
|
+
const secret = new Secret({ _type: TYPES.OPAQUE, data: githubAppData }, ctx);
|
|
192
|
+
|
|
193
|
+
expect(secret.dataPreview).toBe('12345 / 67890');
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('subTypeDisplay should resolve to the GitHub App label', () => {
|
|
197
|
+
const secret = new Secret({ _type: TYPES.OPAQUE, data: githubAppData }, ctx);
|
|
198
|
+
|
|
199
|
+
expect(secret.subTypeDisplay).toBe('GitHub App');
|
|
200
|
+
});
|
|
201
|
+
});
|
|
135
202
|
});
|