@rancher/shell 3.0.9-rc.3 → 3.0.9-rc.5
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/assets/brand/suse/metadata.json +2 -1
- package/assets/translations/en-us.yaml +105 -5
- package/components/ActionMenuShell.vue +1 -1
- package/components/Inactivity.vue +2 -2
- package/components/Resource/Detail/Card/ExtrasCard.vue +49 -15
- package/components/Resource/Detail/Card/__tests__/ExtrasCard.test.ts +111 -0
- package/components/Resource/Detail/Masthead/__tests__/index.test.ts +0 -17
- package/components/Resource/Detail/Masthead/index.vue +11 -4
- package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +3 -1
- package/components/Resource/Detail/Metadata/index.vue +1 -1
- package/components/Resource/Detail/ResourceRow.vue +1 -1
- package/components/ResourceDetail/Masthead/latest.vue +12 -2
- package/components/ResourceList/index.vue +9 -0
- package/components/ResourceTable.vue +38 -4
- package/components/Tabbed/Tab.vue +4 -0
- package/components/Tabbed/index.vue +4 -1
- package/components/__tests__/ProjectRow.test.ts +60 -0
- package/components/form/ChangePassword.vue +41 -35
- package/components/form/ResourceQuota/Project.vue +42 -1
- package/components/form/ResourceQuota/ProjectRow.vue +71 -4
- package/components/form/ResourceQuota/__tests__/Project.test.ts +63 -0
- package/components/form/SelectOrCreateAuthSecret.vue +6 -1
- package/components/form/__tests__/SelectOrCreateAuthSecret.test.ts +35 -0
- package/components/formatter/KubeconfigClusters.vue +74 -0
- package/components/formatter/MachineSummaryGraph.vue +10 -2
- package/components/formatter/__tests__/KubeconfigClusters.test.ts +125 -0
- package/components/nav/TopLevelMenu.helper.ts +50 -2
- package/components/nav/TopLevelMenu.vue +14 -0
- package/components/nav/Type.vue +5 -0
- package/components/nav/__tests__/TopLevelMenu.test.ts +3 -3
- package/components/nav/__tests__/Type.test.ts +6 -4
- package/config/product/explorer.js +4 -3
- package/config/product/manager.js +47 -3
- package/config/router/navigation-guards/authentication.js +8 -9
- package/config/router/routes.js +4 -1
- package/config/types.js +10 -2
- package/detail/auditlog.cattle.io.auditpolicy.vue +19 -0
- package/detail/management.cattle.io.user.vue +1 -2
- package/detail/node.vue +0 -1
- package/detail/provisioning.cattle.io.cluster.vue +2 -1
- package/dialog/ChangePasswordDialog.vue +8 -0
- package/dialog/GenericPrompt.vue +20 -3
- package/dialog/ScaleMachineDownDialog.vue +65 -15
- package/dialog/SearchDialog.vue +10 -2
- package/dialog/__tests__/ScaleMachineDownDialog.test.ts +184 -0
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +89 -0
- package/edit/__tests__/management.cattle.io.project.test.js +56 -1
- package/edit/auditlog.cattle.io.auditpolicy/AdditionalRedactions.vue +114 -0
- package/edit/auditlog.cattle.io.auditpolicy/Filters.vue +119 -0
- package/edit/auditlog.cattle.io.auditpolicy/General.vue +180 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/AdditionalRedactions.test.ts +327 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/Filters.test.ts +449 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/General.test.ts +472 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/AdditionalRedactions.test.ts.snap +27 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/Filters.test.ts.snap +39 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +174 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/index.test.ts.snap +29 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/index.test.ts +215 -0
- package/edit/auditlog.cattle.io.auditpolicy/index.vue +104 -0
- package/edit/auditlog.cattle.io.auditpolicy/types.ts +28 -0
- package/edit/fleet.cattle.io.gitrepo.vue +16 -1
- package/edit/management.cattle.io.project.vue +8 -2
- package/edit/management.cattle.io.user.vue +29 -34
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +178 -0
- package/edit/provisioning.cattle.io.cluster/rke2.vue +22 -2
- package/edit/provisioning.cattle.io.cluster/shared.ts +4 -0
- package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +1 -0
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/S3Config.vue +57 -2
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/__tests__/S3Config.test.ts +109 -0
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +1 -0
- package/list/auditlog.cattle.io.auditpolicy.vue +63 -0
- package/list/ext.cattle.io.kubeconfig.vue +118 -0
- package/list/group.principal.vue +11 -15
- package/list/management.cattle.io.user.vue +11 -21
- package/machine-config/azure.vue +14 -0
- package/mixins/__tests__/chart.test.ts +147 -0
- package/mixins/browser-tab-visibility.js +5 -4
- package/mixins/chart.js +10 -8
- package/mixins/fetch.client.js +6 -0
- package/models/__tests__/auditlog.cattle.io.auditpolicy.test.ts +117 -0
- package/models/__tests__/ext.cattle.io.kubeconfig.test.ts +364 -0
- package/models/__tests__/secret.test.ts +55 -0
- package/models/__tests__/workload.test.ts +49 -6
- package/models/auditlog.cattle.io.auditpolicy.js +46 -0
- package/models/cluster.x-k8s.io.machine.js +1 -1
- package/models/cluster.x-k8s.io.machinedeployment.js +5 -5
- package/models/event.js +5 -0
- package/models/ext.cattle.io.groupmembershiprefreshrequest.js +15 -0
- package/models/ext.cattle.io.kubeconfig.ts +97 -0
- package/models/ext.cattle.io.passwordchangerequest.js +15 -0
- package/models/ext.cattle.io.selfuser.js +15 -0
- package/models/fleet-application.js +17 -7
- package/models/management.cattle.io.user.js +28 -31
- package/models/schema.js +18 -0
- package/models/secret.js +28 -25
- package/models/steve-schema.ts +39 -2
- package/models/workload.js +3 -2
- package/package.json +2 -2
- package/pages/about.vue +3 -2
- package/pages/account/index.vue +23 -16
- package/pages/auth/login.vue +15 -8
- package/pages/auth/setup.vue +52 -15
- package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +38 -14
- package/pages/c/_cluster/apps/charts/index.vue +1 -0
- package/pages/home.vue +9 -3
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +1 -3
- package/plugins/dashboard-store/actions.js +7 -0
- package/plugins/dashboard-store/getters.js +23 -1
- package/plugins/dashboard-store/index.js +3 -2
- package/plugins/dashboard-store/mutations.js +4 -0
- package/plugins/dashboard-store/resource-class.js +12 -5
- package/plugins/steve/__tests__/steve-class.test.ts +167 -0
- package/plugins/steve/schema.d.ts +5 -0
- package/plugins/steve/steve-class.js +19 -0
- package/plugins/steve/steve-pagination-utils.ts +2 -1
- package/rancher-components/RcItemCard/RcItemCard.test.ts +4 -2
- package/rancher-components/RcItemCard/RcItemCard.vue +27 -10
- package/store/auth.js +57 -19
- package/store/notifications.ts +1 -1
- package/store/type-map.js +12 -1
- package/types/shell/index.d.ts +24 -15
- package/types/store/dashboard-store.types.ts +7 -0
- package/utils/__tests__/chart.test.ts +96 -0
- package/utils/__tests__/version.test.ts +1 -19
- package/utils/chart.js +64 -0
- package/utils/pagination-wrapper.ts +11 -3
- package/utils/version.js +5 -17
- package/vue.config.js +26 -13
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref } from 'vue';
|
|
3
|
+
import LabeledInput from '@components/Form/LabeledInput/LabeledInput.vue';
|
|
4
|
+
import LabeledSelect from '@shell/components/form/LabeledSelect.vue';
|
|
5
|
+
import ArrayList from '@shell/components/form/ArrayList.vue';
|
|
6
|
+
import { AuditPolicy, FilterRule } from '@shell/edit/auditlog.cattle.io.auditpolicy/types';
|
|
7
|
+
|
|
8
|
+
// Component Props & Emits
|
|
9
|
+
const props = defineProps({
|
|
10
|
+
value: {
|
|
11
|
+
type: Object,
|
|
12
|
+
default: () => ({})
|
|
13
|
+
},
|
|
14
|
+
mode: {
|
|
15
|
+
type: String,
|
|
16
|
+
default: 'create'
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const emit = defineEmits<{
|
|
21
|
+
'update:value': [value: AuditPolicy];
|
|
22
|
+
}>();
|
|
23
|
+
|
|
24
|
+
// Default Values & Reactive Spec
|
|
25
|
+
const defaults: AuditPolicy = { filters: [] };
|
|
26
|
+
const spec = ref<AuditPolicy>({ ...defaults, ...props.value });
|
|
27
|
+
const defaultAddValue: FilterRule = {
|
|
28
|
+
action: 'allow',
|
|
29
|
+
requestURI: '',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Methods
|
|
33
|
+
function addRow(key: 'action' | 'requestURI', filters: FilterRule[]) {
|
|
34
|
+
const valueToEmit = { ...props.value, ...spec.value };
|
|
35
|
+
|
|
36
|
+
valueToEmit.filters = filters;
|
|
37
|
+
|
|
38
|
+
emit('update:value', valueToEmit);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function updateRow(key: 'action' | 'requestURI', index: number, value: string) {
|
|
42
|
+
const valueToEmit = { ...props.value, ...spec.value };
|
|
43
|
+
|
|
44
|
+
if (!valueToEmit.filters) {
|
|
45
|
+
valueToEmit.filters = [];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Ensure the filter exists at the given index
|
|
49
|
+
if (!valueToEmit.filters[index]) {
|
|
50
|
+
valueToEmit.filters[index] = { action: '', requestURI: '' };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
valueToEmit.filters[index][key] = value;
|
|
54
|
+
emit('update:value', valueToEmit);
|
|
55
|
+
}
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<template>
|
|
59
|
+
<div>
|
|
60
|
+
<div class="row mb-40">
|
|
61
|
+
<div class="col span-12">
|
|
62
|
+
<ArrayList
|
|
63
|
+
key="headers"
|
|
64
|
+
v-model:value="spec.filters"
|
|
65
|
+
:value-placeholder="t('auditPolicy.filters.placeholder')"
|
|
66
|
+
:add-label="t('auditPolicy.filters.add')"
|
|
67
|
+
:mode="mode"
|
|
68
|
+
:protip="false"
|
|
69
|
+
:defaultAddValue="defaultAddValue"
|
|
70
|
+
:show-header="true"
|
|
71
|
+
@update:value="addRow('action', $event)"
|
|
72
|
+
>
|
|
73
|
+
<template v-slot:column-headers>
|
|
74
|
+
<div class="filters-heading mb-10">
|
|
75
|
+
<div
|
|
76
|
+
class="row"
|
|
77
|
+
>
|
|
78
|
+
<div class="col span-6">
|
|
79
|
+
<span class="text-label">{{ t('auditPolicy.filters.action.title') }}</span>
|
|
80
|
+
</div>
|
|
81
|
+
<div class="col span-6 send-to">
|
|
82
|
+
<span class="text-label">{{ t('auditPolicy.filters.requestURI.title') }}</span>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</template>
|
|
87
|
+
<template v-slot:columns="scope">
|
|
88
|
+
<div class="row">
|
|
89
|
+
<div class="col span-6">
|
|
90
|
+
<LabeledSelect
|
|
91
|
+
v-model:value="scope.row.value.action"
|
|
92
|
+
:options="[{ value: 'allow', label: t('auditPolicy.filters.action.allow')},{ value: 'deny', label: t('auditPolicy.filters.action.deny') }]"
|
|
93
|
+
:mode="mode"
|
|
94
|
+
:placeholder="t('auditPolicy.filters.action.placeholder')"
|
|
95
|
+
@update:value="updateRow('action', scope.i, $event)"
|
|
96
|
+
/>
|
|
97
|
+
</div>
|
|
98
|
+
<div class="col span-6">
|
|
99
|
+
<LabeledInput
|
|
100
|
+
v-model:value="scope.row.value.requestURI"
|
|
101
|
+
:mode="mode"
|
|
102
|
+
:placeholder="t('auditPolicy.filters.requestURI.placeholder')"
|
|
103
|
+
@update:value="updateRow('requestURI', scope.i, $event, )"
|
|
104
|
+
/>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
</template>
|
|
108
|
+
</ArrayList>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
</template>
|
|
113
|
+
|
|
114
|
+
<style lang="scss" scoped>
|
|
115
|
+
.filters-heading {
|
|
116
|
+
display: grid;
|
|
117
|
+
grid-template-columns: auto $array-list-remove-margin;
|
|
118
|
+
}
|
|
119
|
+
</style>
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, ref, watch } from 'vue';
|
|
3
|
+
import { useStore } from 'vuex';
|
|
4
|
+
import { useI18n } from '@shell/composables/useI18n';
|
|
5
|
+
import LabeledSelect from '@shell/components/form/LabeledSelect.vue';
|
|
6
|
+
import Checkbox from '@components/Form/Checkbox/Checkbox.vue';
|
|
7
|
+
import { AuditPolicy } from '@shell/edit/auditlog.cattle.io.auditpolicy/types';
|
|
8
|
+
import Banner from '@components/Banner/Banner.vue';
|
|
9
|
+
|
|
10
|
+
// Component Props & Emits
|
|
11
|
+
const props = defineProps({
|
|
12
|
+
value: {
|
|
13
|
+
type: Object,
|
|
14
|
+
default: () => ({})
|
|
15
|
+
},
|
|
16
|
+
mode: {
|
|
17
|
+
type: String,
|
|
18
|
+
default: 'create'
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const emit = defineEmits<{
|
|
23
|
+
'update:value': [value: AuditPolicy];
|
|
24
|
+
}>();
|
|
25
|
+
|
|
26
|
+
// Options
|
|
27
|
+
const levelOptions = [0, 1, 2, 3];
|
|
28
|
+
|
|
29
|
+
// Store & i18n
|
|
30
|
+
const store = useStore();
|
|
31
|
+
const { t } = useI18n(store);
|
|
32
|
+
|
|
33
|
+
// Default Values & Reactive Spec
|
|
34
|
+
const defaults: AuditPolicy = {
|
|
35
|
+
enabled: false,
|
|
36
|
+
verbosity: {
|
|
37
|
+
level: 0, // The default level is 0, even if you set null, it will save as 0
|
|
38
|
+
request: {
|
|
39
|
+
headers: false,
|
|
40
|
+
body: false,
|
|
41
|
+
},
|
|
42
|
+
response: {
|
|
43
|
+
headers: false,
|
|
44
|
+
body: false,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const spec = ref<AuditPolicy>({
|
|
50
|
+
...defaults,
|
|
51
|
+
...props.value,
|
|
52
|
+
verbosity: {
|
|
53
|
+
...defaults.verbosity,
|
|
54
|
+
...props.value?.verbosity,
|
|
55
|
+
request: {
|
|
56
|
+
...defaults.verbosity?.request,
|
|
57
|
+
...props.value?.verbosity?.request,
|
|
58
|
+
},
|
|
59
|
+
response: {
|
|
60
|
+
...defaults.verbosity?.response,
|
|
61
|
+
...props.value?.verbosity?.response,
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Emit update immediately after initializing spec
|
|
67
|
+
emit('update:value', { ...props.value, ...spec.value });
|
|
68
|
+
|
|
69
|
+
// Watch for changes and emit updates
|
|
70
|
+
watch(spec, (newSpec) => {
|
|
71
|
+
const valueToEmit = { ...props.value, ...newSpec };
|
|
72
|
+
|
|
73
|
+
emit('update:value', valueToEmit);
|
|
74
|
+
}, { deep: true });
|
|
75
|
+
|
|
76
|
+
// Computed Properties
|
|
77
|
+
const levelOptionsMap = computed(() => levelOptions.map((value) => {
|
|
78
|
+
return { value, label: `${ t(`auditPolicy.general.verbosity.level.${ value }`) }` };
|
|
79
|
+
}));
|
|
80
|
+
</script>
|
|
81
|
+
|
|
82
|
+
<template>
|
|
83
|
+
<div>
|
|
84
|
+
<div class="row">
|
|
85
|
+
<div class="col span-6">
|
|
86
|
+
<fieldset>
|
|
87
|
+
<h3>{{ t("auditPolicy.general.enabled.title") }}</h3>
|
|
88
|
+
<Checkbox
|
|
89
|
+
v-model:value="spec.enabled"
|
|
90
|
+
:mode="mode"
|
|
91
|
+
label-key="auditPolicy.general.enabled.checkbox"
|
|
92
|
+
data-testid="auditPolicy-enabled"
|
|
93
|
+
/>
|
|
94
|
+
</fieldset>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
<div class="spacer" />
|
|
98
|
+
<div class="row">
|
|
99
|
+
<div class="col span-6">
|
|
100
|
+
<fieldset>
|
|
101
|
+
<h3>{{ t("auditPolicy.general.verbosity.title") }}</h3>
|
|
102
|
+
<Banner
|
|
103
|
+
class="mt-0"
|
|
104
|
+
color="info"
|
|
105
|
+
label-key="auditPolicy.general.verbosity.banner"
|
|
106
|
+
/>
|
|
107
|
+
|
|
108
|
+
<h4>
|
|
109
|
+
{{ t("auditPolicy.general.verbosity.level.title") }}
|
|
110
|
+
<i
|
|
111
|
+
v-clean-tooltip="t('auditPolicy.general.verbosity.level.tooltip')"
|
|
112
|
+
class="icon icon-info"
|
|
113
|
+
/>
|
|
114
|
+
</h4>
|
|
115
|
+
<div class="row">
|
|
116
|
+
<div class="col span-12">
|
|
117
|
+
<LabeledSelect
|
|
118
|
+
v-model:value="spec.verbosity!.level"
|
|
119
|
+
:label="t('auditPolicy.general.verbosity.level.label')"
|
|
120
|
+
:options="levelOptionsMap"
|
|
121
|
+
:mode="mode"
|
|
122
|
+
/>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
<div class="spacer-small" />
|
|
126
|
+
<div
|
|
127
|
+
class="row"
|
|
128
|
+
>
|
|
129
|
+
<div class="col span-6">
|
|
130
|
+
<h4>
|
|
131
|
+
{{ t("auditPolicy.general.verbosity.request.title") }}
|
|
132
|
+
<i
|
|
133
|
+
v-clean-tooltip="t('auditPolicy.general.verbosity.requestResponse.tooltip')"
|
|
134
|
+
class="icon icon-info"
|
|
135
|
+
/>
|
|
136
|
+
</h4>
|
|
137
|
+
<div class="row">
|
|
138
|
+
<Checkbox
|
|
139
|
+
v-model:value="spec.verbosity!.request!.headers"
|
|
140
|
+
:label="t('auditPolicy.general.verbosity.request.requestHeaders')"
|
|
141
|
+
:mode="mode"
|
|
142
|
+
/>
|
|
143
|
+
</div>
|
|
144
|
+
<div class="row">
|
|
145
|
+
<Checkbox
|
|
146
|
+
v-model:value="spec.verbosity!.request!.body"
|
|
147
|
+
:label="t('auditPolicy.general.verbosity.request.requestBody')"
|
|
148
|
+
:mode="mode"
|
|
149
|
+
/>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
<div class="col span-6">
|
|
153
|
+
<h4>
|
|
154
|
+
{{ t("auditPolicy.general.verbosity.response.title") }}
|
|
155
|
+
<i
|
|
156
|
+
v-clean-tooltip="t('auditPolicy.general.verbosity.requestResponse.tooltip')"
|
|
157
|
+
class="icon icon-info"
|
|
158
|
+
/>
|
|
159
|
+
</h4>
|
|
160
|
+
<div class="row">
|
|
161
|
+
<Checkbox
|
|
162
|
+
v-model:value="spec.verbosity!.response!.headers"
|
|
163
|
+
:label="t('auditPolicy.general.verbosity.response.responseHeaders')"
|
|
164
|
+
:mode="mode"
|
|
165
|
+
/>
|
|
166
|
+
</div>
|
|
167
|
+
<div class="row">
|
|
168
|
+
<Checkbox
|
|
169
|
+
v-model:value="spec.verbosity!.response!.body"
|
|
170
|
+
:label="t('auditPolicy.general.verbosity.response.responseBody')"
|
|
171
|
+
:mode="mode"
|
|
172
|
+
/>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
</fieldset>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
</template>
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import { shallowMount, VueWrapper } from '@vue/test-utils';
|
|
2
|
+
import AdditionalRedactions from '../AdditionalRedactions.vue';
|
|
3
|
+
import { ComponentPublicInstance } from 'vue';
|
|
4
|
+
import { AuditPolicy } from '@shell/edit/auditlog.cattle.io.auditpolicy/types';
|
|
5
|
+
|
|
6
|
+
// Mock the ID generation to have consistent snapshots
|
|
7
|
+
jest.mock('@shell/utils/string', () => ({ generateRandomAlphaString: () => 'test-id-123' }));
|
|
8
|
+
|
|
9
|
+
interface AdditionalRedactionsComponent extends ComponentPublicInstance {
|
|
10
|
+
spec: AuditPolicy;
|
|
11
|
+
addRedaction: () => void;
|
|
12
|
+
removeRedaction: (index: number) => void;
|
|
13
|
+
redactionLabel: (index: number) => string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const defaultProps = {
|
|
17
|
+
value: { additionalRedactions: [] },
|
|
18
|
+
mode: 'create'
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const globalMocks = {
|
|
22
|
+
global: {
|
|
23
|
+
mocks: {
|
|
24
|
+
$t: (key: string) => key,
|
|
25
|
+
t: (key: string) => key,
|
|
26
|
+
$store: {
|
|
27
|
+
getters: { 'i18n/t': (key: string) => key },
|
|
28
|
+
dispatch: jest.fn()
|
|
29
|
+
},
|
|
30
|
+
$route: {
|
|
31
|
+
params: {},
|
|
32
|
+
query: {}
|
|
33
|
+
},
|
|
34
|
+
$router: {
|
|
35
|
+
push: jest.fn(),
|
|
36
|
+
replace: jest.fn()
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
stubs: {
|
|
40
|
+
Tabbed: true,
|
|
41
|
+
Tab: true,
|
|
42
|
+
ArrayList: true
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
function factory(props: Record<string, any> = {}, options: Record<string, any> = {}): VueWrapper<AdditionalRedactionsComponent> {
|
|
48
|
+
return shallowMount(AdditionalRedactions, {
|
|
49
|
+
props: { ...defaultProps, ...props },
|
|
50
|
+
...globalMocks,
|
|
51
|
+
...options
|
|
52
|
+
}) as unknown as VueWrapper<AdditionalRedactionsComponent>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
describe('component: AdditionalRedactions', () => {
|
|
56
|
+
describe('rendering & initial state', () => {
|
|
57
|
+
it('should render with default props (snapshot)', () => {
|
|
58
|
+
const wrapper = factory();
|
|
59
|
+
|
|
60
|
+
expect(wrapper.element).toMatchSnapshot();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should render with create mode', () => {
|
|
64
|
+
const wrapper = factory({ mode: 'create' });
|
|
65
|
+
|
|
66
|
+
expect(wrapper.exists()).toBe(true);
|
|
67
|
+
expect(wrapper.find('.row.mb-40').exists()).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should render with edit mode', () => {
|
|
71
|
+
const wrapper = factory({ mode: 'edit' });
|
|
72
|
+
|
|
73
|
+
expect(wrapper.exists()).toBe(true);
|
|
74
|
+
expect(wrapper.find('.row.mb-40').exists()).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should render with initial redactions data', () => {
|
|
78
|
+
const value = {
|
|
79
|
+
additionalRedactions: [
|
|
80
|
+
{ headers: ['X-Test-Header'], paths: ['/api/test'] },
|
|
81
|
+
{ headers: ['Authorization'], paths: ['/secure'] }
|
|
82
|
+
]
|
|
83
|
+
};
|
|
84
|
+
const wrapper = factory({ value });
|
|
85
|
+
|
|
86
|
+
expect(wrapper.vm.spec.additionalRedactions).toHaveLength(2);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('props & state changes', () => {
|
|
91
|
+
it('should handle empty value prop gracefully', () => {
|
|
92
|
+
const wrapper = factory({ value: undefined });
|
|
93
|
+
|
|
94
|
+
expect(wrapper.exists()).toBe(true);
|
|
95
|
+
expect(wrapper.vm.spec.additionalRedactions).toStrictEqual([]);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should handle null value prop gracefully', () => {
|
|
99
|
+
const wrapper = factory({ value: null });
|
|
100
|
+
|
|
101
|
+
expect(wrapper.exists()).toBe(true);
|
|
102
|
+
expect(wrapper.vm.spec.additionalRedactions).toStrictEqual([]);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should update when mode prop changes', async() => {
|
|
106
|
+
const wrapper = factory({ mode: 'create' });
|
|
107
|
+
|
|
108
|
+
expect((wrapper.props() as any).mode).toBe('create');
|
|
109
|
+
await wrapper.setProps({ mode: 'view' });
|
|
110
|
+
expect((wrapper.props() as any).mode).toBe('view');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should merge defaults with provided value', () => {
|
|
114
|
+
const value = {
|
|
115
|
+
additionalRedactions: [{ headers: ['Custom'], paths: ['/custom'] }],
|
|
116
|
+
customProp: 'test'
|
|
117
|
+
};
|
|
118
|
+
const wrapper = factory({ value });
|
|
119
|
+
|
|
120
|
+
expect((wrapper.vm.spec.additionalRedactions ?? [])).toHaveLength(1);
|
|
121
|
+
expect((wrapper.vm.spec.additionalRedactions ?? [])[0]).toStrictEqual({ headers: ['Custom'], paths: ['/custom'] });
|
|
122
|
+
expect((wrapper.vm.spec as any).customProp).toBe('test');
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe('user interaction', () => {
|
|
127
|
+
it('should emit update:value when addRedaction is called', () => {
|
|
128
|
+
const wrapper = factory();
|
|
129
|
+
|
|
130
|
+
wrapper.vm.addRedaction();
|
|
131
|
+
|
|
132
|
+
expect(wrapper.emitted('update:value')).toBeTruthy();
|
|
133
|
+
const events = wrapper.emitted('update:value');
|
|
134
|
+
|
|
135
|
+
expect(events && events[0]).toBeTruthy();
|
|
136
|
+
|
|
137
|
+
const emitted = events && events[0] && events[0][0] as AuditPolicy;
|
|
138
|
+
|
|
139
|
+
expect(emitted && emitted.additionalRedactions?.length).toBe(1);
|
|
140
|
+
expect(emitted && emitted.additionalRedactions?.[0]).toStrictEqual({
|
|
141
|
+
headers: [],
|
|
142
|
+
paths: []
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should emit update:value when removeRedaction is called', () => {
|
|
147
|
+
const value = {
|
|
148
|
+
additionalRedactions: [
|
|
149
|
+
{ headers: ['X-Test'], paths: ['/foo'] },
|
|
150
|
+
{ headers: ['Authorization'], paths: ['/bar'] }
|
|
151
|
+
]
|
|
152
|
+
};
|
|
153
|
+
const wrapper = factory({ value });
|
|
154
|
+
|
|
155
|
+
wrapper.vm.removeRedaction(0);
|
|
156
|
+
|
|
157
|
+
expect(wrapper.emitted('update:value')).toBeTruthy();
|
|
158
|
+
const events = wrapper.emitted('update:value');
|
|
159
|
+
|
|
160
|
+
expect(events && events[0]).toBeTruthy();
|
|
161
|
+
|
|
162
|
+
const emitted = events && events[0] && events[0][0] as AuditPolicy;
|
|
163
|
+
|
|
164
|
+
expect(emitted && emitted.additionalRedactions?.length).toBe(1);
|
|
165
|
+
expect(emitted && emitted.additionalRedactions?.[0]).toStrictEqual({
|
|
166
|
+
headers: ['Authorization'],
|
|
167
|
+
paths: ['/bar']
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should preserve existing prop values when emitting updates', () => {
|
|
172
|
+
const existingValue = { someOtherProp: 'existing' };
|
|
173
|
+
const wrapper = factory({ value: existingValue });
|
|
174
|
+
|
|
175
|
+
wrapper.vm.addRedaction();
|
|
176
|
+
|
|
177
|
+
const events = wrapper.emitted('update:value');
|
|
178
|
+
|
|
179
|
+
expect(events && events[0]).toBeTruthy();
|
|
180
|
+
const emitted = events && events[0] && events[0][0] as AuditPolicy & { someOtherProp: string };
|
|
181
|
+
|
|
182
|
+
expect(emitted && emitted.someOtherProp).toBe('existing');
|
|
183
|
+
expect(emitted && emitted.additionalRedactions).toHaveLength(1);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('should remove correct redaction by index', () => {
|
|
187
|
+
const value = {
|
|
188
|
+
additionalRedactions: [
|
|
189
|
+
{ headers: ['First'], paths: ['/first'] },
|
|
190
|
+
{ headers: ['Second'], paths: ['/second'] },
|
|
191
|
+
{ headers: ['Third'], paths: ['/third'] }
|
|
192
|
+
]
|
|
193
|
+
};
|
|
194
|
+
const wrapper = factory({ value });
|
|
195
|
+
|
|
196
|
+
wrapper.vm.removeRedaction(1); // Remove middle item
|
|
197
|
+
|
|
198
|
+
const events = wrapper.emitted('update:value');
|
|
199
|
+
|
|
200
|
+
expect(events && events[0]).toBeTruthy();
|
|
201
|
+
const emitted = events && events[0] && events[0][0] as AuditPolicy;
|
|
202
|
+
|
|
203
|
+
expect(emitted && emitted.additionalRedactions).toHaveLength(2);
|
|
204
|
+
expect(emitted && emitted.additionalRedactions?.[0]).toStrictEqual({ headers: ['First'], paths: ['/first'] });
|
|
205
|
+
expect(emitted && emitted.additionalRedactions?.[1]).toStrictEqual({ headers: ['Third'], paths: ['/third'] });
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
describe('computed properties & logic', () => {
|
|
210
|
+
it('should return correct redactionLabel values', () => {
|
|
211
|
+
const wrapper = factory();
|
|
212
|
+
|
|
213
|
+
expect(wrapper.vm.redactionLabel(0)).toBe('Rule 1');
|
|
214
|
+
expect(wrapper.vm.redactionLabel(4)).toBe('Rule 5');
|
|
215
|
+
expect(wrapper.vm.redactionLabel(99)).toBe('Rule 100');
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('should have reactive redactionLabel computed property', () => {
|
|
219
|
+
const wrapper = factory();
|
|
220
|
+
const labelFn = wrapper.vm.redactionLabel;
|
|
221
|
+
|
|
222
|
+
// Test that it returns a function that computes labels
|
|
223
|
+
expect(typeof labelFn).toBe('function');
|
|
224
|
+
expect(labelFn(0)).toBe('Rule 1');
|
|
225
|
+
expect(labelFn(1)).toBe('Rule 2');
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('should initialize spec reactive ref correctly', () => {
|
|
229
|
+
const wrapper = factory();
|
|
230
|
+
|
|
231
|
+
expect(wrapper.vm.spec).toBeDefined();
|
|
232
|
+
expect(Array.isArray(wrapper.vm.spec.additionalRedactions ?? [])).toBe(true);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('should merge defaults with props correctly in spec', () => {
|
|
236
|
+
const value = { additionalRedactions: [{ headers: ['Test'], paths: ['/test'] }] };
|
|
237
|
+
const wrapper = factory({ value });
|
|
238
|
+
|
|
239
|
+
expect((wrapper.vm.spec.additionalRedactions ?? [])).toHaveLength(1);
|
|
240
|
+
expect((wrapper.vm.spec.additionalRedactions ?? [])[0]).toStrictEqual({
|
|
241
|
+
headers: ['Test'],
|
|
242
|
+
paths: ['/test']
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
describe('component configuration', () => {
|
|
248
|
+
it('should configure Tabbed component with correct props', () => {
|
|
249
|
+
const wrapper = factory();
|
|
250
|
+
const tabbedComponent = wrapper.findComponent({ name: 'Tabbed' });
|
|
251
|
+
|
|
252
|
+
expect(tabbedComponent.exists()).toBe(true);
|
|
253
|
+
expect(tabbedComponent.props('sideTabs')).toBe(true);
|
|
254
|
+
expect(tabbedComponent.props('useHash')).toBe(true);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('should show/hide add/remove tabs based on mode', () => {
|
|
258
|
+
const createWrapper = factory({ mode: 'create' });
|
|
259
|
+
const editWrapper = factory({ mode: 'edit' });
|
|
260
|
+
const viewWrapper = factory({ mode: 'view' });
|
|
261
|
+
|
|
262
|
+
expect(createWrapper.findComponent({ name: 'Tabbed' }).props('showTabsAddRemove')).toBe(true);
|
|
263
|
+
expect(editWrapper.findComponent({ name: 'Tabbed' }).props('showTabsAddRemove')).toBe(true);
|
|
264
|
+
expect(viewWrapper.findComponent({ name: 'Tabbed' }).props('showTabsAddRemove')).toBe(false);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it('should bind correct event handlers to Tabbed component', () => {
|
|
268
|
+
const wrapper = factory();
|
|
269
|
+
const tabbedComponent = wrapper.findComponent({ name: 'Tabbed' });
|
|
270
|
+
|
|
271
|
+
// Simulate events from Tabbed component
|
|
272
|
+
tabbedComponent.vm.$emit('addTab');
|
|
273
|
+
expect(wrapper.emitted('update:value')).toBeTruthy();
|
|
274
|
+
|
|
275
|
+
const value = { additionalRedactions: [{ headers: [], paths: [] }] };
|
|
276
|
+
const wrapperWithData = factory({ value });
|
|
277
|
+
const tabbedWithData = wrapperWithData.findComponent({ name: 'Tabbed' });
|
|
278
|
+
|
|
279
|
+
tabbedWithData.vm.$emit('removeTab', 0);
|
|
280
|
+
expect(wrapperWithData.emitted('update:value')).toBeTruthy();
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
describe('edge cases', () => {
|
|
285
|
+
it('should handle empty additionalRedactions array', () => {
|
|
286
|
+
const wrapper = factory({ value: { additionalRedactions: [] } });
|
|
287
|
+
|
|
288
|
+
expect((wrapper.vm.spec.additionalRedactions ?? [])).toStrictEqual([]);
|
|
289
|
+
expect(wrapper.findAllComponents({ name: 'Tab' })).toHaveLength(0);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('should handle missing additionalRedactions property', () => {
|
|
293
|
+
const wrapper = factory({ value: {} });
|
|
294
|
+
|
|
295
|
+
expect((wrapper.vm.spec.additionalRedactions ?? [])).toStrictEqual([]);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('should not crash when removing from empty array', () => {
|
|
299
|
+
const wrapper = factory({ value: { additionalRedactions: [] } });
|
|
300
|
+
|
|
301
|
+
expect(() => {
|
|
302
|
+
wrapper.vm.removeRedaction(0);
|
|
303
|
+
}).not.toThrow();
|
|
304
|
+
|
|
305
|
+
const events = wrapper.emitted('update:value');
|
|
306
|
+
|
|
307
|
+
expect(events && events[0]).toBeTruthy();
|
|
308
|
+
const emitted = events && events[0] && events[0][0] as AuditPolicy;
|
|
309
|
+
|
|
310
|
+
expect(emitted && emitted.additionalRedactions).toStrictEqual([]);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('should handle out of bounds removal index gracefully', () => {
|
|
314
|
+
const value = { additionalRedactions: [{ headers: ['Test'], paths: ['/test'] }] };
|
|
315
|
+
const wrapper = factory({ value });
|
|
316
|
+
|
|
317
|
+
wrapper.vm.removeRedaction(5); // Index out of bounds
|
|
318
|
+
|
|
319
|
+
const events = wrapper.emitted('update:value');
|
|
320
|
+
|
|
321
|
+
expect(events && events[0]).toBeTruthy();
|
|
322
|
+
const emitted = events && events[0] && events[0][0] as AuditPolicy;
|
|
323
|
+
|
|
324
|
+
expect(emitted && emitted.additionalRedactions).toHaveLength(1); // Should remain unchanged
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
});
|